mirror of
https://github.com/node-ebics/node-ebics-client.git
synced 2025-08-14 11:45:35 +00:00
Compare commits
35 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
4a09c8e260 | ||
|
3e4ea41090 | ||
|
8dd84444ff | ||
|
e810ce241f | ||
|
b2ae16b933 | ||
|
ac1b554144 | ||
|
bf64593440 | ||
|
138a2a2e6e | ||
|
aa86eaaffe | ||
|
6cb025f441 | ||
|
c1b87a9e32 | ||
|
412c0146db | ||
|
d96b5e650b | ||
|
1fbe17846d | ||
|
a07822e953 | ||
|
c1022e5ca8 | ||
|
ccaa3f14c9 | ||
|
388156a2e4 | ||
|
231830addc | ||
|
475fc58289 | ||
|
ba997d312b | ||
|
bc4b33f7e4 | ||
|
b1abf16864 | ||
|
fd7bdfe55d | ||
|
e41c0bf74d | ||
|
36b8d2999c | ||
|
510654f52e | ||
|
e251cc0e5b | ||
|
8da104515c | ||
|
f3bd654739 | ||
|
b6b27516d5 | ||
|
a03ec2283f | ||
|
66bf57f0dc | ||
|
3a8dc1da0b | ||
|
a3ce00eb3e |
23
.github/workflows/CI.yml
vendored
Normal file
23
.github/workflows/CI.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main, master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node: [ 18, 20, 22 ]
|
||||||
|
name: Node.js ${{ matrix.node }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Setup node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node }}
|
||||||
|
- run: npm ci
|
||||||
|
- run: npm test
|
10
.travis.yml
10
.travis.yml
@@ -1,8 +1,8 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- "8"
|
- "16" # maintance lts
|
||||||
- "9"
|
- "17"
|
||||||
- "10"
|
- "18" # active lts
|
||||||
- "11"
|
- "19"
|
||||||
- "12"
|
- "20" # current
|
||||||
after_success: npm run coverage
|
after_success: npm run coverage
|
||||||
|
61
CHANGELOG.md
61
CHANGELOG.md
@@ -1,5 +1,27 @@
|
|||||||
### Changelog
|
### Changelog
|
||||||
|
|
||||||
|
#### [v4.2.0]
|
||||||
|
|
||||||
|
- feat: update dependencies [`a03ec22`](https://github.com/node-ebics/node-ebics-client/commit/a03ec2283fdd3dcf750c5ed448f0195d92c5c9f0)
|
||||||
|
- chore: Update dev dependency libxmljs [`3a8dc1d`](https://github.com/node-ebics/node-ebics-client/commit/3a8dc1da0bac20a6f25a687da0a783f3b52d5163)
|
||||||
|
- feat: replace <request> with <rock-req> [`b6b2751`](https://github.com/node-ebics/node-ebics-client/commit/b6b27516d5854b38dcdbe56f23967001f844e631)
|
||||||
|
|
||||||
|
#### [v4.0.0](https://github.com/node-ebics/node-ebics-client/compare/v3.0.0...v0.4.0)
|
||||||
|
|
||||||
|
- Add CI [`#75`](https://github.com/node-ebics/node-ebics-client/pull/75)
|
||||||
|
|
||||||
|
- feat: update dependencies [`a03ec22`](https://github.com/node-ebics/node-ebics-client/commit/a03ec2283fdd3dcf750c5ed448f0195d92c5c9f0)
|
||||||
|
- chore: Update dev dependency libxmljs [`3a8dc1d`](https://github.com/node-ebics/node-ebics-client/commit/3a8dc1da0bac20a6f25a687da0a783f3b52d5163)
|
||||||
|
- feat: replace <request> with <rock-req> [`b6b2751`](https://github.com/node-ebics/node-ebics-client/commit/b6b27516d5854b38dcdbe56f23967001f844e631)
|
||||||
|
|
||||||
|
### [v3.0.0](https://github.com/node-ebics/node-ebics-client/compare/v0.2.1...v3.0.0)
|
||||||
|
|
||||||
|
> 4 August 2022
|
||||||
|
|
||||||
|
- Update LICENSE [`7520a56`](https://github.com/node-ebics/node-ebics-client/commit/7520a56be8ba446d3466c6dc9202b1d65ea3396f)
|
||||||
|
- changelog [`fe0f585`](https://github.com/node-ebics/node-ebics-client/commit/fe0f585b277c390415841a343aa86d4533997057)
|
||||||
|
- chore: update license in package.json, bump version [`b46b214`](https://github.com/node-ebics/node-ebics-client/commit/b46b214e57cf1b4e30455da306b26b3a06dc8a5f)
|
||||||
|
|
||||||
#### [v0.2.1](https://github.com/node-ebics/node-ebics-client/compare/v0.2.0...v0.2.1)
|
#### [v0.2.1](https://github.com/node-ebics/node-ebics-client/compare/v0.2.0...v0.2.1)
|
||||||
|
|
||||||
> 30 March 2021
|
> 30 March 2021
|
||||||
@@ -84,38 +106,43 @@
|
|||||||
|
|
||||||
#### [v0.1.1](https://github.com/node-ebics/node-ebics-client/compare/v0.1.0...v0.1.1)
|
#### [v0.1.1](https://github.com/node-ebics/node-ebics-client/compare/v0.1.0...v0.1.1)
|
||||||
|
|
||||||
> 5 November 2019
|
> 7 November 2019
|
||||||
|
|
||||||
- chore: update license [`9aabe93`](https://github.com/node-ebics/node-ebics-client/commit/9aabe933e91b506ea38820b952ce8e5e58b4c2ff)
|
- chore: update license [`9aabe93`](https://github.com/node-ebics/node-ebics-client/commit/9aabe933e91b506ea38820b952ce8e5e58b4c2ff)
|
||||||
|
|
||||||
#### [v0.1.0](https://github.com/node-ebics/node-ebics-client/compare/v0.0.8...v0.1.0)
|
#### [v0.1.0](https://github.com/node-ebics/node-ebics-client/compare/v0.0.36...v0.1.0)
|
||||||
|
|
||||||
> 5 November 2019
|
> 5 November 2019
|
||||||
|
|
||||||
- Feat: handle unsual exponent migrate to node-forge [`#16`](https://github.com/node-ebics/node-ebics-client/pull/16)
|
- Feat: handle unsual exponent migrate to node-forge [`#16`](https://github.com/node-ebics/node-ebics-client/pull/16)
|
||||||
- Add order generation tests and fix linting [`#15`](https://github.com/node-ebics/node-ebics-client/pull/15)
|
- Add order generation tests and fix linting [`#15`](https://github.com/node-ebics/node-ebics-client/pull/15)
|
||||||
- test script run mocha [`#14`](https://github.com/node-ebics/node-ebics-client/pull/14)
|
- test script run mocha [`#14`](https://github.com/node-ebics/node-ebics-client/pull/14)
|
||||||
|
- chore: readme maintenance [`#11`](https://github.com/node-ebics/node-ebics-client/pull/11)
|
||||||
|
- Provide examples [`#10`](https://github.com/node-ebics/node-ebics-client/pull/10)
|
||||||
|
- * Add Z53 order type [`#7`](https://github.com/node-ebics/node-ebics-client/pull/7)
|
||||||
|
- remove eCollect from constant and minor verbose error handeling [`#2`](https://github.com/node-ebics/node-ebics-client/pull/2)
|
||||||
|
- add iso pain format XCT order type [`#1`](https://github.com/node-ebics/node-ebics-client/pull/1)
|
||||||
|
- remove eCollect from constant and minor verbose error handaling [`#4`](https://github.com/node-ebics/node-ebics-client/pull/4)
|
||||||
|
- add iso pain format XCT order type [`#5`](https://github.com/node-ebics/node-ebics-client/pull/5)
|
||||||
|
- Fix parsing of the bank keys in the HPB response [`#3`](https://github.com/node-ebics/node-ebics-client/pull/3)
|
||||||
|
|
||||||
- feat: prepare order generation tests [`e40f79c`](https://github.com/node-ebics/node-ebics-client/commit/e40f79cee68a194272c93f07e763175b213a77a1)
|
- feat: prepare order generation tests [`e40f79c`](https://github.com/node-ebics/node-ebics-client/commit/e40f79cee68a194272c93f07e763175b213a77a1)
|
||||||
- chore: cleanup [`0c01420`](https://github.com/node-ebics/node-ebics-client/commit/0c01420c1e14992a4169098ccd47cd196b899f06)
|
- Major changes. Separating responsibilities. Orders builder, serializer. [`ff9a3a1`](https://github.com/node-ebics/node-ebics-client/commit/ff9a3a16b47d0a25674134c875bfd651995837e4)
|
||||||
|
- code optimization [`1876360`](https://github.com/node-ebics/node-ebics-client/commit/187636019c290d757aca77d4c14fb4f2519acd38)
|
||||||
|
|
||||||
#### [v0.0.36](https://github.com/node-ebics/node-ebics-client/compare/v0.0.35...v0.0.36)
|
#### [v0.0.36](https://github.com/node-ebics/node-ebics-client/compare/v0.0.35...v0.0.36)
|
||||||
|
|
||||||
> 1 June 2018
|
> 7 November 2019
|
||||||
|
|
||||||
- various modular fixes [`8492d94`](https://github.com/node-ebics/node-ebics-client/commit/8492d940542f61b17aa3a2da7de23f6539ffaad5)
|
- various modular fixes [`8492d94`](https://github.com/node-ebics/node-ebics-client/commit/8492d940542f61b17aa3a2da7de23f6539ffaad5)
|
||||||
|
|
||||||
#### [v0.0.35](https://github.com/node-ebics/node-ebics-client/compare/v0.0.3...v0.0.35)
|
#### [v0.0.35](https://github.com/node-ebics/node-ebics-client/compare/v0.0.8...v0.0.35)
|
||||||
|
|
||||||
> 31 May 2018
|
> 7 November 2019
|
||||||
|
|
||||||
- update License to GPL v3 [`babcf76`](https://github.com/node-ebics/node-ebics-client/commit/babcf76b61af6eb737ab291a301e71bb84621820)
|
|
||||||
- Add MGF1.js file - mask generation utility class [`7e6e2ff`](https://github.com/node-ebics/node-ebics-client/commit/7e6e2ff142688b0c453369fa7137b49e8b89cd81)
|
|
||||||
- Add sign, _emsaPSS and _modPowe methods in Key.js file [`5ace213`](https://github.com/node-ebics/node-ebics-client/commit/5ace2137231af9a3563ab31fa0f70fbdf4b148cb)
|
|
||||||
|
|
||||||
#### [v0.0.8](https://github.com/node-ebics/node-ebics-client/compare/v0.0.7...v0.0.8)
|
#### [v0.0.8](https://github.com/node-ebics/node-ebics-client/compare/v0.0.7...v0.0.8)
|
||||||
|
|
||||||
> 8 October 2019
|
> 7 November 2019
|
||||||
|
|
||||||
- chore: readme maintenance [`#11`](https://github.com/node-ebics/node-ebics-client/pull/11)
|
- chore: readme maintenance [`#11`](https://github.com/node-ebics/node-ebics-client/pull/11)
|
||||||
- Provide examples [`#10`](https://github.com/node-ebics/node-ebics-client/pull/10)
|
- Provide examples [`#10`](https://github.com/node-ebics/node-ebics-client/pull/10)
|
||||||
@@ -126,7 +153,7 @@
|
|||||||
|
|
||||||
#### [v0.0.7](https://github.com/node-ebics/node-ebics-client/compare/v0.0.6...v0.0.7)
|
#### [v0.0.7](https://github.com/node-ebics/node-ebics-client/compare/v0.0.6...v0.0.7)
|
||||||
|
|
||||||
> 2 August 2019
|
> 7 November 2019
|
||||||
|
|
||||||
- * Add Z53 order type [`#7`](https://github.com/node-ebics/node-ebics-client/pull/7)
|
- * Add Z53 order type [`#7`](https://github.com/node-ebics/node-ebics-client/pull/7)
|
||||||
- remove eCollect from constant and minor verbose error handeling [`#2`](https://github.com/node-ebics/node-ebics-client/pull/2)
|
- remove eCollect from constant and minor verbose error handeling [`#2`](https://github.com/node-ebics/node-ebics-client/pull/2)
|
||||||
@@ -138,7 +165,7 @@
|
|||||||
|
|
||||||
#### [v0.0.6](https://github.com/node-ebics/node-ebics-client/compare/v0.0.5...v0.0.6)
|
#### [v0.0.6](https://github.com/node-ebics/node-ebics-client/compare/v0.0.5...v0.0.6)
|
||||||
|
|
||||||
> 24 July 2019
|
> 7 November 2019
|
||||||
|
|
||||||
- remove eCollect from constant and minor verbose error handaling [`#4`](https://github.com/node-ebics/node-ebics-client/pull/4)
|
- remove eCollect from constant and minor verbose error handaling [`#4`](https://github.com/node-ebics/node-ebics-client/pull/4)
|
||||||
- add iso pain format XCT order type [`#5`](https://github.com/node-ebics/node-ebics-client/pull/5)
|
- add iso pain format XCT order type [`#5`](https://github.com/node-ebics/node-ebics-client/pull/5)
|
||||||
@@ -148,24 +175,24 @@
|
|||||||
|
|
||||||
#### [v0.0.5](https://github.com/node-ebics/node-ebics-client/compare/v0.0.4...v0.0.5)
|
#### [v0.0.5](https://github.com/node-ebics/node-ebics-client/compare/v0.0.4...v0.0.5)
|
||||||
|
|
||||||
> 28 June 2019
|
> 7 November 2019
|
||||||
|
|
||||||
- Fix parsing of the bank keys in the HPB response [`#3`](https://github.com/node-ebics/node-ebics-client/pull/3)
|
- Fix parsing of the bank keys in the HPB response [`#3`](https://github.com/node-ebics/node-ebics-client/pull/3)
|
||||||
|
|
||||||
- tc for bank keys parsing error [`c571ef1`](https://github.com/node-ebics/node-ebics-client/commit/c571ef181bca2e0cbec70bc6df53c706acd6c829)
|
- tc for bank keys parsing error [`c571ef1`](https://github.com/node-ebics/node-ebics-client/commit/c571ef181bca2e0cbec70bc6df53c706acd6c829)
|
||||||
- #2 corrected bank keys parsing [`5f0b6cd`](https://github.com/node-ebics/node-ebics-client/commit/5f0b6cd3747c4613920d2f71f3c04ce13225d397)
|
- #2 corrected bank keys parsing [`5f0b6cd`](https://github.com/node-ebics/node-ebics-client/commit/5f0b6cd3747c4613920d2f71f3c04ce13225d397)
|
||||||
|
|
||||||
#### [v0.0.4](https://github.com/node-ebics/node-ebics-client/compare/v0.0.36...v0.0.4)
|
#### [v0.0.4](https://github.com/node-ebics/node-ebics-client/compare/v0.0.3...v0.0.4)
|
||||||
|
|
||||||
> 3 September 2018
|
> 3 September 2018
|
||||||
|
|
||||||
- Major changes. Separating responsibilities. Orders builder, serializer. [`ff9a3a1`](https://github.com/node-ebics/node-ebics-client/commit/ff9a3a16b47d0a25674134c875bfd651995837e4)
|
- Major changes. Separating responsibilities. Orders builder, serializer. [`ff9a3a1`](https://github.com/node-ebics/node-ebics-client/commit/ff9a3a16b47d0a25674134c875bfd651995837e4)
|
||||||
- code optimization [`1876360`](https://github.com/node-ebics/node-ebics-client/commit/187636019c290d757aca77d4c14fb4f2519acd38)
|
- code optimization [`1876360`](https://github.com/node-ebics/node-ebics-client/commit/187636019c290d757aca77d4c14fb4f2519acd38)
|
||||||
- client and order optimization [`9454992`](https://github.com/node-ebics/node-ebics-client/commit/945499290a8698aed504b573019de2c23148006a)
|
- various modular fixes [`8492d94`](https://github.com/node-ebics/node-ebics-client/commit/8492d940542f61b17aa3a2da7de23f6539ffaad5)
|
||||||
|
|
||||||
#### v0.0.3
|
#### v0.0.3
|
||||||
|
|
||||||
> 17 May 2018
|
> 7 November 2019
|
||||||
|
|
||||||
- initial commit [`1f947ff`](https://github.com/node-ebics/node-ebics-client/commit/1f947ff1480c522f89fa1f547581b55e2378d920)
|
- initial commit [`1f947ff`](https://github.com/node-ebics/node-ebics-client/commit/1f947ff1480c522f89fa1f547581b55e2378d920)
|
||||||
- Initial commit [`cd37de3`](https://github.com/node-ebics/node-ebics-client/commit/cd37de3895e32a61798c79ce3a6447e2f269019d)
|
- Initial commit [`cd37de3`](https://github.com/node-ebics/node-ebics-client/commit/cd37de3895e32a61798c79ce3a6447e2f269019d)
|
||||||
|
49
README.md
49
README.md
@@ -15,30 +15,55 @@
|
|||||||
<a href='https://coveralls.io/github/eCollect/node-ebics-client?branch=master' title="Coverage Status"><img src='https://coveralls.io/repos/github/eCollect/node-ebics-client/badge.svg?branch=master' alt='Coverage Status' /></a>
|
<a href='https://coveralls.io/github/eCollect/node-ebics-client?branch=master' title="Coverage Status"><img src='https://coveralls.io/repos/github/eCollect/node-ebics-client/badge.svg?branch=master' alt='Coverage Status' /></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Pure node.js ( >=8 ) implementation of [EBICS](https://en.wikipedia.org/wiki/Electronic_Banking_Internet_Communication_Standard) ( Electronic Banking Internet Communication ).
|
Pure Node.js (>= 16) implementation of [EBICS](https://en.wikipedia.org/wiki/Electronic_Banking_Internet_Communication_Standard) (Electronic Banking Internet Communication).
|
||||||
|
|
||||||
The client is aimed to be 100% [ISO 20022](https://www.iso20022.org) compliant, and supports the complete initializations process ( INI, HIA, HPB orders ) and HTML letter generation.
|
The client is aimed to be 100% [ISO 20022](https://www.iso20022.org) compliant, and supports the complete initializations process (INI, HIA, HPB orders) and HTML letter generation.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
For examples on how to use this library, take a look at the [examples](https://github.com/node-ebics/node-ebics-client/tree/master/examples).
|
||||||
|
|
||||||
|
### A note on recent Node.js versions
|
||||||
|
|
||||||
|
The latest Node.js versions don't support `RSA_PKCS1_PADDING` for private decryption for security reasons, throwing an error like _TypeError: RSA_PKCS1_PADDING is no longer supported for private decryption, this can be reverted with --security-revert=CVE-2023-46809_.
|
||||||
|
|
||||||
|
EBICS requires this mode, so in order for this library to work, add the following parameter when starting Node.js: `--security-revert=CVE-2023-46809`
|
||||||
|
|
||||||
|
### Initialization
|
||||||
|
|
||||||
|
1. Create a configuration (see [example configs](https://github.com/node-ebics/node-ebics-client/tree/master/examples/config)) with the EBICS credentials you received from your bank and name it in this schema: `config.<environment>.<bank>[.<entity>].json` (the entity is optional).
|
||||||
|
|
||||||
|
- The fields `url`, `partnerId`, `userId`, `hostId` are provided by your bank.
|
||||||
|
- The `passphrase` is used to encrypt the keys file, which will be stored at the `storageLocation`.
|
||||||
|
- The `bankName` and `bankShortName` are used internally for creating files and identifying the bank to you.
|
||||||
|
- The `languageCode` is used when creating the Initialization Letter and can be either `de`, `en`, or `fr`.
|
||||||
|
- You can chose any environment, bank and, optionally, entity name. Entities are useful if you have multiple EBICS users for the same bank account.
|
||||||
|
|
||||||
|
2. Run `node examples/initialize.js <environment> <bank> [entity]` to generate your key pair and perform the INI and HIA orders (ie. send the public keys to your bank)
|
||||||
|
The generated keys are stored in the file specified in your config and encrypted with the specified passphrase.
|
||||||
|
3. Run `node examples/bankLetter.js <environment> <bank> [entity]` to generate the Initialization Letter
|
||||||
|
4. Print the letter, sign it and send it to your bank. Wait for them to activate your EBICS account.
|
||||||
|
5. Download the bank keys by running `node examples/save-bank-keys.js <environment> <bank> [entity]`
|
||||||
|
|
||||||
|
If all these steps were executed successfully, you can now do all things EBICS, like fetching bank statements by running `node examples/send-sta-order.js <environment> <bank> [entity]`, or actually use this library in your custom banking applications.
|
||||||
|
|
||||||
## Supported Banks
|
## Supported Banks
|
||||||
|
|
||||||
The client is currently tested and verified to work with the following banks:
|
The client is currently tested and verified to work with the following banks:
|
||||||
|
|
||||||
* [Credit Suisse (Schweiz) AG](https://www.credit-suisse.com/ch/en.html)
|
- [Credit Suisse (Schweiz) AG](https://www.credit-suisse.com/ch/en.html)
|
||||||
* [Zürcher Kantonalbank](https://www.zkb.ch/en/lg/ew.html)
|
- [Zürcher Kantonalbank](https://www.zkb.ch/en/lg/ew.html)
|
||||||
* [Raiffeisen Schweiz](https://www.raiffeisen.ch/rch/de.html)
|
- [Raiffeisen Schweiz](https://www.raiffeisen.ch/rch/de.html)
|
||||||
* [BW Bank](https://www.bw-bank.de/de/home.html)
|
- [BW Bank](https://www.bw-bank.de/de/home.html)
|
||||||
* [Bank GPB International S.A.](https://gazprombank.lu/e-banking)
|
- [Bank GPB International S.A.](https://gazprombank.lu/e-banking)
|
||||||
* [Bank GPB AO](https://gazprombank.ru/)
|
- [Bank GPB AO](https://gazprombank.ru/)
|
||||||
* [J.P. Morgan](https://www.jpmorgan.com/)
|
- [J.P. Morgan](https://www.jpmorgan.com/)
|
||||||
|
|
||||||
|
|
||||||
## Inspiration
|
## Inspiration
|
||||||
|
|
||||||
The basic concept of this library was inspired by the [EPICS](https://github.com/railslove/epics) library from the Railslove Team.
|
The basic concept of this library was inspired by the [EPICS](https://github.com/railslove/epics) library from the Railslove Team.
|
||||||
|
|
||||||
|
|
||||||
## Copyright
|
## Copyright
|
||||||
|
|
||||||
Copyright: Dimitar Nanov, 2019-2022.
|
Copyright: Dimitar Nanov, 2019-2022.
|
||||||
Licensed under the [MIT](LICENSE) license.
|
Licensed under the [MIT](LICENSE) license.
|
||||||
|
|
||||||
|
@@ -10,7 +10,7 @@ const os = require('os');
|
|||||||
const config = require('./loadConfig')();
|
const config = require('./loadConfig')();
|
||||||
const client = require('./getClient')(config);
|
const client = require('./getClient')(config);
|
||||||
const bankName = client.bankName;
|
const bankName = client.bankName;
|
||||||
const template = fs.readFileSync("../templates/ini_"+client.languageCode+".hbs", { encoding: 'utf8'});
|
const template = fs.readFileSync("./templates/ini_"+client.languageCode+".hbs", { encoding: 'utf8'});
|
||||||
const bankLetterFile = path.join("./", "bankLetter_"+client.bankShortName+"_"+client.languageCode+".html");
|
const bankLetterFile = path.join("./", "bankLetter_"+client.bankShortName+"_"+client.languageCode+".html");
|
||||||
|
|
||||||
const letter = new ebics.BankLetter({ client, bankName, template });
|
const letter = new ebics.BankLetter({ client, bankName, template });
|
||||||
|
@@ -10,6 +10,7 @@ module.exports = ({
|
|||||||
userId,
|
userId,
|
||||||
hostId,
|
hostId,
|
||||||
passphrase,
|
passphrase,
|
||||||
|
iv,
|
||||||
keyStoragePath,
|
keyStoragePath,
|
||||||
} = loadConfig()) => new Client({
|
} = loadConfig()) => new Client({
|
||||||
url,
|
url,
|
||||||
@@ -17,5 +18,6 @@ module.exports = ({
|
|||||||
userId,
|
userId,
|
||||||
hostId,
|
hostId,
|
||||||
passphrase,
|
passphrase,
|
||||||
|
iv,
|
||||||
keyStorage: fsKeysStorage(keyStoragePath),
|
keyStorage: fsKeysStorage(keyStoragePath),
|
||||||
});
|
});
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const $request = require('request');
|
const rock = require('rock-req');
|
||||||
|
|
||||||
const constants = require('./consts');
|
const constants = require('./consts');
|
||||||
const Keys = require('./keymanagers/Keys');
|
const Keys = require('./keymanagers/Keys');
|
||||||
@@ -43,7 +43,8 @@ const stringifyKeys = (keys) => {
|
|||||||
* @property {string} partnerId - PARTNERID provided by the bank
|
* @property {string} partnerId - PARTNERID provided by the bank
|
||||||
* @property {string} hostId - HOSTID provided by the bank
|
* @property {string} hostId - HOSTID provided by the bank
|
||||||
* @property {string} userId - USERID provided by the bank
|
* @property {string} userId - USERID provided by the bank
|
||||||
* @property {string} passphrase - passphrase for keys encryption
|
* @property {string|Buffer} passphrase - passphrase or key for keys encryption
|
||||||
|
* @property {string|Buffer} iv - Initialization Vector for keys encryption
|
||||||
* @property {KeyStorage} keyStorage - keyStorage implementation
|
* @property {KeyStorage} keyStorage - keyStorage implementation
|
||||||
* @property {object} [tracesStorage] - traces (logs) storage implementation
|
* @property {object} [tracesStorage] - traces (logs) storage implementation
|
||||||
* @property {string} bankName - Full name of the bank to be used in the bank INI letters.
|
* @property {string} bankName - Full name of the bank to be used in the bank INI letters.
|
||||||
@@ -51,8 +52,6 @@ const stringifyKeys = (keys) => {
|
|||||||
* @property {string} languageCode - Language code to be used in the bank INI letters ("de", "en" and "fr" are currently supported).
|
* @property {string} languageCode - Language code to be used in the bank INI letters ("de", "en" and "fr" are currently supported).
|
||||||
* @property {string} storageLocation - Location where to store the files that are downloaded. This can be a network share for example.
|
* @property {string} storageLocation - Location where to store the files that are downloaded. This can be a network share for example.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
module.exports = class Client {
|
module.exports = class Client {
|
||||||
/**
|
/**
|
||||||
*Creates an instance of Client.
|
*Creates an instance of Client.
|
||||||
@@ -64,6 +63,7 @@ module.exports = class Client {
|
|||||||
userId,
|
userId,
|
||||||
hostId,
|
hostId,
|
||||||
passphrase,
|
passphrase,
|
||||||
|
iv,
|
||||||
keyStorage,
|
keyStorage,
|
||||||
tracesStorage,
|
tracesStorage,
|
||||||
bankName,
|
bankName,
|
||||||
@@ -71,18 +71,17 @@ module.exports = class Client {
|
|||||||
languageCode,
|
languageCode,
|
||||||
storageLocation,
|
storageLocation,
|
||||||
}) {
|
}) {
|
||||||
if (!url)
|
if (!url) throw new Error('EBICS URL is required');
|
||||||
throw new Error('EBICS URL is required');
|
if (!partnerId) throw new Error('partnerId is required');
|
||||||
if (!partnerId)
|
if (!userId) throw new Error('userId is required');
|
||||||
throw new Error('partnerId is required');
|
if (!hostId) throw new Error('hostId is required');
|
||||||
if (!userId)
|
if (!passphrase) throw new Error('passphrase is required');
|
||||||
throw new Error('userId is required');
|
|
||||||
if (!hostId)
|
|
||||||
throw new Error('hostId is required');
|
|
||||||
if (!passphrase)
|
|
||||||
throw new Error('passphrase is required');
|
|
||||||
|
|
||||||
if (!keyStorage || typeof keyStorage.read !== 'function' || typeof keyStorage.write !== 'function')
|
if (
|
||||||
|
!keyStorage
|
||||||
|
|| typeof keyStorage.read !== 'function'
|
||||||
|
|| typeof keyStorage.write !== 'function'
|
||||||
|
)
|
||||||
throw new Error('keyStorage implementation missing or wrong');
|
throw new Error('keyStorage implementation missing or wrong');
|
||||||
|
|
||||||
this.url = url;
|
this.url = url;
|
||||||
@@ -90,7 +89,7 @@ module.exports = class Client {
|
|||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
this.hostId = hostId;
|
this.hostId = hostId;
|
||||||
this.keyStorage = keyStorage;
|
this.keyStorage = keyStorage;
|
||||||
this.keyEncryptor = defaultKeyEncryptor({ passphrase });
|
this.keyEncryptor = defaultKeyEncryptor({ passphrase, iv });
|
||||||
this.tracesStorage = tracesStorage || null;
|
this.tracesStorage = tracesStorage || null;
|
||||||
this.bankName = bankName || 'Dummy Bank Full Name';
|
this.bankName = bankName || 'Dummy Bank Full Name';
|
||||||
this.bankShortName = bankShortName || 'BANKSHORTCODE';
|
this.bankShortName = bankShortName || 'BANKSHORTCODE';
|
||||||
@@ -99,17 +98,25 @@ module.exports = class Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async send(order) {
|
async send(order) {
|
||||||
const isInObject = ('operation' in order);
|
const isInObject = 'operation' in order;
|
||||||
|
|
||||||
if (!isInObject) throw new Error('Operation for the order needed');
|
if (!isInObject) throw new Error('Operation for the order needed');
|
||||||
|
|
||||||
if (order.operation.toUpperCase() === constants.orderOperations.ini) return this.initialization(order);
|
if (order.operation.toUpperCase() === constants.orderOperations.ini)
|
||||||
|
return this.initialization(order);
|
||||||
|
|
||||||
const keys = await this.keys();
|
const keys = await this.keys();
|
||||||
if (keys === null) throw new Error('No keys provided. Can not send the order or any other order for that matter.');
|
if (keys === null)
|
||||||
|
throw new Error(
|
||||||
|
'No keys provided. Can not send the order or any other order for that matter.',
|
||||||
|
);
|
||||||
|
|
||||||
if (order.operation.toUpperCase() === constants.orderOperations.upload) return this.upload(order);
|
if (order.operation.toUpperCase() === constants.orderOperations.upload)
|
||||||
if (order.operation.toUpperCase() === constants.orderOperations.download) return this.download(order);
|
return this.upload(order);
|
||||||
|
if (
|
||||||
|
order.operation.toUpperCase() === constants.orderOperations.download
|
||||||
|
)
|
||||||
|
return this.download(order);
|
||||||
|
|
||||||
throw new Error('Wrong order operation provided');
|
throw new Error('Wrong order operation provided');
|
||||||
}
|
}
|
||||||
@@ -118,8 +125,7 @@ module.exports = class Client {
|
|||||||
const keys = await this.keys();
|
const keys = await this.keys();
|
||||||
if (keys === null) await this._generateKeys();
|
if (keys === null) await this._generateKeys();
|
||||||
|
|
||||||
if (this.tracesStorage)
|
if (this.tracesStorage) this.tracesStorage.new().ofType('ORDER.INI');
|
||||||
this.tracesStorage.new().ofType('ORDER.INI');
|
|
||||||
const res = await this.ebicsRequest(order);
|
const res = await this.ebicsRequest(order);
|
||||||
const xml = res.orderData();
|
const xml = res.orderData();
|
||||||
|
|
||||||
@@ -132,7 +138,9 @@ module.exports = class Client {
|
|||||||
|
|
||||||
technicalCode: returnedTechnicalCode,
|
technicalCode: returnedTechnicalCode,
|
||||||
technicalCodeSymbol: res.technicalSymbol(),
|
technicalCodeSymbol: res.technicalSymbol(),
|
||||||
technicalCodeShortText: res.technicalShortText(returnedTechnicalCode),
|
technicalCodeShortText: res.technicalShortText(
|
||||||
|
returnedTechnicalCode,
|
||||||
|
),
|
||||||
technicalCodeMeaning: res.technicalMeaning(returnedTechnicalCode),
|
technicalCodeMeaning: res.technicalMeaning(returnedTechnicalCode),
|
||||||
|
|
||||||
businessCode: returnedBusinessCode,
|
businessCode: returnedBusinessCode,
|
||||||
@@ -167,7 +175,9 @@ module.exports = class Client {
|
|||||||
|
|
||||||
technicalCode: returnedTechnicalCode,
|
technicalCode: returnedTechnicalCode,
|
||||||
technicalCodeSymbol: res.technicalSymbol(),
|
technicalCodeSymbol: res.technicalSymbol(),
|
||||||
technicalCodeShortText: res.technicalShortText(returnedTechnicalCode),
|
technicalCodeShortText: res.technicalShortText(
|
||||||
|
returnedTechnicalCode,
|
||||||
|
),
|
||||||
technicalCodeMeaning: res.technicalMeaning(returnedTechnicalCode),
|
technicalCodeMeaning: res.technicalMeaning(returnedTechnicalCode),
|
||||||
|
|
||||||
businessCode: returnedBusinessCode,
|
businessCode: returnedBusinessCode,
|
||||||
@@ -178,8 +188,7 @@ module.exports = class Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async upload(order) {
|
async upload(order) {
|
||||||
if (this.tracesStorage)
|
if (this.tracesStorage) this.tracesStorage.new().ofType('ORDER.UPLOAD');
|
||||||
this.tracesStorage.new().ofType('ORDER.UPLOAD');
|
|
||||||
let res = await this.ebicsRequest(order);
|
let res = await this.ebicsRequest(order);
|
||||||
const transactionId = res.transactionId();
|
const transactionId = res.transactionId();
|
||||||
const orderId = res.orderId();
|
const orderId = res.orderId();
|
||||||
@@ -197,22 +206,33 @@ module.exports = class Client {
|
|||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
const { version } = order;
|
const { version } = order;
|
||||||
const keys = await this.keys();
|
const keys = await this.keys();
|
||||||
const r = signer.version(version).sign((await serializer.use(order, this)).toXML(), keys.x());
|
const r = signer
|
||||||
|
.version(version)
|
||||||
|
.sign((await serializer.use(order, this)).toXML(), keys.x());
|
||||||
|
|
||||||
if (this.tracesStorage)
|
if (this.tracesStorage)
|
||||||
this.tracesStorage.label(`REQUEST.${order.orderDetails.OrderType}`).data(r).persist();
|
this.tracesStorage
|
||||||
|
.label(`REQUEST.${order.orderDetails.OrderType}`)
|
||||||
|
.data(r)
|
||||||
|
.persist();
|
||||||
|
|
||||||
$request.post({
|
rock({
|
||||||
|
method: 'POST',
|
||||||
url: this.url,
|
url: this.url,
|
||||||
body: r,
|
body: r,
|
||||||
headers: { 'content-type': 'text/xml;charset=UTF-8' },
|
headers: { 'content-type': 'text/xml;charset=UTF-8' },
|
||||||
}, (err, res, data) => {
|
},
|
||||||
|
(err, res, data) => {
|
||||||
if (err) reject(err);
|
if (err) reject(err);
|
||||||
|
|
||||||
const ebicsResponse = response.version(version)(data, keys);
|
const ebicsResponse = response.version(version)(data.toString('utf-8'), keys);
|
||||||
|
|
||||||
if (this.tracesStorage)
|
if (this.tracesStorage)
|
||||||
this.tracesStorage.label(`RESPONSE.${order.orderDetails.OrderType}`).connect().data(ebicsResponse.toXML()).persist();
|
this.tracesStorage
|
||||||
|
.label(`RESPONSE.${order.orderDetails.OrderType}`)
|
||||||
|
.connect()
|
||||||
|
.data(ebicsResponse.toXML())
|
||||||
|
.persist();
|
||||||
|
|
||||||
resolve(ebicsResponse);
|
resolve(ebicsResponse);
|
||||||
});
|
});
|
||||||
@@ -222,13 +242,14 @@ module.exports = class Client {
|
|||||||
async signOrder(order) {
|
async signOrder(order) {
|
||||||
const { version } = order;
|
const { version } = order;
|
||||||
const keys = await this.keys();
|
const keys = await this.keys();
|
||||||
return signer.version(version).sign((await serializer.use(order, this)).toXML(), keys.x());
|
return signer
|
||||||
|
.version(version)
|
||||||
|
.sign((await serializer.use(order, this)).toXML(), keys.x());
|
||||||
}
|
}
|
||||||
|
|
||||||
async keys() {
|
async keys() {
|
||||||
try {
|
try {
|
||||||
const keysString = await this._readKeys();
|
const keysString = await this._readKeys();
|
||||||
|
|
||||||
return new Keys(JSON.parse(this.keyEncryptor.decrypt(keysString)));
|
return new Keys(JSON.parse(this.keyEncryptor.decrypt(keysString)));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return null;
|
return null;
|
||||||
@@ -253,6 +274,8 @@ module.exports = class Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_writeKeys(keysObject) {
|
_writeKeys(keysObject) {
|
||||||
return this.keyStorage.write(this.keyEncryptor.encrypt(stringifyKeys(keysObject.keys)));
|
return this.keyStorage.write(
|
||||||
|
this.keyEncryptor.encrypt(stringifyKeys(keysObject.keys)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
const NodeRSA = require('node-rsa');
|
||||||
|
|
||||||
const BigNumber = require('./BigNumber.js');
|
const BigNumber = require('./BigNumber.js');
|
||||||
const mgf1 = require('./MGF1');
|
const mgf1 = require('./MGF1');
|
||||||
@@ -54,10 +55,14 @@ module.exports = class Crypto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static privateDecrypt(key, data) {
|
static privateDecrypt(key, data) {
|
||||||
return crypto.privateDecrypt({
|
const keyRSA = new NodeRSA(
|
||||||
key: key.toPem(),
|
key.toPem(),
|
||||||
padding: crypto.constants.RSA_PKCS1_PADDING,
|
'pkcs1-private-pem', {
|
||||||
}, data);
|
encryptionScheme: 'pkcs1',
|
||||||
|
environment: 'browser', // would use the crypto module by default, which blocks pkcs1
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return keyRSA.decrypt(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static privateSign(key, data, outputEncoding = 'base64') {
|
static privateSign(key, data, outputEncoding = 'base64') {
|
||||||
|
66
lib/crypto/encryptDecrypt.js
Normal file
66
lib/crypto/encryptDecrypt.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
const createKeyAndIv = (passphrase) => {
|
||||||
|
// this generates a 256-bit key and a 128-bit iv for aes-256-cbc
|
||||||
|
// just like nodejs's deprecated/removed crypto.createCipher would
|
||||||
|
const a = crypto.createHash('md5').update(passphrase).digest();
|
||||||
|
const b = crypto
|
||||||
|
.createHash('md5')
|
||||||
|
.update(Buffer.concat([a, Buffer.from(passphrase)]))
|
||||||
|
.digest();
|
||||||
|
const c = crypto
|
||||||
|
.createHash('md5')
|
||||||
|
.update(Buffer.concat([b, Buffer.from(passphrase)]))
|
||||||
|
.digest();
|
||||||
|
const bytes = Buffer.concat([a, b, c]);
|
||||||
|
const key = bytes.subarray(0, 32);
|
||||||
|
const iv = bytes.subarray(32, 48);
|
||||||
|
return { key, iv };
|
||||||
|
};
|
||||||
|
|
||||||
|
const encrypt = (data, algorithm, passphrase, iv) => {
|
||||||
|
let cipher;
|
||||||
|
if (iv) {
|
||||||
|
cipher = crypto.createCipheriv(algorithm, passphrase, iv);
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
'[Deprecation notice] No IV provided, falling back to legacy key derivation.\n'
|
||||||
|
+ 'This will be removed in a future major release. You should encrypt your keys with a proper key and IV.',
|
||||||
|
);
|
||||||
|
if (crypto.createCipher) {
|
||||||
|
// nodejs < 22
|
||||||
|
cipher = crypto.createCipher(algorithm, passphrase);
|
||||||
|
} else {
|
||||||
|
const { key, iv: generatedIv } = createKeyAndIv(passphrase);
|
||||||
|
cipher = crypto.createCipheriv(algorithm, key, generatedIv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const encrypted = cipher.update(data, 'utf8', 'hex') + cipher.final('hex');
|
||||||
|
return Buffer.from(encrypted).toString('base64');
|
||||||
|
};
|
||||||
|
|
||||||
|
const decrypt = (data, algorithm, passphrase, iv) => {
|
||||||
|
data = Buffer.from(data, 'base64').toString();
|
||||||
|
let decipher;
|
||||||
|
if (iv) {
|
||||||
|
decipher = crypto.createDecipheriv(algorithm, passphrase, iv);
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
'[Deprecation notice] No IV provided, falling back to legacy key derivation.\n'
|
||||||
|
+ 'This will be removed in a future major release. You should re-encrypt your keys with a proper key and IV.',
|
||||||
|
);
|
||||||
|
if (crypto.createDecipher) {
|
||||||
|
// nodejs < 22
|
||||||
|
decipher = crypto.createDecipher(algorithm, passphrase);
|
||||||
|
} else {
|
||||||
|
const { key, iv: generatedIv } = createKeyAndIv(passphrase);
|
||||||
|
decipher = crypto.createDecipheriv(algorithm, key, generatedIv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const decrypted = decipher.update(data, 'hex', 'utf8') + decipher.final('utf8');
|
||||||
|
return decrypted;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { encrypt, decrypt };
|
@@ -1,24 +1,8 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const crypto = require('crypto');
|
const { encrypt, decrypt } = require('../crypto/encryptDecrypt');
|
||||||
|
|
||||||
const Keys = require('./Keys');
|
const Keys = require('./Keys');
|
||||||
|
|
||||||
const encrypt = (data, algorithm, passphrase) => {
|
|
||||||
const cipher = crypto.createCipher(algorithm, passphrase);
|
|
||||||
const encrypted = cipher.update(data, 'utf8', 'hex') + cipher.final('hex');
|
|
||||||
|
|
||||||
return Buffer.from(encrypted).toString('base64');
|
|
||||||
};
|
|
||||||
const decrypt = (data, algorithm, passphrase) => {
|
|
||||||
data = (Buffer.from(data, 'base64')).toString();
|
|
||||||
|
|
||||||
const decipher = crypto.createDecipher(algorithm, passphrase);
|
|
||||||
const decrypted = decipher.update(data, 'hex', 'utf8') + decipher.final('utf8');
|
|
||||||
|
|
||||||
return decrypted;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = (keysStorage, passphrase, algorithm = 'aes-256-cbc') => {
|
module.exports = (keysStorage, passphrase, algorithm = 'aes-256-cbc') => {
|
||||||
const storage = keysStorage;
|
const storage = keysStorage;
|
||||||
const pass = passphrase;
|
const pass = passphrase;
|
||||||
|
@@ -1,24 +1,9 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const crypto = require('crypto');
|
const { encrypt, decrypt } = require('../crypto/encryptDecrypt');
|
||||||
|
|
||||||
const encrypt = (data, algorithm, passphrase) => {
|
|
||||||
const cipher = crypto.createCipher(algorithm, passphrase);
|
|
||||||
const encrypted = cipher.update(data, 'utf8', 'hex') + cipher.final('hex');
|
|
||||||
return Buffer.from(encrypted).toString('base64');
|
|
||||||
};
|
|
||||||
const decrypt = (data, algorithm, passphrase) => {
|
|
||||||
data = (Buffer.from(data, 'base64')).toString();
|
|
||||||
const decipher = crypto.createDecipher(algorithm, passphrase);
|
|
||||||
const decrypted = decipher.update(data, 'hex', 'utf8') + decipher.final('utf8');
|
|
||||||
|
|
||||||
return decrypted;
|
module.exports = ({ passphrase, iv, algorithm = 'aes-256-cbc' }) => ({
|
||||||
};
|
encrypt: data => encrypt(data, algorithm, passphrase, iv),
|
||||||
|
|
||||||
module.exports = ({
|
|
||||||
passphrase,
|
|
||||||
algorithm = 'aes-256-cbc',
|
|
||||||
}) => ({
|
|
||||||
encrypt: data => encrypt(data, algorithm, passphrase),
|
|
||||||
decrypt: data => decrypt(data, algorithm, passphrase),
|
decrypt: data => decrypt(data, algorithm, passphrase),
|
||||||
});
|
});
|
||||||
|
@@ -5,7 +5,7 @@ const crypto = require('crypto');
|
|||||||
|
|
||||||
const Crypto = require('../../crypto/Crypto');
|
const Crypto = require('../../crypto/Crypto');
|
||||||
|
|
||||||
const { DOMParser, XMLSerializer } = require('xmldom');
|
const { DOMParser, XMLSerializer } = require('@xmldom/xmldom');
|
||||||
const xpath = require('xpath');
|
const xpath = require('xpath');
|
||||||
const errors = require('./errors');
|
const errors = require('./errors');
|
||||||
|
|
||||||
@@ -25,45 +25,72 @@ module.exports = (xml, keys) => ({
|
|||||||
|
|
||||||
isSegmented() {
|
isSegmented() {
|
||||||
const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
|
const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
|
||||||
const node = select('//xmlns:header/xmlns:mutable/xmlns:SegmentNumber', this.doc);
|
const node = select(
|
||||||
|
'//xmlns:header/xmlns:mutable/xmlns:SegmentNumber',
|
||||||
|
this.doc,
|
||||||
|
);
|
||||||
|
|
||||||
return !!node.length;
|
return !!node.length;
|
||||||
},
|
},
|
||||||
|
|
||||||
isLastSegment() {
|
isLastSegment() {
|
||||||
const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
|
const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
|
||||||
const node = select("//xmlns:header/xmlns:mutable/*[@lastSegment='true']", this.doc);
|
const node = select(
|
||||||
|
"//xmlns:header/xmlns:mutable/*[@lastSegment='true']",
|
||||||
|
this.doc,
|
||||||
|
);
|
||||||
|
|
||||||
return !!node.length;
|
return !!node.length;
|
||||||
},
|
},
|
||||||
|
|
||||||
orderData() {
|
orderData() {
|
||||||
const orderDataNode = this.doc.getElementsByTagNameNS('urn:org:ebics:H004', 'OrderData');
|
const orderDataNode = this.doc.getElementsByTagNameNS(
|
||||||
|
'urn:org:ebics:H004',
|
||||||
|
'OrderData',
|
||||||
|
);
|
||||||
|
|
||||||
if (!orderDataNode.length) return {};
|
if (!orderDataNode.length) return {};
|
||||||
|
|
||||||
const orderData = orderDataNode[0].textContent;
|
const orderData = orderDataNode[0].textContent;
|
||||||
const decipher = crypto.createDecipheriv('aes-128-cbc', this.transactionKey(), DEFAULT_IV).setAutoPadding(false);
|
const decipher = crypto
|
||||||
const data = Buffer.from(decipher.update(orderData, 'base64', 'binary') + decipher.final('binary'), 'binary');
|
.createDecipheriv('aes-128-cbc', this.transactionKey(), DEFAULT_IV)
|
||||||
|
.setAutoPadding(false);
|
||||||
|
const data = Buffer.from(
|
||||||
|
decipher.update(orderData, 'base64', 'binary')
|
||||||
|
+ decipher.final('binary'),
|
||||||
|
'binary',
|
||||||
|
);
|
||||||
|
|
||||||
return zlib.inflateSync(data);
|
return zlib.inflateSync(data);
|
||||||
},
|
},
|
||||||
|
|
||||||
transactionKey() {
|
transactionKey() {
|
||||||
const keyNodeText = this.doc.getElementsByTagNameNS('urn:org:ebics:H004', 'TransactionKey')[0].textContent;
|
const keyNodeText = this.doc.getElementsByTagNameNS(
|
||||||
return Crypto.privateDecrypt(this.keys.e(), Buffer.from(keyNodeText, 'base64'));
|
'urn:org:ebics:H004',
|
||||||
|
'TransactionKey',
|
||||||
|
)[0].textContent;
|
||||||
|
return Crypto.privateDecrypt(
|
||||||
|
this.keys.e(),
|
||||||
|
Buffer.from(keyNodeText, 'base64'),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
transactionId() {
|
transactionId() {
|
||||||
const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
|
const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
|
||||||
const node = select('//xmlns:header/xmlns:static/xmlns:TransactionID', this.doc);
|
const node = select(
|
||||||
|
'//xmlns:header/xmlns:static/xmlns:TransactionID',
|
||||||
|
this.doc,
|
||||||
|
);
|
||||||
|
|
||||||
return node.length ? node[0].textContent : '';
|
return node.length ? node[0].textContent : '';
|
||||||
},
|
},
|
||||||
|
|
||||||
orderId() {
|
orderId() {
|
||||||
const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
|
const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
|
||||||
const node = select('.//xmlns:header/xmlns:mutable/xmlns:OrderID', this.doc);
|
const node = select(
|
||||||
|
'.//xmlns:header/xmlns:mutable/xmlns:OrderID',
|
||||||
|
this.doc,
|
||||||
|
);
|
||||||
|
|
||||||
return node.length ? node[0].textContent : '';
|
return node.length ? node[0].textContent : '';
|
||||||
},
|
},
|
||||||
@@ -89,14 +116,20 @@ module.exports = (xml, keys) => ({
|
|||||||
|
|
||||||
technicalCode() {
|
technicalCode() {
|
||||||
const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
|
const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
|
||||||
const node = select('//xmlns:header/xmlns:mutable/xmlns:ReturnCode', this.doc);
|
const node = select(
|
||||||
|
'//xmlns:header/xmlns:mutable/xmlns:ReturnCode',
|
||||||
|
this.doc,
|
||||||
|
);
|
||||||
|
|
||||||
return node.length ? node[0].textContent : '';
|
return node.length ? node[0].textContent : '';
|
||||||
},
|
},
|
||||||
|
|
||||||
technicalSymbol() {
|
technicalSymbol() {
|
||||||
const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
|
const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
|
||||||
const node = select('//xmlns:header/xmlns:mutable/xmlns:ReportText', this.doc);
|
const node = select(
|
||||||
|
'//xmlns:header/xmlns:mutable/xmlns:ReportText',
|
||||||
|
this.doc,
|
||||||
|
);
|
||||||
|
|
||||||
return node.length ? node[0].textContent : '';
|
return node.length ? node[0].textContent : '';
|
||||||
},
|
},
|
||||||
@@ -122,8 +155,14 @@ module.exports = (xml, keys) => ({
|
|||||||
|
|
||||||
for (let i = 0; i < keyNodes.length; i++) {
|
for (let i = 0; i < keyNodes.length; i++) {
|
||||||
const type = lastChild(keyNodes[i].parentNode).textContent;
|
const type = lastChild(keyNodes[i].parentNode).textContent;
|
||||||
const modulus = xpath.select(".//*[local-name(.)='Modulus']", keyNodes[i])[0].textContent;
|
const modulus = xpath.select(
|
||||||
const exponent = xpath.select(".//*[local-name(.)='Exponent']", keyNodes[i])[0].textContent;
|
".//*[local-name(.)='Modulus']",
|
||||||
|
keyNodes[i],
|
||||||
|
)[0].textContent;
|
||||||
|
const exponent = xpath.select(
|
||||||
|
".//*[local-name(.)='Exponent']",
|
||||||
|
keyNodes[i],
|
||||||
|
)[0].textContent;
|
||||||
|
|
||||||
const mod = Buffer.from(modulus, 'base64');
|
const mod = Buffer.from(modulus, 'base64');
|
||||||
const exp = Buffer.from(exponent, 'base64');
|
const exp = Buffer.from(exponent, 'base64');
|
||||||
|
@@ -1,26 +1,32 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// const crypto = require('crypto');
|
// const crypto = require('crypto');
|
||||||
const Crypto = require('../../crypto/Crypto');
|
const Crypto = require('../../crypto/Crypto');
|
||||||
|
|
||||||
const { DOMParser, XMLSerializer } = require('xmldom');
|
const { DOMParser, XMLSerializer } = require('@xmldom/xmldom');
|
||||||
const xpath = require('xpath');
|
const xpath = require('xpath');
|
||||||
const C14n = require('xml-crypto/lib/c14n-canonicalization').C14nCanonicalization;
|
const C14n = require('xml-crypto/lib/c14n-canonicalization').C14nCanonicalization;
|
||||||
|
|
||||||
const digest = (doc) => {
|
const digest = (doc) => {
|
||||||
// get the xml node, where the digested value is supposed to be
|
// get the xml node, where the digested value is supposed to be
|
||||||
const nodeDigestValue = doc.getElementsByTagName('ds:DigestValue')[0];
|
const nodeDigestValue = doc.getElementsByTagName('ds:DigestValue')[0];
|
||||||
|
|
||||||
// canonicalize the node that has authenticate='true' attribute
|
// canonicalize the node that has authenticate='true' attribute
|
||||||
const contentToDigest = xpath.select("//*[@authenticate='true']", doc)
|
const contentToDigest = xpath
|
||||||
.map(x => new C14n().process(x)).join('');
|
.select("//*[@authenticate='true']", doc)
|
||||||
|
.map(x => new C14n().process(x))
|
||||||
|
.join('');
|
||||||
|
|
||||||
// fix the canonicalization
|
// fix the canonicalization
|
||||||
const fixedContent = contentToDigest.replace(/xmlns="urn:org:ebics:H004"/g, 'xmlns="urn:org:ebics:H004" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"');
|
const fixedContent = contentToDigest.replace(
|
||||||
|
/xmlns="urn:org:ebics:H004"/g,
|
||||||
|
'xmlns="urn:org:ebics:H004" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"',
|
||||||
|
);
|
||||||
|
|
||||||
if (nodeDigestValue)
|
if (nodeDigestValue)
|
||||||
nodeDigestValue.textContent = Crypto.digestWithHash(fixedContent).toString('base64').trim();
|
nodeDigestValue.textContent = Crypto.digestWithHash(fixedContent)
|
||||||
|
.toString('base64')
|
||||||
|
.trim();
|
||||||
|
|
||||||
return doc;
|
return doc;
|
||||||
};
|
};
|
||||||
@@ -29,8 +35,15 @@ const sign = (doc, key) => {
|
|||||||
const nodeSignatureValue = doc.getElementsByTagName('ds:SignatureValue')[0];
|
const nodeSignatureValue = doc.getElementsByTagName('ds:SignatureValue')[0];
|
||||||
|
|
||||||
if (nodeSignatureValue) {
|
if (nodeSignatureValue) {
|
||||||
const select = xpath.useNamespaces({ ds: 'http://www.w3.org/2000/09/xmldsig#' });
|
const select = xpath.useNamespaces({
|
||||||
const contentToSign = (new C14n().process(select('//ds:SignedInfo', doc)[0])).replace('xmlns:ds="http://www.w3.org/2000/09/xmldsig#"', 'xmlns="urn:org:ebics:H004" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"');
|
ds: 'http://www.w3.org/2000/09/xmldsig#',
|
||||||
|
});
|
||||||
|
const contentToSign = new C14n()
|
||||||
|
.process(select('//ds:SignedInfo', doc)[0])
|
||||||
|
.replace(
|
||||||
|
'xmlns:ds="http://www.w3.org/2000/09/xmldsig#"',
|
||||||
|
'xmlns="urn:org:ebics:H004" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"',
|
||||||
|
);
|
||||||
|
|
||||||
nodeSignatureValue.textContent = Crypto.privateSign(key, contentToSign); // this.keys.x().key.sign(contentToSign, 'base64');
|
nodeSignatureValue.textContent = Crypto.privateSign(key, contentToSign); // this.keys.x().key.sign(contentToSign, 'base64');
|
||||||
}
|
}
|
||||||
|
7920
package-lock.json
generated
7920
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
42
package.json
42
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ebics-client",
|
"name": "ebics-client",
|
||||||
"version": "0.2.1",
|
"version": "5.0.0",
|
||||||
"description": "Node.js ISO 20022 Compliant EBICS Client",
|
"description": "Node.js ISO 20022 Compliant EBICS Client",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"files": [
|
"files": [
|
||||||
@@ -9,7 +9,10 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"test": "nyc mocha test/**/*.js",
|
"test": "npm run test:node$(node -v | cut -d '.' -f 1 | cut -c 2-)",
|
||||||
|
"test:node22": "nyc node ./node_modules/.bin/mocha test/**/*.js",
|
||||||
|
"test:node20": "nyc node --security-revert=CVE-2023-46809 ./node_modules/.bin/mocha test/**/*.js",
|
||||||
|
"test:node18": "nyc node --security-revert=CVE-2023-46809 ./node_modules/.bin/mocha test/**/*.js",
|
||||||
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||||
"version": "auto-changelog -p -t changelog-template.hbs && git add CHANGELOG.md"
|
"version": "auto-changelog -p -t changelog-template.hbs && git add CHANGELOG.md"
|
||||||
},
|
},
|
||||||
@@ -55,28 +58,33 @@
|
|||||||
{
|
{
|
||||||
"name": "Herrie",
|
"name": "Herrie",
|
||||||
"url": "https://github.com/Herrie82"
|
"url": "https://github.com/Herrie82"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Maik Marschner ",
|
||||||
|
"url": "https://github.com/leMaik"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "GPL-3.0-only",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"handlebars": "^4.7.7",
|
"@xmldom/xmldom": "^0.8.10",
|
||||||
"js2xmlparser": "^4.0.1",
|
"handlebars": "^4.7.8",
|
||||||
"node-forge": "^0.10.0",
|
"js2xmlparser": "^5.0.0",
|
||||||
"request": "^2.88.2",
|
"node-forge": "^1.3.1",
|
||||||
"uuid": "^8.3.2",
|
"node-rsa": "^1.1.1",
|
||||||
"xml-crypto": "^2.1.1",
|
"rock-req": "^5.1.3",
|
||||||
"xmldom": "^0.5.0",
|
"uuid": "^9.0.1",
|
||||||
|
"xml-crypto": "^6.0.0",
|
||||||
"xpath": "0.0.32"
|
"xpath": "0.0.32"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"auto-changelog": "^1.16.2",
|
"auto-changelog": "^2.4.0",
|
||||||
"chai": "^4.3.4",
|
"chai": "^4.3.10",
|
||||||
"coveralls": "^3.1.0",
|
"coveralls": "^3.1.1",
|
||||||
"eslint": "^6.7.2",
|
"eslint": "^6.7.2",
|
||||||
"eslint-config-ecollect-base": "^0.1.2",
|
"eslint-config-ecollect-base": "^0.1.2",
|
||||||
"eslint-plugin-import": "^2.18.2",
|
"eslint-plugin-import": "^2.28.1",
|
||||||
"libxmljs": "^0.19.7",
|
"mocha": "^10.2.0",
|
||||||
"mocha": "^7.1.2",
|
"nyc": "^15.1.0",
|
||||||
"nyc": "^15.1.0"
|
"xmllint-wasm": "^4.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,25 +9,36 @@ const fs = require('fs');
|
|||||||
|
|
||||||
const ebics = require('../../');
|
const ebics = require('../../');
|
||||||
|
|
||||||
const libxml = require('libxmljs');
|
const xmlLintWasm = require('xmllint-wasm');
|
||||||
|
|
||||||
const schemaPath = path.resolve(__dirname, '../xsd/ebics_H004.xsd');
|
const validateXML = (() => {
|
||||||
const schemaDoc = libxml.parseXml(fs.readFileSync(schemaPath, { encoding: 'utf8' }));
|
const xsdDir = path.resolve(__dirname, '../xsd');
|
||||||
|
const schemaPath = path.resolve(xsdDir, 'ebics_H004.xsd');
|
||||||
|
const schemaDoc = fs.readFileSync(schemaPath, { encoding: 'utf8' });
|
||||||
|
const preload = fs
|
||||||
|
.readdirSync(xsdDir)
|
||||||
|
.filter(file => file.endsWith('.xsd') && file !== 'ebics_H004.xsd')
|
||||||
|
.map(file => ({
|
||||||
|
fileName: file,
|
||||||
|
contents: fs.readFileSync(path.join(xsdDir, file), {
|
||||||
|
encoding: 'utf8',
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
const schemaDir = path.dirname(schemaPath);
|
return async (str) => {
|
||||||
const cwd = process.cwd();
|
const results = await xmlLintWasm.validateXML({
|
||||||
|
xml: { fileName: 'ebics.xml', contents: str },
|
||||||
const validateXML = (str) => {
|
schema: [
|
||||||
try {
|
{
|
||||||
process.chdir(schemaDir);
|
fileName: 'ebics_H004.xsd',
|
||||||
const isValid = libxml.parseXmlString(str).validate(schemaDoc);
|
contents: schemaDoc,
|
||||||
process.chdir(cwd);
|
},
|
||||||
return isValid;
|
],
|
||||||
} catch (e) {
|
preload,
|
||||||
process.chdir(cwd);
|
});
|
||||||
return false;
|
return results.valid;
|
||||||
}
|
};
|
||||||
};
|
})();
|
||||||
|
|
||||||
const client = new ebics.Client({
|
const client = new ebics.Client({
|
||||||
url: 'https://iso20022test.credit-suisse.com/ebicsweb/ebicsweb',
|
url: 'https://iso20022test.credit-suisse.com/ebicsweb/ebicsweb',
|
||||||
@@ -35,7 +46,9 @@ const client = new ebics.Client({
|
|||||||
userId: 'CRS04381',
|
userId: 'CRS04381',
|
||||||
hostId: 'CRSISOTB',
|
hostId: 'CRSISOTB',
|
||||||
passphrase: 'test',
|
passphrase: 'test',
|
||||||
keyStorage: ebics.fsKeysStorage(path.resolve(__dirname, '../support/TEST_KEYS.key')),
|
keyStorage: ebics.fsKeysStorage(
|
||||||
|
path.resolve(__dirname, '../support/TEST_KEYS.key'),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { Orders } = ebics;
|
const { Orders } = ebics;
|
||||||
@@ -83,10 +96,8 @@ const fnOrders = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getOrderObject = (name, order) => {
|
const getOrderObject = (name, order) => {
|
||||||
if (typeof order === 'object')
|
if (typeof order === 'object') return order;
|
||||||
return order;
|
if (fnOrders[name]) return fnOrders[name](order);
|
||||||
if (fnOrders[name])
|
|
||||||
return fnOrders[name](order);
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -94,15 +105,14 @@ describe('H004 order generation', () => {
|
|||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const [name, orderDefinition] of Object.entries(Orders)) {
|
for (const [name, orderDefinition] of Object.entries(Orders)) {
|
||||||
const order = getOrderObject(name, orderDefinition);
|
const order = getOrderObject(name, orderDefinition);
|
||||||
if (!order)
|
if (!order) continue;
|
||||||
continue;
|
|
||||||
|
|
||||||
const type = order.orderDetails.OrderType;
|
const type = order.orderDetails.OrderType;
|
||||||
const { operation } = order;
|
const { operation } = order;
|
||||||
|
|
||||||
it(`[${operation}] ${type} order generation`, async () => {
|
it(`[${operation}] ${type} order generation`, async () => {
|
||||||
const signedOrder = await client.signOrder(order);
|
const signedOrder = await client.signOrder(order);
|
||||||
assert.isTrue(validateXML(signedOrder));
|
assert.isTrue(await validateXML(signedOrder));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user