144 Commits

Author SHA1 Message Date
nanov
5e73d3ce5e bump 2019-12-18 16:53:18 +02:00
nanov
c7e739f767 lock 2019-12-18 16:49:26 +02:00
nanov
478571e447 chore: update deps 2019-12-18 16:49:20 +02:00
nanov
f6dfdf4c40 feat: generalize examples 2019-12-18 16:48:05 +02:00
Dimitar Nanov
fcbf4ca165 Merge pull request #28 from Herrie82/herrie/config
examples: Externalize configuration values and allow for multiple env…
2019-12-18 10:37:02 +02:00
nanov
6187902ae5 chore: transfer to new repository 2019-12-16 14:28:35 +02:00
Dimitar Nanov
65658ab131 Merge pull request #29 from Herrie82/herrie/send-XG1
Add XG1 order type and example
2019-12-13 10:36:30 +02:00
Herrie
d38460011c Add XG1 order type and example
XG1 order type is used for ISO XML 20022 CGI (Common Global Implementation) which should be a unified format that works within a country (irrespective of bank).

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
2019-11-29 14:38:46 +01:00
Dimitar Nanov
b9058fac93 Merge pull request #27 from Herrie82/herrie/SupportedBank
README.md: Add Bank GPB International S.A. to supported bank list
2019-11-29 14:14:10 +02:00
Herrie
c3d3decd6a examples: Externalize configuration values and allow for multiple environments
In order to more easily manage testing of various order types as well as production, development etc. environments, externalize this config values.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
2019-11-29 13:12:15 +01:00
Herrie
a5b65f815e README.md: Add Bank GPB International S.A. to supported bank list
I was able to test DKI, INI, HAA, HAC, HEV, HVD, STA, VMK, XG1 with Bank GPB International S.A., so I think we're good to add it to the supported bank list.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
2019-11-29 11:46:53 +01:00
nanov
5f57a072ea chore: changelog 2019-11-26 18:25:29 +02:00
nanov
f95ecc60b8 chore: changelog 2019-11-26 18:24:35 +02:00
nanov
3873ff49d1 bump version 2019-11-26 18:23:52 +02:00
nanov
9725380edd feat: add C53, C52 and DKI orders 2019-11-26 18:22:09 +02:00
nanov
f2a3d6f067 chore: gitignore 2019-11-26 18:21:56 +02:00
Dimitar Nanov
6d882f56fe Merge pull request #26 from Herrie82/herrie/DKI
* Add DKI, C52, C53 order types.
* Some examples for STA, VMK, C52, C53, DKI and HKD order types.
* Rename example for HBT into HTD.
2019-11-26 18:15:32 +02:00
nanov
88ba543784 feat: add ebics 061011 error 2019-11-26 18:13:30 +02:00
Herrie
f8c9cc7ba8 Add C52/C53 order types and examples
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
2019-11-25 16:22:39 +01:00
Herrie
0c08d534ff Add examples for MT940 and MT942
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
2019-11-25 16:22:39 +01:00
Herrie
ad7d55ce7c Add example for HKD order type
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
2019-11-25 16:20:59 +01:00
Herrie
c15e19d22f Rename send-hbt-order.js to send-htd-order.js
To reflect the name of the order type correctly.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
2019-11-25 16:20:59 +01:00
Herrie
d2473bb81d Add DKI order type
To download Exchange rate files and fix syntax error.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
2019-11-25 16:20:59 +01:00
nanov
4e6383e23c chore: add coveralls to travis 2019-11-20 19:46:39 +02:00
nanov
a4a64709f0 chore: update dependencies 2019-11-20 19:40:23 +02:00
nanov
7533a861e5 chore: add coveralls badge 2019-11-20 19:38:57 +02:00
nanov
e56e7f224b test: add coveralls support 2019-11-20 19:37:32 +02:00
nanov
6d73ef77d4 test: add additional tests 2019-11-20 14:42:22 +02:00
nanov
a10a3a8d63 feat: async letter serialization 2019-11-20 14:40:18 +02:00
nanov
67d74cfa00 test: add more tests 2019-11-20 11:12:37 +02:00
nanov
ddd0933c0b chore: add Herrie82 to contributors list 2019-11-19 19:48:05 +02:00
Dimitar Nanov
ea52c71fb2 Merge pull request #24 from Herrie82/herrie/english
feat: Add English template & flexible generation
2019-11-19 19:46:46 +02:00
Dimitar Nanov
ddcd31a5c0 Merge pull request #23 from Herrie82/master
chore: Various fixes for spelling
2019-11-19 19:44:11 +02:00
Herrie
95403cbe6f Bank Letter: Add English template & flexible generation
Allow for both German (de) and English (en) initialization letters.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
2019-11-19 16:57:13 +01:00
Herman van Hazendonk
0b9d93f745 Various fixes for spelling
Various minor spelling mistake fixes.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
2019-11-19 14:48:26 +01:00
nanov
6ecb256483 tests: start Client tests 2019-11-08 11:24:59 +02:00
nanov
d482b1dde1 chore: update readme 2019-11-07 11:53:09 +02:00
nanov
ecf51680c5 chore: add logo 2019-11-07 11:49:22 +02:00
nanov
9a748fa2bc chore: changelog and files settings 2019-11-07 11:42:37 +02:00
nanov
7355096dfd v0.1.2 2019-11-07 11:37:34 +02:00
nanov
25eedd5071 chore: update package.json 2019-11-07 11:36:04 +02:00
Dimitar Nanov
4182e7956a Merge pull request #22 from eCollect/release-it
chore: add changelog configoration
2019-11-07 11:35:34 +02:00
nanov
fee2aa4681 chore: add version script 2019-11-07 11:35:11 +02:00
nanov
1f34dcbfb6 add changelog configoration 2019-11-07 11:20:34 +02:00
nanov
3a64d95eb5 chore: add tags 2019-11-07 10:58:38 +02:00
Dimitar Nanov
926d1bca18 Merge pull request #21 from eCollect/feat/drop-bn.js
Closes #17 and #13
2019-11-07 10:52:01 +02:00
nanov
6d601f4186 chore: fix package json syntax 2019-11-07 10:42:11 +02:00
nanov
2d05bf88f9 chore: add contributors to package json 2019-11-07 10:41:43 +02:00
nanov
10e0d602bd chore: update license expression in package.json 2019-11-07 10:38:01 +02:00
nanov
eea0a49130 feat: drop bn.js 2019-11-07 10:37:31 +02:00
nanov
3e95478b3b feat: add nyc and cleanup .gitignore 2019-11-07 10:35:31 +02:00
nanov
4496cbf560 feat: remove toBuffer from number 2019-11-07 10:30:03 +02:00
nanov
0efc46b014 feat add BigNumber tests 2019-11-07 10:27:20 +02:00
nanov
eafe2f9f55 chore: restructure BigNumber 2019-11-07 10:27:08 +02:00
nanov
24afdeb257 chroe: import consts 2019-11-07 10:26:53 +02:00
nanov
34051f0a9f feat: migrate Crypto to own BigNumber 2019-11-07 08:58:52 +02:00
nanov
14779088f1 chore: lint 2019-11-07 08:57:54 +02:00
nanov
aad0bd97c8 feat: implement all needed BigNumber methods 2019-11-07 08:57:44 +02:00
nanov
429e807994 fix number generarion 2019-11-06 17:27:48 +02:00
nanov
bb8d1cfaa0 wip: migrate MFG1 to BigNumber 2019-11-06 17:16:56 +02:00
nanov
cda36bfcb3 wip: implement BigNumber wrapper 2019-11-06 17:16:15 +02:00
Dimitar Nanov
d21d89fb36 Merge pull request #20 from eCollect/feat/drop-moment
Drop moment dependency
2019-11-06 16:55:15 +02:00
nanov
df9c330411 feat: drop moment dependency 2019-11-06 16:52:31 +02:00
nanov
2a17ff6056 chore: write tests for date range 2019-11-06 16:52:20 +02:00
nanov
33ac6ac60f feat: implement date handling and formatting 2019-11-06 16:51:59 +02:00
nanov
e9f7c11bbb v0.1.1 2019-11-05 06:48:24 +02:00
nanov
9aabe933e9 chore: update license 2019-11-05 06:41:31 +02:00
nanov
dc5f52198b v0.1.0 2019-11-05 06:26:02 +02:00
nanov
bb5e551057 chore: cleanup readme 2019-11-05 06:24:39 +02:00
nanov
0a3a4631df update readme 2019-11-05 06:23:45 +02:00
nanov
3ffa323264 chore: commit test keys for tests to pass 2019-11-05 06:22:16 +02:00
nanov
e56dc082d0 chore: add travis config 2019-11-05 06:13:27 +02:00
nanov
0c01420c1e chore: cleanup 2019-11-05 06:09:07 +02:00
nanov
0964992164 chore: drop node-rsa 2019-11-05 06:00:09 +02:00
Dimitar Nanov
5c419630c1 Merge pull request #16 from eCollect/feat/handle-unsual-exponent
Feat: handle unsual exponent migrate to node-forge
2019-11-05 05:58:50 +02:00
Dimitar Nanov
594f2a2623 Merge pull request #15 from eCollect/chore/tests
Add order generation tests and fix linting
2019-11-05 05:55:33 +02:00
nanov
fa557f5e9b feat: tests 2019-11-05 05:54:32 +02:00
nanov
cf16abc843 chire: jsdoc 2019-11-05 05:54:16 +02:00
nanov
c60fed4d58 chore: lint 2019-11-05 05:54:09 +02:00
nanov
9aa2ab7380 chore: lint 2019-11-05 05:53:20 +02:00
nanov
fe2ad67139 chore: update linter 2019-11-05 05:49:07 +02:00
Vladislav Hristov
104ec67f8c chore: add ; at the end of Key.js 2019-11-01 16:52:36 +02:00
Vladislav Hristov
e8a80932e1 fit: key size is now calculated instead of hardcoded 2019-11-01 16:50:39 +02:00
Vladislav Hristov
ce6cd02e58 fit: BankLetter.js get key size 2019-11-01 16:28:04 +02:00
Vladislav Hristov
836ec0ebf3 fit(tests): use new Key in keys test 2019-11-01 13:49:59 +02:00
Vladislav Hristov
59a281c895 feat: use new Key in Keys manager 2019-11-01 13:49:33 +02:00
Vladislav Hristov
61ada747f3 clean: remove modulus and exponent property from bankKeys() 2019-11-01 13:48:51 +02:00
Vladislav Hristov
f15e09840a add: add Key class to manage a single key 2019-11-01 13:44:29 +02:00
nanov
422821fbbd feat: test all H004 orders 2019-11-01 11:27:36 +02:00
nanov
e40f79cee6 feat: prepare order generation tests 2019-11-01 08:13:53 +02:00
Dimitar Nanov
837fdaa08c Merge pull request #14 from yagop/test/mocha-script
test script run mocha
2019-11-01 05:59:38 +02:00
Yago Perez
361acf89d0 test script run mocha 2019-10-31 21:40:07 +01:00
Vladislav Hristov
4149c01695 wip: migrate to node-forge 2019-10-31 18:57:42 +02:00
Vladislav Hristov
1c1f112ff5 fix: modulus zero byte handling 2019-10-30 10:53:03 +02:00
nanov
1320f6f21d chore: update deps 2019-10-14 14:19:11 +03:00
Vladislav Hristov
0049fffc00 up the version to 0.0.8 2019-10-08 16:10:08 +03:00
Vladislav Hristov
2c9040904a chore: remove console.log statements 2019-10-08 16:09:08 +03:00
Dimitar Nanov
0f6dcf9eb2 Merge pull request #11 from yagop/fix-version
chore: readme maintenance
2019-10-03 09:32:17 +03:00
nanov
ca31edf245 chore: add yagop to contributors 2019-10-03 09:30:49 +03:00
nanov
4464349d0f chore: remove version from readme 2019-10-03 09:29:35 +03:00
Dimitar Nanov
15546df9ea Merge pull request #10 from yagop/examples
Provide examples
2019-10-03 09:20:29 +03:00
Yago Perez
40467a2c5e v0.0.35 -> v0.0.7 2019-10-03 00:20:36 +02:00
Yago Perez
eb9fbf5834 Change HBT order text 2019-10-03 00:10:52 +02:00
Yago Perez
166c61aec4 examples/send-hbt-order.js 2019-10-02 23:58:37 +02:00
Yago Perez
5a63e19aab examples/save-bank-kesy.js 2019-10-02 23:58:35 +02:00
Yago Perez
9e6c318372 keys -> passphrase 2019-10-02 23:58:33 +02:00
Yago Perez
ce6e58b3f3 examples/bankLetter.js 2019-10-02 23:58:29 +02:00
Yago Perez
7dad7c8787 initialize.js 2019-10-02 23:56:51 +02:00
Dimitar Nanov
79f17e1404 bump version 2019-08-02 10:52:48 +03:00
Dimitar Nanov
3ef32c8ce5 Merge pull request #7 from anandsahil/addCAMT53OrderType
* Add Z53 order type
* Create utils container and move dateRange functionality to there
2019-08-01 16:59:25 +03:00
anandsahil
aa761cf7ad move dateRange from constants to utils 2019-08-01 14:14:30 +02:00
anandsahil
5ff3147124 add new order type XZ53 and removed repeated dateRange to consts 2019-08-01 14:00:07 +02:00
anandsahil
01d4634d86 Merge remote-tracking branch 'src/master' 2019-08-01 13:52:51 +02:00
sahil anand
89904afa63 Merge pull request #2 from anandsahil/minorChanges
remove eCollect from constant and minor verbose error handeling
2019-07-25 17:40:29 +02:00
sahil anand
4907524259 Merge pull request #1 from anandsahil/and-XCT-order-type
add iso pain format XCT order type
2019-07-25 17:40:04 +02:00
Dimitar Nanov
c9f52d3bd9 bump 2019-07-24 09:12:06 +03:00
Dimitar Nanov
f5b05ae491 Merge pull request #4 from anandsahil/minorChanges
remove eCollect from constant and minor verbose error handaling
2019-07-24 09:05:14 +03:00
Dimitar Nanov
9f88b048d7 Merge pull request #5 from anandsahil/and-XCT-order-type
add iso pain format XCT order type
2019-07-24 09:04:22 +03:00
anandsahil
d06e92c51c add iso pain format XCT order type 2019-07-22 17:36:38 +02:00
anandsahil
cb2062ae2f remove eCollect from constant and minor verbose error handeling 2019-07-22 17:31:24 +02:00
Dimitar Nanov
07a48e9cc5 0.0.5 2019-06-28 17:12:49 +03:00
Dimitar Nanov
800002701d Merge pull request #3 from vasyas/master
Fix parsing of the bank keys in the HPB response
2019-06-28 17:09:49 +03:00
vasyas
5f0b6cd374 #2 corrected bank keys parsing 2019-06-27 00:13:44 +03:00
vasyas
c571ef181b tc for bank keys parsing error 2019-06-27 00:10:53 +03:00
Dimitar Nanov
7b0015b2b3 0.0.4 2018-09-03 09:35:57 +03:00
Vladislav Hristov
968b582493 Fix BankLetter constructor 2018-08-31 10:24:34 +03:00
Vladislav Hristov
1f857a7f85 Fix serialize method 2018-08-31 10:23:41 +03:00
Vladislav Hristov
e2af5c6ea8 Add traces storage 2018-08-31 08:50:18 +03:00
Vladislav Hristov
548c17a868 Add serialize function to BankLetter class 2018-08-30 13:53:24 +03:00
Vladislav Hristov
5d4e9f8309 Fix orderData method 2018-07-04 16:30:16 +03:00
Vladislav Hristov
044bbba9e3 Add returned codes errors and messages 2018-07-03 12:48:00 +03:00
Vladislav Hristov
ab2853956c Return body error code 2018-07-03 12:03:37 +03:00
Vladislav Hristov
b259ac76d6 Code optimization 2018-06-28 11:34:14 +03:00
Vladislav Hristov
9660242234 Async read and write keys 2018-06-27 17:59:35 +03:00
Vladislav Hristov
9cfed8ec81 Add keys generation and code optimization 2018-06-27 14:21:49 +03:00
Vladislav Hristov
c0ec4b8fb7 add predefined orders 2018-06-20 14:08:31 +03:00
Vladislav Hristov
8e9e0df276 code cleaning 2018-06-20 12:22:16 +03:00
Vladislav Hristov
945499290a client and order optimization 2018-06-20 12:20:03 +03:00
Vladislav Hristov
187636019c code optimization 2018-06-15 09:33:41 +03:00
Vladislav Hristov
d5d80ee1b8 clean up the code 2018-06-12 11:27:56 +03:00
Vladislav Hristov
10111878fa reorder file structure 2018-06-11 15:25:07 +03:00
Vladislav Hristov
ff9a3a16b4 Major changes. Separating responsibilities. Orders builder, serializer. 2018-06-11 11:38:32 +03:00
Vladislav Hristov
90f51544b7 update dependencies 2018-06-04 15:30:33 +03:00
AngelPashov
8492d94054 various modular fixes 2018-06-01 16:16:43 +03:00
122 changed files with 13880 additions and 1355 deletions

View File

@@ -10,6 +10,9 @@ charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.xml]
insert_final_newline = false
[*.json]
indent_size = 2

View File

@@ -1,4 +1,5 @@
node_modules/
examples/
build/
static/
config/

View File

@@ -1,34 +1,4 @@
{
"extends": "airbnb",
"env": {
"node": true
},
"parserOptions": {
"ecmaVersion": 8,
"sourceType": "script",
"ecmaFeatures": {
"modules": false
}
},
"rules": {
"max-len": 0,
"linebreak-style": 0,
"no-plusplus": [
2,
{
"allowForLoopAfterthoughts": true
}
],
"no-continue": 0,
"indent": [2, "tab"],
"no-tabs": 0,
"strict": [2, "safe"],
"curly": [2, "multi", "consistent"],
"import/no-extraneous-dependencies": 0,
"import/no-unresolved": 0,
"no-underscore-dangle": 0,
"no-param-reassign": 0,
"generator-star-spacing": 0,
"jsx-a11y/href-no-hash": "off"
}
"extends": "ecollect-base",
"root": true
}

31
.gitignore vendored
View File

@@ -1,14 +1,23 @@
npm-debug.log
node_modules
# mac shit
.DS_Store
*.local.json5
yarn.lock
/project.sublime-workspace
/public/css/style.css.map
/.idea
# nyc test coverage
.nyc_output
coverage/
# Dependency directories
node_modules/
# Optional npm cache directory
.npm
npm-debug.log
# Optional eslint cache
.eslintcache
# vscode
/.vscode
*.pid
/coverage
package-lock.json
*.key
*.html
# Tests temporary
.test_tmp

8
.travis.yml Normal file
View File

@@ -0,0 +1,8 @@
language: node_js
node_js:
- "8"
- "9"
- "10"
- "11"
- "12"
after_success: npm run coverage

108
CHANGELOG.md Normal file
View File

@@ -0,0 +1,108 @@
### Changelog
#### [v0.1.3](https://github.com/eCollect/node-ebics-client/compare/v0.1.2...v0.1.3)
> 26 November 2019
- * Add DKI, C52, C53 order types. [`#26`](https://github.com/eCollect/node-ebics-client/pull/26)
- Bank Letter: Add English template & flexible generation [`95403cb`](https://github.com/eCollect/node-ebics-client/commit/95403cbe6f90c81a696bb589533d713a4ba04873)
#### [v0.1.2](https://github.com/eCollect/node-ebics-client/compare/v0.1.1...v0.1.2)
> 7 November 2019
- chore: add changelog configoration [`#22`](https://github.com/eCollect/node-ebics-client/pull/22)
- Closes #17 and #13 [`#21`](https://github.com/eCollect/node-ebics-client/pull/21)
- Drop moment dependency [`#20`](https://github.com/eCollect/node-ebics-client/pull/20)
- feat: add nyc and cleanup .gitignore [`3e95478`](https://github.com/eCollect/node-ebics-client/commit/3e95478b3be719c86f32c7df10c42e46b7518669)
- feat: drop bn.js [`eea0a49`](https://github.com/eCollect/node-ebics-client/commit/eea0a49130e30c123b110120c69d7b7c19fd12ba)
- add changelog configoration [`1f34dcb`](https://github.com/eCollect/node-ebics-client/commit/1f34dcbfb6e0febbb93d5356fa36ac57d697a990)
#### [v0.1.1](https://github.com/eCollect/node-ebics-client/compare/v0.1.0...v0.1.1)
> 5 November 2019
- chore: update license [`9aabe93`](https://github.com/eCollect/node-ebics-client/commit/9aabe933e91b506ea38820b952ce8e5e58b4c2ff)
#### [v0.1.0](https://github.com/eCollect/node-ebics-client/compare/v0.0.8...v0.1.0)
> 5 November 2019
- Feat: handle unsual exponent migrate to node-forge [`#16`](https://github.com/eCollect/node-ebics-client/pull/16)
- Add order generation tests and fix linting [`#15`](https://github.com/eCollect/node-ebics-client/pull/15)
- test script run mocha [`#14`](https://github.com/eCollect/node-ebics-client/pull/14)
- feat: prepare order generation tests [`e40f79c`](https://github.com/eCollect/node-ebics-client/commit/e40f79cee68a194272c93f07e763175b213a77a1)
- chore: cleanup [`0c01420`](https://github.com/eCollect/node-ebics-client/commit/0c01420c1e14992a4169098ccd47cd196b899f06)
#### [v0.0.36](https://github.com/eCollect/node-ebics-client/compare/v0.0.35...v0.0.36)
> 1 June 2018
- various modular fixes [`8492d94`](https://github.com/eCollect/node-ebics-client/commit/8492d940542f61b17aa3a2da7de23f6539ffaad5)
#### [v0.0.35](https://github.com/eCollect/node-ebics-client/compare/v0.0.3...v0.0.35)
> 31 May 2018
- update License to GPL v3 [`babcf76`](https://github.com/eCollect/node-ebics-client/commit/babcf76b61af6eb737ab291a301e71bb84621820)
- Add MGF1.js file - mask generation utility class [`7e6e2ff`](https://github.com/eCollect/node-ebics-client/commit/7e6e2ff142688b0c453369fa7137b49e8b89cd81)
- Add sign, _emsaPSS and _modPowe methods in Key.js file [`5ace213`](https://github.com/eCollect/node-ebics-client/commit/5ace2137231af9a3563ab31fa0f70fbdf4b148cb)
#### [v0.0.8](https://github.com/eCollect/node-ebics-client/compare/v0.0.7...v0.0.8)
> 8 October 2019
- chore: readme maintenance [`#11`](https://github.com/eCollect/node-ebics-client/pull/11)
- Provide examples [`#10`](https://github.com/eCollect/node-ebics-client/pull/10)
- initialize.js [`7dad7c8`](https://github.com/eCollect/node-ebics-client/commit/7dad7c878722be94e03808cef3af38d34019c623)
- examples/bankLetter.js [`ce6e58b`](https://github.com/eCollect/node-ebics-client/commit/ce6e58b3f33017967e5b26fe15a2c435012b8af6)
- examples/send-hbt-order.js [`166c61a`](https://github.com/eCollect/node-ebics-client/commit/166c61aec4a247d923de82278271ec02cbef815f)
#### [v0.0.7](https://github.com/eCollect/node-ebics-client/compare/v0.0.6...v0.0.7)
> 2 August 2019
- * Add Z53 order type [`#7`](https://github.com/eCollect/node-ebics-client/pull/7)
- remove eCollect from constant and minor verbose error handeling [`#2`](https://github.com/eCollect/node-ebics-client/pull/2)
- add iso pain format XCT order type [`#1`](https://github.com/eCollect/node-ebics-client/pull/1)
- add new order type XZ53 and removed repeated dateRange to consts [`5ff3147`](https://github.com/eCollect/node-ebics-client/commit/5ff314712443c4c8465f46292b010cfedfed8c2e)
- move dateRange from constants to utils [`aa761cf`](https://github.com/eCollect/node-ebics-client/commit/aa761cf7ad87a271d6e6d9eed40e04eb4376f6c5)
- bump version [`79f17e1`](https://github.com/eCollect/node-ebics-client/commit/79f17e14045d121c9505eb3118967f5f88ae79e2)
#### [v0.0.6](https://github.com/eCollect/node-ebics-client/compare/v0.0.5...v0.0.6)
> 24 July 2019
- remove eCollect from constant and minor verbose error handaling [`#4`](https://github.com/eCollect/node-ebics-client/pull/4)
- add iso pain format XCT order type [`#5`](https://github.com/eCollect/node-ebics-client/pull/5)
- remove eCollect from constant and minor verbose error handeling [`cb2062a`](https://github.com/eCollect/node-ebics-client/commit/cb2062ae2fbd8e8881de26561efddad1f272e065)
- bump [`c9f52d3`](https://github.com/eCollect/node-ebics-client/commit/c9f52d3bd99b9f8761652365b217d9580fa34632)
#### [v0.0.5](https://github.com/eCollect/node-ebics-client/compare/v0.0.4...v0.0.5)
> 28 June 2019
- Fix parsing of the bank keys in the HPB response [`#3`](https://github.com/eCollect/node-ebics-client/pull/3)
- tc for bank keys parsing error [`c571ef1`](https://github.com/eCollect/node-ebics-client/commit/c571ef181bca2e0cbec70bc6df53c706acd6c829)
- #2 corrected bank keys parsing [`5f0b6cd`](https://github.com/eCollect/node-ebics-client/commit/5f0b6cd3747c4613920d2f71f3c04ce13225d397)
#### [v0.0.4](https://github.com/eCollect/node-ebics-client/compare/v0.0.36...v0.0.4)
> 3 September 2018
- Major changes. Separating responsibilities. Orders builder, serializer. [`ff9a3a1`](https://github.com/eCollect/node-ebics-client/commit/ff9a3a16b47d0a25674134c875bfd651995837e4)
- code optimization [`1876360`](https://github.com/eCollect/node-ebics-client/commit/187636019c290d757aca77d4c14fb4f2519acd38)
- client and order optimization [`9454992`](https://github.com/eCollect/node-ebics-client/commit/945499290a8698aed504b573019de2c23148006a)
#### v0.0.3
> 17 May 2018
- initial commit [`1f947ff`](https://github.com/eCollect/node-ebics-client/commit/1f947ff1480c522f89fa1f547581b55e2378d920)
- Initial commit [`cd37de3`](https://github.com/eCollect/node-ebics-client/commit/cd37de3895e32a61798c79ce3a6447e2f269019d)

View File

@@ -1,5 +1,19 @@
# node-ebics-client v0.0.35
---
<p align="center">
<img src="assets/logo.png" width="300px" height="auto"/>
</p>
<h1 align="center">node-ebics-client</h1>
<p align="center">
<a href="https://travis-ci.org/eCollect/node-ebics-client" title="Build Status"><img src="https://travis-ci.org/eCollect/node-ebics-client.svg?branch=master" alt="Build Status" /></a>
<a href="https://www.npmjs.com/package/ebics-client" title="Build Status">
<img alt="ebics-client" src="https://img.shields.io/npm/v/ebics-client">
</a>
<a href="https://snyk.io/test/github/ecollect/node-ebics-client" title="Known Vulnerabilities">
<img src="https://snyk.io/test/github/ecollect/node-ebics-client/badge.svg" alt="Known Vulnerabilities">
</a>
<a href="/eCollect/node-ebics-client/blob/master/LICENSE" title="GPL-3.0"><img alt="GPL-3.0" src="https://img.shields.io/github/license/eCollect/node-ebics-client"></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>
Pure node.js ( >=8 ) implementation of [EBICS](https://en.wikipedia.org/wiki/Electronic_Banking_Internet_Communication_Standard) ( Electronic Banking Internet Communication ).
@@ -12,6 +26,8 @@ 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)
* [Zürcher Kantonalbank](https://www.zkb.ch/en/lg/ew.html)
* [Raiffeisen Schweiz](https://www.raiffeisen.ch/rch/de.html)
* [BW Bank](https://www.bw-bank.de/de/home.html)
* [Bank GPB International S.A.](https://gazprombank.lu/e-banking)
## Inspiration
@@ -21,4 +37,6 @@ The basic concept of this library was inspired by the [EPICS](https://github.com
## Copyright
Copyright: eCollect AG, 2018.
Copyright: eCollect AG, 2018-9.
Licensed under the [GPLv3](LICENSE) license.

BIN
assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

25
changelog-template.hbs Normal file
View File

@@ -0,0 +1,25 @@
### Changelog
{{#each releases}}
{{#if href}}
###{{#unless major}}#{{/unless}} [{{title}}]({{href}})
{{else}}
#### {{title}}
{{/if}}
{{#if tag}}
> {{niceDate}}
{{/if}}
{{#if summary}}
{{summary}}
{{/if}}
{{#each merges}}
- {{message}}{{#if href}} [`#{{id}}`]({{href}}){{/if}}
{{/each}}
{{#commit-list commits heading='' message='' exclude='wip: '}}
- {{#if breaking}}**Breaking change:** {{/if}}{{subject}}{{#if href}} [`{{shorthash}}`]({{href}}){{/if}}
{{/commit-list}}
{{/each}}

26
examples/bankLetter.js Executable file
View File

@@ -0,0 +1,26 @@
#! /usr/bin/env node
'use strict';
const ebics = require('../index');
const path = require('path');
const fs = require('fs');
const os = require('os');
const configjs = require('./config.js');
const client = gClient;
const bankName = gConfig.bankName;
const languageCode = gConfig.languageCode;
const template = fs.readFileSync('../templates/ini_'+languageCode+'.hbs').toString();
const letter = new ebics.BankLetter({ client, bankName, template });
const bankLetterFile = path.join(os.homedir(), 'bankLetter_'+languageCode+'.html');
letter.serialize(bankLetterFile)
.then(() => {
console.log('Send your bank the letter (%s)', bankLetterFile);
})
.catch((err) => {
console.error(err);
process.exit(1);
});

View File

@@ -0,0 +1,10 @@
{
"serverAddress": "https://ebics-test.server",
"partnerId": "EBICS ParnerID Test",
"userId": "MyUserIdTest",
"hostId": "MyHostIdTest",
"passphrase": "MyPasswordTest",
"keyStorage": "./keys-test",
"bankName":"Test Bank",
"languageCode":"en"
}

View File

@@ -0,0 +1,10 @@
{
"serverAddress": "https://ebics.server",
"partnerId": "EBICS ParnerID Production",
"userId": "MyUserIdProduction",
"hostId": "MyHostIdProduction",
"passphrase": "MyPasswordProduction",
"keyStorage": "./keys-prod",
"bankName":"Production Bank",
"languageCode":"en"
}

21
examples/getClient.js Normal file
View File

@@ -0,0 +1,21 @@
'use strict';
const { Client, fsKeysStorage } = require('../index');
const loadConfig = require('./loadConfig');
module.exports = ({
serverAddress,
partnerId,
userId,
hostId,
passphrase,
keyStoragePath,
} = loadConfig()) => new Client({
serverAddress,
partnerId,
userId,
hostId,
passphrase,
keyStorage: fsKeysStorage(keyStoragePath),
});

26
examples/initialize.js Executable file
View File

@@ -0,0 +1,26 @@
#! /usr/bin/env node
'use strict';
const ebics = require('../index');
const configjs = require('./config.js');
const client = gClient;
// New keys will be generated and saved in ./keys-test
client.send(ebics.Orders.INI)
.then((resp) => {
console.log('Response for INI order %j', resp);
return client.send(ebics.Orders.HIA);
})
.then((resp) => {
console.log('Response for HIA order %j', resp);
if (resp.technicalCode !== '000000')
throw new Error('Something might went wrong');
console.log('Public keys should be sent to bank now. See examples/bankLetter.js');
})
.catch((err) => {
console.error(err);
process.exit(1);
});

35
examples/loadConfig.js Normal file
View File

@@ -0,0 +1,35 @@
'use strict';
const fs = require('fs');
const path = require('path');
const safeLoadJson = (file) => {
if (!file)
return {};
try {
return JSON.parse(fs.readFileSync(file));
} catch (e) {
console.warn(`Couldn't load ${file} config file.`);
return {};
}
}
const getDefaultEnv = () => {
const [,,parArg] = process.argv;
return parArg || process.env.NODE_ENV;
}
const loadConfig = (configDirectory = path.join(__dirname, './config'), env = getDefaultEnv()) => {
console.log(`Loading config form ${configDirectory} with env set to ${env}.`);
const baseConfigFile = path.join(configDirectory, 'config.json');
const envConfigFile = env ? path.join(configDirectory, `config.${env}.json`) : null;
return {
...safeLoadJson(baseConfigFile),
...safeLoadJson(envConfigFile),
}
}
module.exports = loadConfig();

View File

@@ -0,0 +1,21 @@
#! /usr/bin/env node
'use strict';
const client = require('./getClient')();
// Client keys must be already generated and send by letter.
// The bank should have enabled the user
client.send(ebics.Orders.HPB)
.then((resp) => {
console.log('Response for HPB order %j', resp);
if (resp.technicalCode !== '000000')
throw new Error('Something went wrong');
console.log('Received bank keys: %j', resp.bankKeys);
return client.setBankKeys(resp.bankKeys);
})
.catch((err) => {
console.error(err);
process.exit(1);
});

View File

@@ -0,0 +1,21 @@
#! /usr/bin/env node
'use strict';
const client = require('./getClient')();
// The bank keys must have been already saved
client.send(ebics.Orders.C52(null, null)) // startDate 'YYYY-MM-DD', endDate 'YYYY-MM-DD'
.then((resp) => {
console.log('Response for C52 order %j', resp);
if (resp.technicalCode !== '000000')
throw new Error('Something went wrong');
// Parsing and processing the CAMT052 file should happen somewhere here, ideally after saving it to disk
const data = Buffer.from(resp.orderData);
console.log(data.toString('utf8'));
})
.catch((err) => {
console.error(err);
process.exit(1);
});

View File

@@ -0,0 +1,21 @@
#! /usr/bin/env node
'use strict';
const client = require('./getClient')();
// The bank keys must have been already saved
client.send(ebics.Orders.C53(null, null)) // startDate 'YYYY-MM-DD', endDate 'YYYY-MM-DD'
.then((resp) => {
console.log('Response for C53 order %j', resp);
if (resp.technicalCode !== '000000')
throw new Error('Something went wrong');
// Parsing and processing the CAMT053 file should happen somewhere here, ideally after saving it to disk
const data = Buffer.from(resp.orderData);
console.log(data.toString('utf8'));
})
.catch((err) => {
console.error(err);
process.exit(1);
});

View File

@@ -0,0 +1,21 @@
#! /usr/bin/env node
'use strict';
const client = require('./getClient')();
// The bank keys must have been already saved
client.send(ebics.Orders.DKI(null, null)) // startDate 'YYYY-MM-DD', endDate 'YYYY-MM-DD'
.then((resp) => {
console.log('Response for DKI order %j', resp);
if (resp.technicalCode !== '000000')
throw new Error('Something went wrong');
// Processing of the Exchange Rate file should go here, ideally after saving it to disk
const data = Buffer.from(resp.orderData);
console.log(data.toString('utf8'));
})
.catch((err) => {
console.error(err);
process.exit(1);
});

View File

@@ -0,0 +1,20 @@
#! /usr/bin/env node
'use strict';
const client = require('./getClient')();
// The bank keys must have been already saved
client.send(ebics.Orders.HKD)
.then((resp) => {
console.log('Response for HKD order %j', resp);
if (resp.technicalCode !== '000000')
throw new Error('Something went wrong');
const data = Buffer.from(resp.orderData);
console.log(data.toString('utf8'));
})
.catch((err) => {
console.error(err);
process.exit(1);
});

View File

@@ -0,0 +1,20 @@
#! /usr/bin/env node
'use strict';
const client = require('./getClient')();
// The bank keys must have been already saved
client.send(ebics.Orders.HTD)
.then((resp) => {
console.log('Response for HTD order %j', resp);
if (resp.technicalCode !== '000000')
throw new Error('Something went wrong');
const data = Buffer.from(resp.orderData);
console.log(data.toString('utf8'));
})
.catch((err) => {
console.error(err);
process.exit(1);
});

View File

@@ -0,0 +1,21 @@
#! /usr/bin/env node
'use strict';
const client = require('./getClient')();
// The bank keys must have been already saved
client.send(ebics.Orders.STA(null, null)) // startDate 'YYYY-MM-DD', endDate 'YYYY-MM-DD'
.then((resp) => {
console.log('Response for STA order %j', resp);
if (resp.technicalCode !== '000000')
throw new Error('Something went wrong');
// Parsing and processing the MT940 file should happen somewhere here, ideally after saving it to disk
const data = Buffer.from(resp.orderData);
console.log(data.toString('utf8'));
})
.catch((err) => {
console.error(err);
process.exit(1);
});

View File

@@ -0,0 +1,21 @@
#! /usr/bin/env node
'use strict';
const client = require('./getClient')();
// The bank keys must have been already saved
client.send(ebics.Orders.VMK(null, null)) // startDate 'YYYY-MM-DD', endDate 'YYYY-MM-DD'
.then((resp) => {
console.log('Response for VMK order %j', resp);
if (resp.technicalCode !== '000000')
throw new Error('Something went wrong');
// Parsing and processing the MT942 file should happen somewhere here, ideally after saving it to disk
const data = Buffer.from(resp.orderData);
console.log(data.toString('utf8'));
})
.catch((err) => {
console.error(err);
process.exit(1);
});

View File

@@ -0,0 +1,20 @@
#! /usr/bin/env node
'use strict';
const ebics = require('../index');
const fs = require('fs');
const client = require('./getClient')();
// The bank keys must have been already saved
const paymentFile = fs.readFileSync('mytestfile.xml').toString();
client.send(ebics.Orders.XG1(paymentFile))
.then((resp) => {
console.log('Response for XG1 order %j', resp);
})
.catch((err) => {
console.error(err);
process.exit(1);
});

View File

@@ -1,4 +1,15 @@
'use strict';
const Client = require('./lib/Client');
module.exports = Client;
const Orders = require('./lib/predefinedOrders');
const fsKeysStorage = require('./lib/storages/fsKeysStorage');
const tracesStorage = require('./lib/storages/tracesStorage');
const BankLetter = require('./lib/BankLetter');
module.exports = {
Client,
Orders,
BankLetter,
fsKeysStorage,
tracesStorage,
};

View File

@@ -2,65 +2,66 @@
const fs = require('fs');
const moment = require('moment');
const handlebars = require('handlebars');
const BN = require("bn.js");
const Crypto = require('./crypto/Crypto');
const { date } = require('./utils.js');
const registerHelpers = () => {
handlebars.registerHelper('today', () => date.toISODate(Date.now(), false));
handlebars.registerHelper('now', () => date.toISOTime(Date.now(), false));
handlebars.registerHelper('keyExponentBits', k => Buffer.byteLength(k.e()) * 8);
handlebars.registerHelper('keyModulusBits', k => k.size());
handlebars.registerHelper('keyExponent', k => k.e('hex'));
handlebars.registerHelper('keyModulus', k => k.n('hex').toUpperCase().match(/.{1,2}/g).join(' '));
handlebars.registerHelper('sha256', (k) => {
const digest = Buffer.from(Crypto.digestPublicKey(k), 'base64').toString('HEX');
return digest.toUpperCase().match(/.{1,2}/g).join(' ');
});
};
const writeFile = (file, content) => new Promise((resolve, reject) => fs.writeFile(file, content, (err, result) => {
if (err)
return reject(err);
return resolve(result);
}));
module.exports = class BankLetter {
constructor(client, bankName) {
this.client = client;
this.bankName = bankName;
this.pathToTemplate = './app/ebics/ini.hbs';
};
constructor({
client,
bankName,
template,
}) {
this.client = client;
this.bankName = bankName;
this.template = template;
}
_registerHelpers() {
handlebars.registerHelper("today", () => {
return moment().format('DD.MM.YYYY');
});
async generate() {
registerHelpers();
handlebars.registerHelper("now", () => {
return moment().format('HH:mm:ss');
});
handlebars.registerHelper("keyExponentBits", (k) => {
return Buffer.byteLength(new BN(k.key.keyPair.e).toBuffer()) * 8;
});
handlebars.registerHelper("keyModulusBits", (k) => {
return k.key.getKeySize();
// return Buffer.byteLength(new BN(k.key.keyPair.e).toBuffer()) * 8;
});
handlebars.registerHelper("keyExponent", (k) => {
return k.e();
});
handlebars.registerHelper("keyModulus", (k) => {
return k.n().toUpperCase().match(/.{1,2}/g).join(' ');
});
handlebars.registerHelper("sha256", (k) => {
const digest = Buffer.from(k.publicDigest(), 'base64').toString('HEX');
return digest.toUpperCase().match(/.{1,2}/g).join(' ');
});
};
generate() {
this._registerHelpers();
const str = fs.readFileSync(this.pathToTemplate).toString();
const templ = handlebars.compile(str);
const templ = handlebars.compile(this.template);
const keys = await this.client.keys();
const data = {
bankName : this.bankName,
userId : this.client.userId,
bankName: this.bankName,
userId: this.client.userId,
partnerId: this.client.partnerId,
A006 : this.client.a(),
X002 : this.client.x(),
E002 : this.client.e(),
A006: keys.a(),
X002: keys.x(),
E002: keys.e(),
};
return templ(data);
}
}
async serialize(path) {
const letter = await this.generate();
await writeFile(path, letter);
return true;
}
};

View File

@@ -1,234 +1,246 @@
'use strict';
const fs = require("fs");
const crypto = require("crypto");
const $request = require("request");
const $request = require('request');
const BN = require('bn.js');
const xpath = require("xpath");
const NodeRSA = require("node-rsa");
const DOMParser = require("xmldom").DOMParser;
const constants = require('./consts');
const Keys = require('./keymanagers/Keys');
const defaultKeyEncryptor = require('./keymanagers/defaultKeyEncryptor');
const signer = require('./middleware/signer');
const serializer = require('./middleware/serializer');
const response = require('./middleware/response');
const stringifyKeys = (keys) => {
Object.keys(keys).map((key) => {
keys[key] = keys[key] === null ? null : keys[key].toPem();
return key;
});
return JSON.stringify(keys);
};
/**
* Keys persistent object
* @typedef {Object} KeysObject
* @property {string} A006 - PEM representation of the A006 private key
* @property {string} E002 - PEM representation of the E002 private key
* @property {string} X002 - PEM representation of the X002 private key
* @property {string} bankX002 - PEM representation of the bankX002 public key
* @property {string} bankE002 - PEM representation of the bankE002 public key
*/
/**
* Key storage implementation
* @typedef {Object} KeyStorage
* @property {(data: KeysObject):Promise<void>} write - writes the keys to storage
* @property {():Promise<KeysObject>} read - reads the keys from storage
*/
/**
* Client initialization options
* @typedef {Object} EbicClientOptions
* @property {string} url - EBICS URL provided by the bank
* @property {string} partnerId - PARTNERID provided by the bank
* @property {string} hostId - HOSTID provided by the bank
* @property {string} userId - USERID provided by the bank
* @property {string} passphrase - passphrase for keys encryption
* @property {KeyStorage} keyStorage - keyStorage implementation
* @property {object} [tracesStorage] - traces (logs) storage implementation
*/
const Key = require('./Key');
const XMLSign = require('./middleware/XMLSign');
const ParseResponse = require('./middleware/ParseResponse');
const BankLetter = require('./BankLetter');
const EBICSINI = require('./orders/INI');
const EBICSHIA = require('./orders/HIA');
const EBICSHPB = require('./orders/HPB');
const EBICSHKD = require('./orders/HKD');
const EBICSHAA = require('./orders/HAA');
const EBICSHAC = require('./orders/HAC');
const EBICSHTD = require('./orders/HTD');
const EBICSC52 = require('./orders/C52');
const utils = {
exponent: {
// str = 65537 => AQAB
toBase64(str) {
return new BN(str).toBuffer().toString('base64');
},
// str = AQAB => 65537
fromBase64(str) {
return new BN(Buffer.from(str, 'base64'), 2).toNumber();
}
}
}
module.exports = class Client {
constructor(keysContent, passphrase, url, hostId, userId, partnerId) {
this.keysContent = keysContent;
this.passphrase = passphrase;
this.url = url;
this.hostId = hostId;
this.userId = userId;
this.partnerId = partnerId;
this.encryptAlgorithm = 'aes-256-cbc';
this.keys = keysContent ? this.extractKeys() : {};
};
/**
*Creates an instance of Client.
* @param {EbicClientOptions} clientOptions
*/
constructor({
url,
partnerId,
userId,
hostId,
passphrase,
keyStorage,
tracesStorage,
}) {
if (!url)
throw new Error('EBICS URL is required');
if (!partnerId)
throw new Error('partnerId is required');
if (!userId)
throw new Error('userId is required');
if (!hostId)
throw new Error('hostId is required');
if (!passphrase)
throw new Error('passphrase is required');
a() {
return this.keys["A006"];
};
if (!keyStorage || typeof keyStorage.read !== 'function' || typeof keyStorage.write !== 'function')
throw new Error('keyStorage implementation missing or wrong');
e() {
return this.keys["E002"];
};
x() {
return this.keys["X002"];
this.url = url;
this.partnerId = partnerId;
this.userId = userId;
this.hostId = hostId;
this.keyStorage = keyStorage;
this.keyEncryptor = defaultKeyEncryptor({ passphrase });
this.tracesStorage = tracesStorage || null;
}
bankX() {
return this.keys[`${this.hostId}.X002`];
async send(order) {
const isInObject = ('operation' in order);
if (!isInObject) throw new Error('Operation for the order needed');
if (order.operation.toUpperCase() === constants.orderOperations.ini) return this.initialization(order);
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 (order.operation.toUpperCase() === constants.orderOperations.upload) return this.upload(order);
if (order.operation.toUpperCase() === constants.orderOperations.download) return this.download(order);
throw new Error('Wrong order operation provided');
}
bankE() {
return this.keys[`${this.hostId}.E002`];
}
async initialization(order) {
const keys = await this.keys();
if (keys === null) this._generateKeys();
encrypt(data) {
const cipher = crypto.createCipher(this.encryptAlgorithm, this.passphrase);
const encrypted = cipher.update(data, 'utf8', 'hex') + cipher.final('hex');
if (this.tracesStorage)
this.tracesStorage.new().ofType('ORDER.INI');
const res = await this.ebicsRequest(order);
const xml = res.orderData();
return Buffer.from(encrypted).toString('base64');
};
const returnedTechnicalCode = res.technicalCode();
const returnedBusinessCode = res.businessCode();
decrypt(data) {
data = (new Buffer(data, 'base64')).toString();
return {
orderData: xml.length ? xml.toString() : xml,
orderId: res.orderId(),
const decipher = crypto.createDecipher(this.encryptAlgorithm, this.passphrase);
const decrypted = decipher.update(data, 'hex', 'utf8') + decipher.final('utf8');
technicalCode: returnedTechnicalCode,
technicalCodeSymbol: res.technicalSymbol(),
technicalCodeShortText: res.technicalShortText(returnedTechnicalCode),
technicalCodeMeaning: res.technicalMeaning(returnedTechnicalCode),
return decrypted;
};
businessCode: returnedBusinessCode,
businessCodeSymbol: res.businessSymbol(returnedBusinessCode),
businessCodeShortText: res.businessShortText(returnedBusinessCode),
businessCodeMeaning: res.businessMeaning(returnedBusinessCode),
static setup(passphrase, url, hostId, userId, partnerId, keysize = 2048) {
const client = new Client(null, passphrase, url, hostId, userId, partnerId);
for (let key in {A006: '', X002: '', E002: ''}) {
client.keys[key] = new Key(new NodeRSA({ b: keysize }));
}
return client;
};
saveIniLetter(bankName, path) {
const letter = new BankLetter(this, bankName);
try {
fs.writeFileSync(path, letter.generate());
} catch (error) {
throw error;
}
};
saveKeys(path) {
const data = {};
for (let key in this.keys) {
data[key] = this.encrypt(this.keys[key].toPem());
bankKeys: res.bankKeys(),
};
try {
fs.writeFileSync(path, JSON.stringify(data));
} catch(error) {
throw error;
}
};
extractKeys() {
const keys = {};
const jsonData = JSON.parse(this.keysContent);
for (let key in jsonData) {
keys[key] = new Key(this.decrypt(jsonData[key]));
}
return keys;
}
async download(order) {
const res = await this.ebicsRequest(order.toXML());
const ttt = res.toXML(); // keep this for debugging purposes
if (this.tracesStorage)
this.tracesStorage.new().ofType('ORDER.DOWNLOAD');
const res = await this.ebicsRequest(order);
order.transactionId = res.transactionId();
if (res.isSegmented() && res.isLastSegment()) {
const receipt = await this.ebicsRequest(order.toReceiptXML());
if (this.tracesStorage)
this.tracesStorage.connect().ofType('RECEIPT.ORDER.DOWNLOAD');
const receiptXML = order.toReceiptXML(); // keep this for debugging purposes
const rX = receipt.toXML(); // keep this for debugging purposes
await this.ebicsRequest(order);
}
return res.orderData();
};
const returnedTechnicalCode = res.technicalCode();
const returnedBusinessCode = res.businessCode();
async upload(order) {
let res = await this.ebicsRequest(order.toXML());
order.transactionId = res.transactionId();
const orderId = res.orderId();
return {
orderData: res.orderData(),
orderId: res.orderId(),
res = await this.ebicsRequest(order.toTransferXML());
technicalCode: returnedTechnicalCode,
technicalCodeSymbol: res.technicalSymbol(),
technicalCodeShortText: res.technicalShortText(returnedTechnicalCode),
technicalCodeMeaning: res.technicalMeaning(returnedTechnicalCode),
return res.transactionId();
businessCode: returnedBusinessCode,
businessCodeSymbol: res.businessSymbol(returnedBusinessCode),
businessCodeShortText: res.businessShortText(returnedBusinessCode),
businessCodeMeaning: res.businessMeaning(returnedBusinessCode),
};
}
async downloadAndUnzip(order) {
async upload(order) {
if (this.tracesStorage)
this.tracesStorage.new().ofType('ORDER.UPLOAD');
let res = await this.ebicsRequest(order);
const transactionId = res.transactionId();
const orderId = res.orderId();
order.transactionId = transactionId;
if (this.tracesStorage)
this.tracesStorage.connect().ofType('TRANSFER.ORDER.UPLOAD');
res = await this.ebicsRequest(order);
return [transactionId, orderId];
}
ebicsRequest(order) {
return new Promise((resolve, reject) => {
return new Promise(async (resolve, reject) => {
const { version } = order;
const keys = await this.keys();
const r = signer.version(version).sign((await serializer.use(order, this)).toXML(), keys.x());
if (this.tracesStorage)
this.tracesStorage.label(`REQUEST.${order.orderDetails.OrderType}`).data(r).persist();
$request.post({
url : this.url,
body : XMLSign.go(this, order),
headers: { 'content-type': 'text/xml;charset=UTF-8' }
url: this.url,
body: r,
headers: { 'content-type': 'text/xml;charset=UTF-8' },
}, (err, res, data) => {
return err ? reject(err): resolve(ParseResponse.go(this, data));
if (err) reject(err);
const ebicsResponse = response.version(version)(data, keys);
if (this.tracesStorage)
this.tracesStorage.label(`RESPONSE.${order.orderDetails.OrderType}`).connect().data(ebicsResponse.toXML()).persist();
resolve(ebicsResponse);
});
});
};
}
async INI() {
return this.ebicsRequest((new EBICSINI(this)).toXML());
};
async signOrder(order) {
const { version } = order;
const keys = await this.keys();
return signer.version(version).sign((await serializer.use(order, this)).toXML(), keys.x());
}
async HIA() {
return this.ebicsRequest((new EBICSHIA(this)).toXML());
};
async keys() {
try {
const keysString = await this._readKeys();
async HPB() {
const data = await this.download(new EBICSHPB(this));
const doc = new DOMParser().parseFromString(data, 'text/xml');
const sel = xpath.useNamespaces({'xmlns': "urn:org:ebics:H004"});
const keyNodes = sel("//xmlns:PubKeyValue", doc);
// console.log(keyNodes);
function xmlLastChild (node) {
let y = node.lastChild;
while (y.nodeType != 1) y = y.previousSibling;
return y;
};
for (let i = 0; i < keyNodes.length; i++) {
const type = xmlLastChild(keyNodes[i].parentNode).textContent;
const modulus = xpath.select("//*[local-name(.)='Modulus']", keyNodes[i])[0].textContent;
const exponent = xpath.select("//*[local-name(.)='Exponent']", keyNodes[i])[0].textContent;
const mod = new BN(Buffer.from(modulus, 'base64'), 2).toBuffer();
const exp = utils.exponent.fromBase64(exponent);
const bank = new NodeRSA();
bank.importKey({ n: mod, e: exp }, 'components-public');
this.keys[`${this.hostId}.${type}`] = new Key(bank);
return new Keys(JSON.parse(this.keyEncryptor.decrypt(keysString)));
} catch (err) {
return null;
}
}
return [this.bankX(), this.bankE()];
};
_generateKeys() {
const keysObject = Keys.generate();
HKD() {
return this.download(new EBICSHKD(this));
};
this._writeKeys(keysObject);
}
HAA() {
return this.download(new EBICSHAA(this));
};
async setBankKeys(bankKeys) {
const keysObject = await this.keys();
HTD() {
return this.download(new EBICSHTD(this));
};
keysObject.setBankKeys(bankKeys);
await this._writeKeys(keysObject);
}
HAC(from = null, to = null) {
return this.download(new EBICSHAC(this, from, to));
};
_readKeys() {
return this.keyStorage.read();
}
C52(from, to) {
return this.downloadAndUnzip(new EBICSC52(this, from, to));
_writeKeys(keysObject) {
return this.keyStorage.write(this.keyEncryptor.encrypt(stringifyKeys(keysObject.keys)));
}
};

View File

@@ -1,79 +0,0 @@
'use strict';
const crypto = require('crypto');
const BN = require('bn.js');
const NodeRSA = require("node-rsa");
const MGF1 = require('./MGF1');
module.exports = class Key {
constructor(encodedKey, passphrase = null) {
if (encodedKey instanceof NodeRSA) {
this.key = encodedKey
} else {
this.key = new NodeRSA(encodedKey);
}
};
publicDigest() {
const str = [this.e().replace(/^(0+)/g, ''), this.n().replace(/^(0+)/g, '')].map((x) => x.toLowerCase()).join(' ');
return crypto.createHash('sha256').update(str).digest('base64').trim();
};
publicEncrypt(buf) {
return crypto.publicEncrypt({ 'key': this.toPem(), padding: crypto.constants.RSA_PKCS1_PADDING }, buf);
}
n() {
return this.key.exportKey("components-public").n.toString("hex", 1);
};
e() {
return new BN(this.key.exportKey("components-public").e).toBuffer().toString('hex');
};
toPem() {
return this.key.isPrivate() ? this.key.exportKey("pkcs1-private-pem") : this.key.exportKey("pkcs8-public-pem");
}
sign(msg, salt = crypto.randomBytes(32)) {
const base = new BN(this._emsaPSS(msg, salt));
const power = new BN(this.key.keyPair.d.toBuffer());
const mod = new BN(this.key.keyPair.n.toBuffer());
return (this._modPow(base, power, mod)).toBuffer().toString('base64');
}
_emsaPSS(msg, salt) {
const eightNullBytes = Buffer.from("\x00".repeat(8));
const digestedMsg = crypto.createHash('sha256').update(msg).digest();
const mTickHash = crypto.createHash('sha256').update(Buffer.concat([eightNullBytes, digestedMsg, salt]), 'binary').digest();
const ps = Buffer.from("\x00".repeat(190));
const db = Buffer.concat([ps, Buffer.from("\x01"), salt]);
const dbMask = MGF1.generate(mTickHash, db.length);
const maskedDb = MGF1.xor(db, dbMask); // so far so good
let maskedDbMsb = (new MGF1)._rjust(new BN(maskedDb.slice(0, 1), 2).toString(2), 8, "0");
maskedDbMsb = "0" + maskedDbMsb.substr(1);
maskedDb[0] = (new BN(maskedDbMsb, 2).toBuffer())[0];
return Buffer.concat([maskedDb, mTickHash, Buffer.from('BC', 'hex')]);
}
_modPow(base, power, mod) {
let result = new BN(1);
while( power > 0 ) {
result = power.and(new BN(1)) == 1 ? (result.mul(base)).mod(mod) : result;
base = (base.mul(base)).mod(mod);
power = power.shrn(1);
}
return result;
}
};

View File

@@ -1,67 +0,0 @@
'use strict'
const crypto = require("crypto");
const BN = require('bn.js');
module.exports = class MGF1 {
constructor() {
this._len = 32;
}
/**
*
* @param {Buffer} seed
* @param {Number} masklen
*
* @returns Buffer
*/
static generate(seed, masklen) {
const mgf1 = new MGF1();
if ( masklen > 4294967296 * this._len) {
throw new Error('Mask too long');
}
const b = [];
for (let i = 0; i < mgf1._divceil(masklen, mgf1._len); i++) {
b[i] = crypto.createHash('sha256').update(Buffer.concat([seed, mgf1._i2osp(i, 4)])).digest();
}
return (Buffer.concat(b)).slice(0, masklen);
}
static xor(a, b) {
if ( a.length != b.length ) {
throw new Error('Different length for a and b');
}
for ( let i = 0; i < a.length; i++ ) {
a[i] ^= b[i];
}
return a;
}
_divceil(a, b) {
return ~~((a + b - 1) / b);
}
_i2osp(x, len) {
if ( x >= 256 ** len ) {
throw new Error('Integer too large');
}
return Buffer.from(this._rjust( (Buffer.from((new BN(x)).toArray('be', 4)).toString()).replace(/\x00/gi, ''), len, "\x00" ));
}
_rjust( string, width, padding ) {
padding = padding || " ";
padding = padding.substr( 0, 1 );
if ( string.length < width )
return padding.repeat( width - string.length ) + string;
else
return string;
}
}

View File

@@ -1,57 +0,0 @@
'use strict';
const zlib = require('zlib');
const crypto = require("crypto");
const DOMParser = require("xmldom").DOMParser;
const XMLSerializer = require("xmldom").XMLSerializer;
const xpath = require("xpath");
module.exports = class Response {
constructor(client, data) {
this.client = client;
this.doc = new DOMParser().parseFromString(data, 'text/xml');
};
isSegmented() {
const select = xpath.useNamespaces({'xmlns': "urn:org:ebics:H004"});
const node = select("//xmlns:header/xmlns:mutable/xmlns:SegmentNumber", this.doc);
return node.length ? true: false;
}
isLastSegment() {
const select = xpath.useNamespaces({'xmlns': "urn:org:ebics:H004"});
const node = select("//xmlns:header/xmlns:mutable/*[@lastSegment='true']", this.doc);
return node.length ? true: false;
}
orderData() {
const orderData = this.doc.getElementsByTagNameNS("urn:org:ebics:H004", "OrderData")[0].textContent;
const decipher = crypto.createDecipheriv('aes-128-cbc', this.transactionKey(), Buffer.from([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,])).setAutoPadding(false);
const data = Buffer.from(decipher.update(orderData, 'base64', 'binary') + decipher.final('binary'), 'binary');
return zlib.inflateSync(data).toString();
}
transactionKey() {
const keyNodeText = this.doc.getElementsByTagNameNS("urn:org:ebics:H004", "TransactionKey")[0].textContent;
const tkEncrypted = Buffer.from(keyNodeText, 'base64');
this.client.e().key.setOptions({encryptionScheme: 'pkcs1'});
return this.client.e().key.decrypt(tkEncrypted);
}
transactionId() {
const select = xpath.useNamespaces({'xmlns': "urn:org:ebics:H004"});
const node = select("//xmlns:header/xmlns:static/xmlns:TransactionID", this.doc);
return node.length ? node[0].textContent : '';
}
toXML() {
return new XMLSerializer().serializeToString(this.doc);
}
};

View File

@@ -1,103 +0,0 @@
'use strict';
const crypto = require("crypto");
const DOMParser = require("xmldom").DOMParser;
const XMLSerializer = require("xmldom").XMLSerializer;
const select = require("xpath.js");
const c14n = require('xml-crypto/lib/c14n-canonicalization').C14nCanonicalization;
module.exports = class Signer {
/**
* Contructor.
*
* @param {Client} client
* @param {String} data
*/
constructor(client, data) {
/**
* The main client
*
* @type {Signer}
*/
this.client = client;
/**
* Request data - generated xml
*
* @type {...}
*/
this.doc = new DOMParser().parseFromString(data, 'text/xml');
}
_junk() {
this.digest();
this.sign();
// console.log(this.toXML());
/* const headerSet = select(this.doc, "//*[@authenticate='true']").map(x => {
// x.setAttribute('xmlns:ds', 'http://www.w3.org/2000/09/xmldsig#');
return new c14n().process(x);
}).join();
const can = headerSet.replace('xmlns="urn:org:ebics:H004"', 'xmlns="urn:org:ebics:H004" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"');
const hash = crypto.createHash('sha256');
hash.update(can);
const digester = hash.digest('base64').trim();
if ( this.doc.getElementsByTagName("ds:DigestValue")[0] )
this.doc.getElementsByTagName("ds:DigestValue")[0].textContent = digester; */
/* const nodeSet = select(this.doc, "//ds:SignedInfo");
const canonicalized = nodeSet.map(x => {
const g = x.toString();
const res = new c14n().process(x);
return res;
}).join();
const canonicalizedString = canonicalized.replace('xmlns:ds="http://www.w3.org/2000/09/xmldsig#"', 'xmlns="urn:org:ebics:H004" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"');
// const SIGN = crypto.createSign('RSA-SHA256');
// SIGN.update(canonicalizedString);
// const key = SIGN.sign(this.client.x().key.exportKey("pkcs1-private-pem"), 'base64');
const f = this.client.x().key.sign(canonicalizedString, 'base64');
if ( this.doc.getElementsByTagName("ds:SignatureValue")[0] ) {
this.doc.getElementsByTagName("ds:SignatureValue")[0].textContent = f;
} */
}
digest() {
// get the xml node, where the digested value is supposed to be
const nodeDigestValue = this.doc.getElementsByTagName("ds:DigestValue")[0];
const nodes = select(this.doc, "//*[@authenticate='true']");
// canonicalize the node that has authenticate='true' attribute
const contentToDigest = select(this.doc, "//*[@authenticate='true']")
.map(x => {
const aaaa = x.toString();
return new c14n().process(x)
}).join("");
// 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#"');
if (nodeDigestValue) {
nodeDigestValue.textContent = crypto.createHash('sha256').update(fixedContent).digest('base64').trim();
}
};
sign() {
const nodeSignatureValue = this.doc.getElementsByTagName("ds:SignatureValue")[0];
if (nodeSignatureValue) {
const contentToSign = (new c14n().process(select(this.doc, "//ds:SignedInfo")[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 = this.client.x().key.sign(contentToSign, 'base64');
}
}
toXML() {
return new XMLSerializer().serializeToString(this.doc);
}
};

View File

@@ -1,12 +1,17 @@
'use strict';
const packageJson = require('../package.json');
const { version } = require('../package.json');
const name = 'eCollect Node Ebics Client';
const version = packageJson.version;
const name = 'Node Ebics Client';
const orderOperations = {
ini: 'INI',
upload: 'UPLOAD',
download: 'DOWNLOAD',
};
module.exports = {
name,
version,
productString: `${name} ${version}`,
};
name,
version,
orderOperations,
productString: `${name} ${version}`,
};

66
lib/crypto/BigNumber.js Normal file
View File

@@ -0,0 +1,66 @@
'use strict';
const { jsbn: { BigInteger } } = require('node-forge');
class BigNumber {
constructor(value, radix = 10) {
if (value === null || value === undefined)
throw new Error('value is missing.');
this._n = new BigInteger(null);
if (value instanceof BigNumber)
this._n = value._n;
else if (value instanceof BigInteger)
this._n = value;
else if (typeof value === 'number')
this._n.fromInt(value);
else if (typeof value === 'string')
this._n.fromString(value, radix);
else if (Buffer.isBuffer(value))
this._n.fromString(value.toString('hex'), 16);
else if (Array.isArray(value))
this._n.fromString(Buffer.from(value).toString('hex'), 16);
else
throw new TypeError('Unsupported value type.');
}
toBEBuffer(length) {
const arr = this._n.toByteArray();
if (length === undefined)
return Buffer.from(arr);
if (arr.length > length)
throw new Error('Number out of range.');
while (arr.length < length)
arr.unshift(0);
return Buffer.from(arr);
}
toString(radix = 10) {
const result = this._n.toString(radix);
if (radix === 16)
return result.padStart(2, '0');
return result;
}
and(num) {
return new BigNumber(this._n.and(new BigNumber(num)._n));
}
mul(num) {
return new BigNumber(this._n.multiply(new BigNumber(num)._n));
}
mod(num) {
return new BigNumber(this._n.mod(new BigNumber(num)._n));
}
shrn(num) {
return new BigNumber(this._n.shiftRight(new BigNumber(num)._n));
}
}
module.exports = BigNumber;

96
lib/crypto/Crypto.js Normal file
View File

@@ -0,0 +1,96 @@
'use strict';
const crypto = require('crypto');
const BigNumber = require('./BigNumber.js');
const mgf1 = require('./MGF1');
const modPow = (base, power, mod) => {
let result = new BigNumber(1);
while (power > 0) {
result = power.and(new BigNumber(1)) == 1 ? (result.mul(base)).mod(mod) : result; // eslint-disable-line
base = (base.mul(base)).mod(mod);
power = power.shrn(1);
}
return result;
};
const emsaPSS = (msg, salt) => {
const eightNullBytes = Buffer.from('\x00'.repeat(8));
const digestedMsg = crypto.createHash('sha256').update(msg).digest();
const mTickHash = crypto.createHash('sha256').update(Buffer.concat([eightNullBytes, digestedMsg, salt]), 'binary').digest();
const ps = Buffer.from('\x00'.repeat(190));
const db = Buffer.concat([ps, Buffer.from('\x01'), salt]);
const dbMask = mgf1.generate(mTickHash, db.length);
const maskedDb = mgf1.xor(db, dbMask);
let maskedDbMsb = mgf1.rjust(new BigNumber(maskedDb.slice(0, 1)).toString(2), 8, '0');
maskedDbMsb = `0${maskedDbMsb.substr(1)}`;
// console.log((new BN(maskedDbMsb, 2).toBuffer())[0], new BigNumber(maskedDbMsb, 2).toBuffer()[0]);
// maskedDb[0] = (new BN(maskedDbMsb, 2).toBuffer())[0]; // eslint-disable-line
maskedDb[0] = new BigNumber(maskedDbMsb, 2).toBEBuffer()[0]; // eslint-disable-line
return Buffer.concat([maskedDb, mTickHash, Buffer.from('BC', 'hex')]);
};
module.exports = class Crypto {
static digestPublicKey(key) {
const str = [key.e('hex').replace(/^(0+)/g, ''), key.n('hex').replace(/^(0+)/g, '')].map(x => x.toLowerCase()).join(' ');
return crypto.createHash('sha256').update(str).digest('base64').trim();
}
static publicEncrypt(key, data) {
return crypto.publicEncrypt({
key: key.toPem(),
padding: crypto.constants.RSA_PKCS1_PADDING,
}, data);
}
static privateDecrypt(key, data) {
return crypto.privateDecrypt({
key: key.toPem(),
padding: crypto.constants.RSA_PKCS1_PADDING,
}, data);
}
static privateSign(key, data, outputEncoding = 'base64') {
const signer = crypto.createSign('SHA256');
return signer.update(data).sign(key.toPem(), outputEncoding);
}
static sign(key, msg, salt = crypto.randomBytes(32)) {
// console.log(key.d());
const base = new BigNumber(emsaPSS(msg, salt));
const power = new BigNumber(key.d());
const mod = new BigNumber(key.n());
return (modPow(base, power, mod)).toBEBuffer().toString('base64');
}
static pad(d) {
const dLen = d.length;
const len = 16 * (Math.trunc(dLen / 16) + 1);
return Buffer.concat([d, Buffer.from(Buffer.from([0]).toString().repeat(len - dLen - 1)), Buffer.from([len - dLen])]);
}
static digestWithHash(data, algorith = 'sha256') {
return crypto.createHash(algorith).update(data).digest();
}
static nonce(outputEncoding = 'hex') {
return crypto.randomBytes(16).toString(outputEncoding);
}
static timestamp() {
return new Date().toISOString();
}
};

48
lib/crypto/MGF1.js Normal file
View File

@@ -0,0 +1,48 @@
'use strict';
const crypto = require('crypto');
const BigNumber = require('./BigNumber.js');
const MFG_LEN = 32;
const divceil = (a, b) => ~~(((a + b) - 1) / b); // eslint-disable-line no-bitwise
const rjust = (string, width, padding = ' ') => {
padding = padding.substr(0, 1);
if (string.length < width)
return padding.repeat(width - string.length) + string;
return string;
};
const xor = (a, b) => {
if (a.length !== b.length)
throw new Error('Different length for a and b');
for (let i = 0; i < a.length; i++)
a[i] ^= b[i]; // eslint-disable-line no-bitwise
return a;
};
const i2osp = (x, len) => {
if (x >= 256 ** len)
throw new Error('Integer too large');
return Buffer.from(rjust(new BigNumber(x).toBEBuffer(4).toString().replace(/\x00/gi, ''), len, '\x00')); // eslint-disable-line no-control-regex
};
module.exports = {
generate(seed, masklen) {
if (masklen > 4294967296 * MFG_LEN)
throw new Error('Mask too long');
const b = [];
for (let i = 0; i < divceil(masklen, MFG_LEN); i++)
b[i] = crypto.createHash('sha256').update(Buffer.concat([seed, i2osp(i, 4)])).digest();
return (Buffer.concat(b)).slice(0, masklen);
},
xor,
rjust,
};

138
lib/keymanagers/Key.js Normal file
View File

@@ -0,0 +1,138 @@
'use strict';
const {
pki: {
rsa,
publicKeyToPem,
privateKeyToPem,
publicKeyFromPem,
privateKeyFromPem,
},
jsbn: {
BigInteger,
},
} = require('node-forge');
const getKeyType = (str) => {
const matches = str.match(/(PRIVATE|PUBLIC) KEY/);
if (!matches)
return null;
return matches[1].toLowerCase();
};
const keyFromPem = (pem) => {
const type = getKeyType(pem);
const isPublic = type === 'public';
const key = isPublic ? publicKeyFromPem(pem) : privateKeyFromPem(pem);
return {
isPublic,
key,
};
};
/**
* Creates a public key from modulus and exponent
* @param {Buffer} mod - the modulus
* @param {Buffer} exp - the exponent
*/
const keyFromModAndExp = (mod, exp) => {
const bnMod = new BigInteger(mod.toString('hex'), 16);
const bnExp = new BigInteger(exp.toString('hex'), 16);
return {
key: rsa.setPublicKey(bnMod, bnExp),
isPublic: true,
};
};
module.exports = class Key {
constructor({
pem = null, mod = null, exp = null, size = 2048,
} = {}) {
// generate new private key
if (!pem && !mod && !exp) {
const keyPair = rsa.generateKeyPair(size);
this.keyIsPublic = false;
this.privateKey = keyPair.privateKey;
this.publicKey = keyPair.publicKey;
return;
}
// new key from pem string
if (pem) {
const { key, isPublic } = keyFromPem(pem);
this.keyIsPublic = isPublic;
this.privateKey = isPublic ? null : key;
this.publicKey = isPublic ? key : null;
return;
}
// new key from mod and exp
if (mod && exp) {
const { key, isPublic } = keyFromModAndExp(mod, exp);
this.keyIsPublic = isPublic;
this.privateKey = isPublic ? null : key;
this.publicKey = isPublic ? key : null;
return;
}
// not good
throw new Error(`Can not create key without ${!mod ? 'modulus' : 'exponent'}.`);
}
static generate(size = 2048) {
return new Key({ size });
}
static importKey({ mod, exp }) {
return new Key({ mod, exp });
}
n(to = 'buff') {
const key = this.keyIsPublic ? this.publicKey : this.privateKey;
const keyN = Buffer.from(key.n.toByteArray());
return to === 'hex' ? keyN.toString('hex', 1) : keyN;
}
e(to = 'buff') {
const key = this.keyIsPublic ? this.publicKey : this.privateKey;
const eKey = Buffer.from(key.e.toByteArray());
return to === 'hex' ? eKey.toString('hex') : eKey;
}
d() {
if (this.keyIsPublic)
throw new Error('Can not get d component out of public key.');
return Buffer.from(this.privateKey.d.toByteArray());
}
isPrivate() {
return !this.keyIsPublic;
}
isPublic() {
return this.keyIsPublic;
}
// eslint-disable-next-line class-methods-use-this
size() {
const keyN = this.n('hex');
const bn = new BigInteger(keyN, 16);
return bn.bitLength();
}
toPem() {
return this.keyIsPublic ? publicKeyToPem(this.publicKey) : privateKeyToPem(this.privateKey);
}
};

63
lib/keymanagers/Keys.js Normal file
View File

@@ -0,0 +1,63 @@
'use strict';
const Key = require('./Key');
const keyOrNull = (key) => {
if (key instanceof Key)
return key;
return key ? new Key({ pem: key }) : null;
};
module.exports = class Keys {
constructor({
A006,
E002,
X002,
bankX002,
bankE002,
}) {
this.keys = {
A006: keyOrNull(A006),
E002: keyOrNull(E002),
X002: keyOrNull(X002),
bankX002: keyOrNull(bankX002),
bankE002: keyOrNull(bankE002),
};
}
static generate() {
const keys = {};
Object.keys({ A006: '', X002: '', E002: '' }).forEach((key) => {
keys[key] = Key.generate(); // Key().generate();
});
return new Keys(keys);
}
setBankKeys(bankKeys) {
this.keys.bankX002 = new Key(bankKeys.bankX002); // Key().importKey(bankKeys.bankX002);
this.keys.bankE002 = new Key(bankKeys.bankE002); // Key().importKey(bankKeys.bankE002);
}
a() {
return this.keys.A006;
}
e() {
return this.keys.E002;
}
x() {
return this.keys.X002;
}
bankX() {
return this.keys.bankX002;
}
bankE() {
return this.keys.bankE002;
}
};

View File

@@ -0,0 +1,68 @@
'use strict';
const crypto = require('crypto');
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') => {
const storage = keysStorage;
const pass = passphrase;
const algo = algorithm;
// const createIfNone = createIfNone;
return {
generate(save = true) {
const keys = Keys.generate();
if (save) {
this.write(keys);
return this;
}
return keys;
},
write(keysObject) {
keysObject = keysObject.keys;
Object.keys(keysObject).map((key) => {
keysObject[key] = keysObject[key] === null ? null : keysObject[key].toPem();
return key;
});
storage.write(encrypt(JSON.stringify(keysObject), algo, pass));
return this;
},
setBankKeys(bankKeys) {
const keys = this.keys();
keys.setBankKeys(bankKeys);
this.write(keys);
},
keys() {
const keysString = storage.read();
return new Keys(JSON.parse(decrypt(keysString, algo, pass)));
},
};
};

View File

@@ -0,0 +1,24 @@
'use strict';
const crypto = require('crypto');
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,
algorithm = 'aes-256-cbc',
}) => ({
encrypt: data => encrypt(data, algorithm, passphrase),
decrypt: data => decrypt(data, algorithm, passphrase),
});

View File

@@ -1,22 +0,0 @@
'use strict';
const Response = require('../Response');
module.exports = class ParseResponse {
constructor(client, data) {
this.client = client;
this.data = data;
};
static go (client, data) {
const parseRensponse = new ParseResponse(client, data);
const response = new Response(client, data);
// TODO:
// raise error if any
this.data = response.doc;
return response;
}
};

View File

@@ -1,22 +0,0 @@
'use strict';
const Signer = require('../Signer');
module.exports = class XMLSign {
constructor(client, data) {
this.client = client;
this.data = data;
};
static go (client, data) {
const xmlSigner = new XMLSign(client, data);
const signer = new Signer(client, data);
signer.digest();
signer.sign();
this.data = signer.toXML();
return this.data;
}
};

View File

@@ -0,0 +1,11 @@
'use strict';
const H004Response = require('../orders/H004/response');
module.exports = {
version(v) {
if (v.toUpperCase() === 'H004') return H004Response;
throw Error('Error from middleware/response.js: Invalid version number');
},
};

View File

@@ -0,0 +1,13 @@
'use strict';
const H004Serializer = require('../orders/H004/serializer');
module.exports = {
use(order, client) {
const { version } = order;
if (version.toUpperCase() === 'H004') return H004Serializer.use(order, client);
throw Error('Error middleware/serializer.js: Invalid version number');
},
};

11
lib/middleware/signer.js Normal file
View File

@@ -0,0 +1,11 @@
'use strict';
const H004Signer = require('../orders/H004/signer');
module.exports = {
version(v) {
if (v.toUpperCase() === 'H004') return H004Signer;
throw Error('Error from middleware/signer.js: Invalid version number');
},
};

View File

@@ -1,50 +0,0 @@
'use strict';
const GenericOrder = require('./GenericOrder');
module.exports = class C52 extends GenericOrder {
constructor (client, from, to) {
super(client);
this._from = from;
this._to = to;
this._schema.header = {
"@" : { authenticate: true },
static: {
HostID : this.hostId,
Nonce : this.nonce(),
Timestamp: this.timestamp(),
PartnerID: this.partnerId,
UserID : this.userId,
Product : {
"@": { Language: "de" },
"#": this.productString,
},
OrderDetails: {
OrderType : "C52",
OrderAttribute : "DZHNN",
StandardOrderParams: {
DateRange: {
Start: this._from,
End : this._to
}
},
},
BankPubKeyDigests: {
Authentication: {
"@": { Version: "X002", Algorithm: "http://www.w3.org/2001/04/xmlenc#sha256" },
"#": this.client.bankX().publicDigest()
},
Encryption: {
"@": { Version: "E002", Algorithm: "http://www.w3.org/2001/04/xmlenc#sha256" },
"#": this.client.bankE().publicDigest()
}
},
SecurityMedium: "0000"
},
mutable: {
TransactionPhase: "Initialisation"
}
};
};
};

View File

@@ -1,180 +0,0 @@
'use strict';
// const randHex = require('../../lib/utils').randHex;
const crypto = require("crypto");
const js2xmlparser = require('js2xmlparser');
const consts = require('../consts');
module.exports = class GenericOrder {
constructor(client) {
this.client = client;
this.hostId = client.hostId;
this.userId = client.userId;
this.partnerId = client.partnerId;
this.transactionId = '';
this.xmlOptions = {
declaration: {
include: true,
encoding: "utf-8"
},
format: {
doubleQuotes: true,
indent: '',
newline: '',
pretty: true
}
};
this._schema = {
"@": {
"xmlns:ds": "http://www.w3.org/2000/09/xmldsig#",
xmlns: "urn:org:ebics:H004",
Version: "H004",
Revision: "1"
},
header: {},
AuthSignature: this.authSignature(),
body: {}
};
}
authSignature() {
return {
"ds:SignedInfo": {
"ds:CanonicalizationMethod": {
"@": {
Algorithm:
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
}
},
"ds:SignatureMethod": {
"@": {
Algorithm:
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
}
},
"ds:Reference": {
"@": { URI: "#xpointer(//*[@authenticate='true'])" },
"ds:Transforms": {
"ds:Transform": {
"@": {
Algorithm:
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
}
}
},
"ds:DigestMethod": {
"@": {
Algorithm:
"http://www.w3.org/2001/04/xmlenc#sha256"
}
},
"ds:DigestValue": {}
}
},
"ds:SignatureValue": {}
};
}
get schema() {
return this._schema;
}
get productString() {
return consts.productString;
}
nonce() {
return crypto.randomBytes(16).toString('hex');
}
timestamp() {
return new Date().toISOString();
}
root() {
return "ebicsRequest";
}
toReceiptXML() {
const xmlObj = {
"@": {
"xmlns:ds": "http://www.w3.org/2000/09/xmldsig#",
xmlns: "urn:org:ebics:H004",
Version: "H004",
Revision: "1"
},
header: {
"@": { authenticate: true },
static: {
HostID: this.hostId,
TransactionID: this.transactionId
},
mutable: {
TransactionPhase: 'Receipt',
}
},
AuthSignature: this.authSignature(),
body: {
TransferReceipt: {
"@": { authenticate: true },
ReceiptCode: 0
}
}
};
return js2xmlparser.parse(this.root(), xmlObj, this.xmlOptions);
}
toTransferXML(){
const xmlObj = {
"@": {
"xmlns:ds": "http://www.w3.org/2000/09/xmldsig#",
xmlns: "urn:org:ebics:H004",
Version: "H004",
Revision: "1"
},
header: {
"@": { authenticate: true },
static: {
HostID: this.hostId,
TransactionID: this.transactionId
},
mutable: {
TransactionPhase: 'Transfer',
SegmentNumber: {
"@": { lastSegment: true },
"#": 1
}
}
},
AuthSignature: this.authSignature(),
body: {
DataTransfer: {
OrderData: this.encryptedOrderData()
}
}
};
return js2xmlparser.parse(this.root(), xmlObj, this.xmlOptions);
}
encryptedOrderData() {
}
toXML() {
return js2xmlparser.parse(this.root(), this._schema, this.xmlOptions);
}
};

View File

@@ -1,79 +0,0 @@
'use strict';
const zlib = require('zlib');
const crypto = require("crypto");
const js2xmlparser = require('js2xmlparser');
const GenericOrder = require('./GenericOrder');
module.exports = class GenericUploadOrder extends GenericOrder {
constructor(client, document) {
super(client);
this._document = document;
this._key = crypto.randomBytes(16);
this._schema.body = {
DataTransfer: {
DataEncryptionInfo: {
"@": { authenticate: true },
EncryptionPubKeyDigest: {
"@": { Version: "E002", Algorithm: "http://www.w3.org/2001/04/xmlenc#sha256" },
"#": this.client.bankE().publicDigest()
},
TransactionKey: this.client.bankE().publicEncrypt(this._key).toString('base64'),
},
SignatureData: {
"@": { authenticate: true },
"#": this.encryptedOrderSignature()
}
}
};
};
orderSignature() {
const xmlObj = {
"@": {
xmlns: "http://www.ebics.org/S001",
"xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
"xsi:schemaLocation": "http://www.ebics.org/S001 http://www.ebics.org/S001/ebics_signature.xsd"
},
OrderSignatureData: {
SignatureVersion: "A006",
SignatureValue: this.signatureValue(),
PartnerID: this.partnerId,
UserID: this.userId
}
};
return js2xmlparser.parse('UserSignatureData', xmlObj, this.xmlOptions);
};
signatureValue() {
const digested = crypto.createHash('sha256').update(this._document.replace(/\n|\r/g, "")).digest();
return this.client.a().sign(digested);
};
encryptedOrderData() {
const dst = zlib.deflateSync(this._document.replace(/\r|\n/g, ""));
const cipher = crypto.createCipheriv('aes-128-cbc', this._key, Buffer.from([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,])).setAutoPadding(false);
return Buffer.concat([cipher.update(this._pad(dst)), cipher.final()]).toString('base64');
}
encryptedOrderSignature() {
const dst = zlib.deflateSync(this.orderSignature());
const cipher = crypto.createCipheriv('aes-128-cbc', this._key, Buffer.from([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,])).setAutoPadding(false);
return Buffer.concat([cipher.update(this._pad(dst)), cipher.final()]).toString('base64');
};
_pad(d) {
const dLen = d.length;
const len = 16 * ( Math.trunc(dLen / 16) + 1 );
return Buffer.concat([d, Buffer.from(Buffer.from([0]).toString().repeat(len - dLen - 1)), Buffer.from([len-dLen])]);
}
};

318
lib/orders/H004/errors.js Normal file
View File

@@ -0,0 +1,318 @@
'use strict';
const errors = {
technical: {
'000000': {
symbol: 'EBICS_OK',
short_text: 'OK',
meaning: 'No technical errors occurred during processing of the EBICS request',
},
'011000': {
symbol: 'EBICS_DOWNLOAD_POSTPROCESS_DONE',
short_text: 'Positive acknowledgement received',
meaning: "After receipt of a positive acknowledgment the download task was finished at the server's end and the EBICS transaction ended.",
},
'011001': {
symbol: 'EBICS_DOWNLOAD_POSTPROCESS_SKIPPED',
short_text: 'Negative acknowledgment received',
meaning: "After receipt of a negative acknowledgment the transaction was ended at the server's end without finishing the download task",
},
'011101': {
symbol: 'EBICS_TX_SEGMENT_NUMBER_UNDERRUN',
short_text: 'Segment number not reached',
meaning: 'The total number of segments transmitted during transaction initialisation was not reached (i.e. the attribute @lastSegment was set to "true" before the specified segment number was reached)',
},
'031001': {
symbol: 'EBICS_ORDER_PARAMS_IGNORED',
short_text: 'Unknown order parameters are ignored',
meaning: 'E.g. OrderParams for Upload specified',
},
'061001': {
symbol: 'EBICS_AUTHENTICATION_FAILED',
short_text: 'Authentication signature error',
meaning: 'Verification of the authentication signature was not successful',
},
'061011': {
symbol: 'EBICS_INVALID_HOST_ID',
short_text: 'The transmitted HostID is unknown on the bank side',
meaning: 'The transmitted HostID is unknown on the bank side',
},
'061002': {
symbol: 'EBICS_INVALID_REQUEST',
short_text: 'Message not EBICSconformant',
meaning: 'The syntax of the received message does not conform with EBICS specifications',
},
'061099': {
symbol: 'EBICS_INTERNAL_ERROR',
short_text: 'Internal EBICS error',
meaning: 'An internal error occurred during',
},
'061101': {
symbol: 'EBICS_TX_RECOVERY_SYNC',
short_text: 'Synchronisation necessary',
meaning: "Recovery of the transaction requires synchronisation between the customer system and the bank system Continuation of the transaction using the recovery point from the bank system's EBICS response",
},
'091002': {
symbol: 'EBICS_INVALID_USER_OR_USER_STATE',
short_text: 'Subscriber unknown or subscriber state inadmissible',
meaning: 'Either the initiating party is not known to the bank system or the subscriber state that is stored in the bank of the initiating party is inadmissible with regard to the order type',
},
'091003': {
symbol: 'EBICS_USER_UNKNOWN',
short_text: 'Subscriber unknown',
meaning: 'The initiating party is not known to the bank system',
},
'091004': {
symbol: 'EBICS_INVALID_USER_STATE',
short_text: 'Subscriber state unknown',
meaning: 'The subscriber state of the initiating party that is stored in the bank system is inadmissible with regard to the order type',
},
'091005': {
symbol: 'EBICS_INVALID_ORDER_TYPE',
short_text: 'Order type inadmissible',
meaning: 'The order type is unknown or not approved for use with EBICS',
},
'091006': {
symbol: 'EBICS_UNSUPPORTED_ORDER_TYPE',
short_text: 'Order type not supported',
meaning: 'The selected order type is optional with EBICS and is not supported by the financial institution',
},
'091007': {
symbol: 'EBICS_DISTRIBUTED_SIGNATURE_AUTHORISATION_FAILED',
short_text: 'Subscriber possesses no authorisation of signature for the referenced order in the VEU administration (Request recent signature folder)',
meaning: 'Retrieve recent signature folder with permissible orders of order type HVU (or HVZ, respectively)',
},
'091008': {
symbol: 'EBICS_BANK_PUBKEY_UPDATE_REQUIRED',
short_text: 'Bank key invalid',
meaning: 'The public bank key that is available to the subscriber is invalid',
},
'091009': {
symbol: 'EBICS_SEGMENT_SIZE_EXCEEDED',
short_text: 'Segment size exceeded',
meaning: 'The specified size of an upload order data segment (in the case of H003: 1 MB) has been exceeded',
},
'091010': {
symbol: 'EBICS_INVALID_XML',
short_text: 'XML invalid according to EBICS XML schema',
meaning: 'XML validation with EBICS schema failed or XML not well-formed',
},
'091011': {
symbol: 'EBICS_INVALID_HOST_ID',
short_text: "The transmitted HostID is unknown on the bank's side",
meaning: "The transmitted HostID is unknown on the bank's side. The use of this code is only provided for the HEV request Check the used HostID and correct it. Consultation with the bank, if necessary",
},
'091101': {
symbol: 'EBICS_TX_UNKNOWN_TXID',
short_text: 'Transaction ID invalid',
meaning: 'The supplied transaction ID is invalid',
},
'091102': {
symbol: 'EBICS_TX_ABORT',
short_text: 'Transaction canceled',
meaning: "The transaction was canceled at the server's end since recovery of the transaction is not supported or is no longer possible due to the recovery counter being too high",
},
'091103': {
symbol: 'EBICS_TX_MESSAGE_REPLAY',
short_text: 'Suspected Message replay (wrong time/time zone or nonce error)',
meaning: 'A message replay has been identified (Nonce/Timestamp pair doubled) or the difference of clock time between client and server exceeds the (parametrisable) tolerance limit',
},
'091104': {
symbol: 'EBICS_TX_SEGMENT_NUMBER_EXCEEDED',
short_text: 'Segment number exceeded',
meaning: 'The total segment number from transaction initialisation was exceeded, i.e. the attribute @lastSegment was set to "false" when the last segment was transmitted',
},
'091112': {
symbol: 'EBICS_INVALID_ORDER_PARAMS',
short_text: 'Invalid order parameters',
meaning: 'The content of OrderParams is invalid, e.g. if starting off behind the end in case of StandardOrderParams, or, in case of HVT, fetchOffset is higher than NumOrderInfos (total number of particular order information of an order)',
},
'091113': {
symbol: 'EBICS_INVALID_REQUEST_CONTENT',
short_text: 'Message content semantically not compliant to EBICS',
meaning: 'The received message complies syntactically EBICS XML schema, but not semantically to the EBICS guidelines, e.g. IZV upload with UZHNN requires NumSegments = 0',
},
'091117': {
symbol: 'EBICS_MAX_ORDER_DATA_SIZE_EXCEEDED',
short_text: 'The bank system does not support the requested order size',
meaning: 'Upload or download of an order file of improper size (e.g. for HVT, IZV, STA)',
},
'091118': {
symbol: 'EBICS_MAX_SEGMENTS_EXCEEDED',
short_text: 'Submitted number of segments for upload is too high',
meaning: 'The bank system does not support the specified total number of segments for upload',
},
'091119': {
symbol: 'EBICS_MAX_TRANSACTIONS_EXCEEDED',
short_text: 'Maximum number of parallel transactions per customer is exceeded',
meaning: 'The maximum number of parallel EBICS transactions defined in the bank system for the customer has been exceeded',
},
'091120': {
symbol: 'EBICS_PARTNER_ID_MISMATCH',
short_text: 'The partner ID (=customer ID) of the ES file is not identical to the partner ID (=customer ID) of the submitter.',
meaning: "On verifying the submitted signatures a partner ID was found in the document UserSignatureData that is not identical to the subscriber's partner ID in the request header",
},
'091121': {
symbol: 'EBICS_INCOMPATIBLE_ORDER_ATTRIBUTE',
short_text: 'The specified order attribute is not compatible with the order in the bank system',
meaning: 'Case 1) File with order attribute "DZHNN" or "OZHNN" submitted with an orderId or Case 2) File with order attribute "UZHNN" submitted without an orderId or with orderID which is already used for "DZHNN" File with order attribute "DZHNN" submitted with an orderId',
},
},
business: {
'000000': {
symbol: 'EBICS_OK',
short_text: 'OK',
meaning: 'No technical errors occurred during processing of the EBICS request',
},
'011301': {
symbol: 'EBICS_NO_ONLINE_CHECKS',
short_text: 'Optional preliminary verification is not supported by the bank system',
},
'091001': {
symbol: 'EBICS_DOWNLOAD_SIGNED_ONLY',
short_text: 'The bank system only supports bank-technically signed download order data for the order in question',
},
'091002': {
symbol: 'EBICS_DOWNLOAD_UNSIGNED_ONLY',
short_text: 'The bank system only supports unsigned download order data for the order in question',
},
'090003': {
symbol: 'EBICS_AUTHORISATION_ORDER_TYPE_FAILED',
short_text: 'The subscriber is not entitled to submit orders of the selected order type',
},
'090004': {
symbol: 'EBICS_INVALID_ORDER_DATA_FORMAT',
short_text: 'The transferred order data does not correspond with the specified format',
},
'090005': {
symbol: 'EBICS_NO_DOWNLOAD_DATA_AVAILABLE',
short_text: 'No data are available at present for the selected download order type',
},
'090006': {
symbol: 'EBICS_UNSUPPORTED_REQUEST_FOR_ORDER_INSTANCE',
short_text: 'The bank system does not support the selected order request for the concrete business transaction associated with this order',
},
'091105': {
symbol: 'EBICS_RECOVERY_NOT_SUPPORTED',
short_text: 'The bank system does not support Recovery',
},
'091111': {
symbol: 'EBICS_INVALID_SIGNATURE_FILE_FORMAT',
short_text: 'The submitted ES files do not comply with the defined format The ES file cannot be parsed syntactically (no business-related verification!)',
},
'091114': {
symbol: 'EBICS_ORDERID_UNKNOWN',
short_text: 'The submitted order number is unknown',
},
'091115': {
symbol: 'EBICS_ORDERID_ALREADY_EXISTS',
short_text: 'The submitted order number is already existent',
},
'091116': {
symbol: 'EBICS_PROCESSING_ERROR',
short_text: 'During processing of the EBICS request, other business-related errors have ocurred',
},
'091201': {
symbol: 'EBICS_KEYMGMT_UNSUPPORTED_VERSION_SIGNATURE',
short_text: 'The algorithm version of the bank-technical signature key is not supported by the financial institution (order types INI, HCS and PUB)',
},
'091202': {
symbol: 'EBICS_KEYMGMT_UNSUPPORTED_VERSION_AUTHENTICATION',
short_text: 'The algorithm version of theauthentication key is notsupported by the financialinstitution (order types HIA,HSA and HCA)',
},
'091203': {
symbol: 'EBICS_KEYMGMT_UNSUPPORTED_VERSION_ENCRYPTION',
short_text: 'The algorithm version of the encryption key is not supported by the financial institution (order types HIA, HSA and HCA) This error message is returned particularly when the process ID E001 is used which is invalid from schema version H003 on',
},
'091204': {
symbol: 'EBICS_KEYMGMT_KEYLENGTH_ERROR_SIGNATURE',
short_text: 'The key length of the banktechnical signature key is not supported by the financial institution (order types INI and PUB or HCS)',
},
'091205': {
symbol: 'EBICS_KEYMGMT_KEYLENGTH_ERROR_AUTHENTICATION',
short_text: 'The key length of the authentication key is not supported by the financial institution (order types HIA, HSA, HCS and HCA)',
},
'091206': {
symbol: 'EBICS_KEYMGMT_KEYLENGTH_ERROR_ENCRYPTION',
short_text: 'The key length of the encryption key is not supported by the financial institution (order types HIA, HSA, HCS and HCA)',
},
'091207': {
symbol: 'EBICS_KEYMGMT_NO_X509_SUPPORT',
short_text: 'The bank system does not support the evaluation of X.509 data (order types INI, HIA, HSA, PUB, HCA, HCS)',
},
'091208': {
symbol: 'EBICS_X509_CERTIFICATE_EXPIRED',
short_text: 'certificate is not valid because it has expired',
},
'091209': {
symbol: 'EBICS_X509_ERTIFICATE_NOT_VALID_YET',
short_text: 'certificate is not valid because it is not yet in effect',
},
'091210': {
symbol: 'EBICS_X509_WRONG_KEY_USAGE',
short_text: 'When verifying the certificate key usage, it has been detected that the certificate has not been issued for the current use. (only applies when key management order types are used)',
},
'091211': {
symbol: 'EBICS_X509_WRONG_ALGORITHM',
short_text: 'When verifying the certificate algorithm, it has been detected that the certificate has not been issued for the current use. (only applies when key management order types are used)',
},
'091212': {
symbol: 'EBICS_X509_INVALID_THUMBPRINT',
short_text: 'Reserved for next version',
},
'091213': {
symbol: 'EBICS_X509_CTL_INVALID',
short_text: 'When verifying the certificate, it has been detected that the certificate trust list (CTL) is not valid because, for example, it has expired.',
},
'091214': {
symbol: 'EBICS_X509_UNKNOWN_CERTIFICATE_AUTHORITY',
short_text: 'The chain cannot be verified due to an unknown certificate authority (CA) If OrderType = INI, PUB or HCS and X509v3 supported: The Reject of the Request is mandatory, if signature class <> "T"',
},
'091215': {
symbol: 'EBICS_X509_INVALID_POLICY',
short_text: 'Reserved for next version',
},
'091216': {
symbol: 'EBICS_X509_INVALID_BASIC_CONSTRAINTS',
short_text: 'Reserved for next version',
},
'091217': {
symbol: 'EBICS_ONLY_X509_SUPPORT',
short_text: 'With respect to certificates, the bank system only supports the evaluation of X.509 data',
},
'091218': {
symbol: 'EBICS_KEYMGMT_DUPLICATE_KEY',
short_text: 'During the key management request, it has been detected that the key or certificate sent for authentication or for encryption is the same as the signature key/certificate (INI, HIA, PUB, HCS,..)',
},
'091219': {
symbol: 'EBICS_CERTIFICATES_VALIDATION_ERROR',
short_text: 'The server is unable to match the certificate (ES key) with the previously declared information automatically.',
},
'091301': {
symbol: 'EBICS_SIGNATURE_VERIFICATION_FAILED',
short_text: 'Verification of the ES has failed In the case of asynchronouslyimplemented orders, the error can occur during preliminary verification.',
},
'091302': {
symbol: 'EBICS_ACCOUNT_AUTHORISATION_FAILED',
short_text: 'Preliminary verification of the account authorisation has failed',
},
'091303': {
symbol: 'EBICS_AMOUNT_CHECK_FAILED',
short_text: 'Preliminary verification of the account amount limit has failed',
},
'091304': {
symbol: 'EBICS_SIGNER_UNKNOWN',
short_text: 'A signatory of the order in question is not a valid subscriber.',
},
'091305': {
symbol: 'EBICS_INVALID_SIGNER_STATE',
short_text: 'The state of a signatory in the order in question is not admissible.',
},
'091306': {
symbol: 'EBICS_DUPLICATE_SIGNATURE',
short_text: 'The signatory has already signed the order on hand.',
},
},
};
module.exports = errors;

142
lib/orders/H004/response.js Normal file
View File

@@ -0,0 +1,142 @@
'use strict';
const zlib = require('zlib');
const crypto = require('crypto');
const Crypto = require('../../crypto/Crypto');
const { DOMParser, XMLSerializer } = require('xmldom');
const xpath = require('xpath');
const errors = require('./errors');
const DEFAULT_IV = Buffer.from(Array(16).fill(0, 0, 15));
const lastChild = (node) => {
let y = node.lastChild;
while (y.nodeType !== 1) y = y.previousSibling;
return y;
};
module.exports = (xml, keys) => ({
keys,
doc: new DOMParser().parseFromString(xml, 'text/xml'),
isSegmented() {
const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
const node = select('//xmlns:header/xmlns:mutable/xmlns:SegmentNumber', this.doc);
return !!node.length;
},
isLastSegment() {
const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
const node = select("//xmlns:header/xmlns:mutable/*[@lastSegment='true']", this.doc);
return !!node.length;
},
orderData() {
const orderDataNode = this.doc.getElementsByTagNameNS('urn:org:ebics:H004', 'OrderData');
if (!orderDataNode.length) return {};
const orderData = orderDataNode[0].textContent;
const decipher = crypto.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);
},
transactionKey() {
const keyNodeText = this.doc.getElementsByTagNameNS('urn:org:ebics:H004', 'TransactionKey')[0].textContent;
return Crypto.privateDecrypt(this.keys.e(), Buffer.from(keyNodeText, 'base64'));
},
transactionId() {
const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
const node = select('//xmlns:header/xmlns:static/xmlns:TransactionID', this.doc);
return node.length ? node[0].textContent : '';
},
orderId() {
const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
const node = select('.//xmlns:header/xmlns:mutable/xmlns:OrderID', this.doc);
return node.length ? node[0].textContent : '';
},
businessCode() {
const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
const node = select('//xmlns:body/xmlns:ReturnCode', this.doc);
return node.length ? node[0].textContent : '';
},
businessSymbol(code) {
return errors.business[code].symbol;
},
businessShortText(code) {
return errors.business[code].short_text;
},
businessMeaning(code) {
return errors.business[code].meaning;
},
technicalCode() {
const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
const node = select('//xmlns:header/xmlns:mutable/xmlns:ReturnCode', this.doc);
return node.length ? node[0].textContent : '';
},
technicalSymbol() {
const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
const node = select('//xmlns:header/xmlns:mutable/xmlns:ReportText', this.doc);
return node.length ? node[0].textContent : '';
},
technicalShortText(code) {
return errors.technical[code].short_text;
},
technicalMeaning(code) {
return errors.technical[code].meaning;
},
bankKeys() {
const orderData = this.orderData().toString();
if (!Object.keys(orderData).length) return {};
const doc = new DOMParser().parseFromString(orderData, 'text/xml');
const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
const keyNodes = select('//xmlns:PubKeyValue', doc);
const bankKeys = {};
if (!keyNodes.length) return {};
for (let i = 0; i < keyNodes.length; i++) {
const type = lastChild(keyNodes[i].parentNode).textContent;
const modulus = xpath.select(".//*[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 exp = Buffer.from(exponent, 'base64');
bankKeys[`bank${type}`] = {
mod,
exp,
};
}
return bankKeys;
},
toXML() {
return new XMLSerializer().serializeToString(this.doc);
},
});

View File

@@ -0,0 +1,19 @@
'use strict';
const constants = require('../../consts');
const iniSerializer = require('./serializers/ini');
const downloadSerializer = require('./serializers/download');
const uploadSerializer = require('./serializers/upload');
module.exports = {
use(order, client) {
const operation = order.operation.toUpperCase();
if (operation === constants.orderOperations.ini) return iniSerializer.use(order, client);
if (operation === constants.orderOperations.download) return downloadSerializer.use(order, client);
if (operation === constants.orderOperations.upload) return uploadSerializer.use(order, client);
throw Error('Error from orders/orders.js: Wrong order version/type.');
},
};

View File

@@ -0,0 +1,66 @@
'use strict';
const js2xmlparser = require('js2xmlparser');
const Crypto = require('../../../crypto/Crypto');
const genericSerializer = require('./generic');
module.exports = {
async use(order, client) {
const keys = await client.keys();
const ebicsAccount = {
partnerId: client.partnerId,
userId: client.userId,
hostId: client.hostId,
};
const { orderDetails, transactionId } = order;
const {
rootName, xmlOptions, xmlSchema, receipt, transfer, productString,
} = genericSerializer(client.hostId, transactionId);
this.productString = productString;
this.rootName = rootName;
this.xmlOptions = xmlOptions;
this.xmlSchema = xmlSchema;
this.receipt = receipt;
this.transfer = transfer;
if (transactionId) return this.receipt();
this.xmlSchema.header = {
'@': { authenticate: true },
static: {
HostID: ebicsAccount.hostId,
Nonce: Crypto.nonce(),
Timestamp: Crypto.timestamp(),
PartnerID: ebicsAccount.partnerId,
UserID: ebicsAccount.userId,
Product: {
'@': { Language: 'en' },
'#': productString,
},
OrderDetails: orderDetails,
BankPubKeyDigests: {
Authentication: {
'@': { Version: 'X002', Algorithm: 'http://www.w3.org/2001/04/xmlenc#sha256' },
'#': Crypto.digestPublicKey(keys.bankX()),
},
Encryption: {
'@': { Version: 'E002', Algorithm: 'http://www.w3.org/2001/04/xmlenc#sha256' },
'#': Crypto.digestPublicKey(keys.bankE()),
},
},
SecurityMedium: '0000',
},
mutable: {
TransactionPhase: 'Initialisation',
},
};
return this;
},
toXML() {
return js2xmlparser.parse(this.rootName, this.xmlSchema, this.xmlOptions);
},
};

View File

@@ -0,0 +1,134 @@
'use strict';
const constants = require('../../../consts');
const rootName = 'ebicsRequest';
const rootAttributes = {
'xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#',
xmlns: 'urn:org:ebics:H004',
Version: 'H004',
Revision: '1',
};
const header = {};
const authSignature = ({
'ds:SignedInfo': {
'ds:CanonicalizationMethod': {
'@': {
Algorithm:
'http://www.w3.org/TR/2001/REC-xml-c14n-20010315',
},
},
'ds:SignatureMethod': {
'@': {
Algorithm:
'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
},
},
'ds:Reference': {
'@': { URI: "#xpointer(//*[@authenticate='true'])" },
'ds:Transforms': {
'ds:Transform': {
'@': {
Algorithm:
'http://www.w3.org/TR/2001/REC-xml-c14n-20010315',
},
},
},
'ds:DigestMethod': {
'@': {
Algorithm:
'http://www.w3.org/2001/04/xmlenc#sha256',
},
},
'ds:DigestValue': {},
},
},
'ds:SignatureValue': {},
});
const body = {};
const xmlOptions = {
declaration: {
include: true,
encoding: 'utf-8',
},
format: {
doubleQuotes: true,
indent: '',
newline: '',
pretty: true,
},
};
module.exports = (hostId, transactionId) => ({
// return {
productString: constants.productString,
rootName,
xmlOptions,
xmlSchema: {
'@': rootAttributes,
header,
AuthSignature: authSignature,
body,
},
receipt() {
this.xmlSchema = {
'@': rootAttributes,
header: {
'@': { authenticate: true },
static: {
HostID: hostId,
TransactionID: transactionId,
},
mutable: {
TransactionPhase: 'Receipt',
},
},
AuthSignature: authSignature,
body: {
TransferReceipt: {
'@': { authenticate: true },
ReceiptCode: 0,
},
},
};
return this;
},
transfer(encryptedOrderData) {
this.xmlSchema = {
'@': rootAttributes,
header: {
'@': { authenticate: true },
static: {
HostID: hostId,
TransactionID: transactionId,
},
mutable: {
TransactionPhase: 'Transfer',
SegmentNumber: {
'@': { lastSegment: true },
'#': 1,
},
},
},
AuthSignature: authSignature,
body: {
DataTransfer: {
OrderData: encryptedOrderData,
},
},
};
return this;
},
// };
});

View File

@@ -0,0 +1,148 @@
'use strict';
const zlib = require('zlib');
const js2xmlparser = require('js2xmlparser');
const Crypto = require('../../../crypto/Crypto');
const genericSerializer = require('./generic');
const keySignature = (ebicsAccount, key, xmlOptions) => {
const xmlOrderData = {
'@': {
'xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#',
xmlns: 'http://www.ebics.org/S001',
},
SignaturePubKeyInfo: {
PubKeyValue: {
'ds:RSAKeyValue': {
'ds:Modulus': key.n().toString('base64'),
'ds:Exponent': key.e().toString('base64'),
},
TimeStamp: Crypto.timestamp(),
},
SignatureVersion: 'A006',
},
PartnerID: ebicsAccount.partnerId,
UserID: ebicsAccount.userId,
};
return js2xmlparser.parse('SignaturePubKeyOrderData', xmlOrderData, xmlOptions);
};
const orderData = (ebicsAccount, keys, xmlOptions) => {
const xmlOrderData = {
'@': {
'xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#',
xmlns: 'urn:org:ebics:H004',
},
AuthenticationPubKeyInfo: {
PubKeyValue: {
'ds:RSAKeyValue': {
'ds:Modulus': keys.x().n().toString('base64'),
'ds:Exponent': keys.x().e().toString('base64'),
},
},
AuthenticationVersion: 'X002',
},
EncryptionPubKeyInfo: {
PubKeyValue: {
'ds:RSAKeyValue': {
'ds:Modulus': keys.e().n().toString('base64'),
'ds:Exponent': keys.e().e().toString('base64'),
},
},
EncryptionVersion: 'E002',
},
PartnerID: ebicsAccount.partnerId,
UserID: ebicsAccount.userId,
};
return js2xmlparser.parse('HIARequestOrderData', xmlOrderData, xmlOptions);
};
const commonHeader = (ebicsAccount, orderDetails, productString) => ({
'@': { authenticate: true },
static: {
HostID: ebicsAccount.hostId,
Nonce: Crypto.nonce(),
Timestamp: Crypto.timestamp(),
PartnerID: ebicsAccount.partnerId,
UserID: ebicsAccount.userId,
Product: {
'@': { Language: 'en' },
'#': productString,
},
OrderDetails: orderDetails,
SecurityMedium: '0000',
},
mutable: {},
});
const process = {
INI: {
rootName: 'ebicsUnsecuredRequest',
header: (ebicsAccount, orderDetails, productString) => {
const ch = commonHeader(ebicsAccount, orderDetails, productString);
delete ch.static.Nonce;
delete ch.static.Timestamp;
return ch;
},
body: (ebicsAccount, keys, xmlOptions) => ({
DataTransfer: {
OrderData: Buffer.from(zlib.deflateSync(keySignature(ebicsAccount, keys.a(), xmlOptions))).toString('base64'),
},
}),
},
HIA: {
rootName: 'ebicsUnsecuredRequest',
header: (ebicsAccount, orderDetails, productString) => {
const ch = commonHeader(ebicsAccount, orderDetails, productString);
delete ch.static.Nonce;
delete ch.static.Timestamp;
return ch;
},
body: (ebicsAccount, keys, xmlOptions) => ({
DataTransfer: {
OrderData: Buffer.from(zlib.deflateSync(orderData(ebicsAccount, keys, xmlOptions))).toString('base64'),
},
}),
},
HPB: {
rootName: 'ebicsNoPubKeyDigestsRequest',
header: (ebicsAccount, orderDetails, productString) => commonHeader(ebicsAccount, orderDetails, productString),
body: () => ({}),
},
};
module.exports = {
async use(order, client) {
const keys = await client.keys();
const { orderDetails, transactionId } = order;
const { xmlOptions, xmlSchema, productString } = genericSerializer(client.host, transactionId);
const orderType = orderDetails.OrderType.toUpperCase();
const ebicsAccount = {
partnerId: client.partnerId,
userId: client.userId,
hostId: client.hostId,
};
this.rootName = process[orderType].rootName;
this.xmlOptions = xmlOptions;
this.xmlSchema = xmlSchema;
this.xmlSchema.header = process[orderType].header(ebicsAccount, orderDetails, productString);
this.xmlSchema.body = process[orderType].body(ebicsAccount, keys, this.xmlOptions);
if (orderType !== 'HPB' && Object.prototype.hasOwnProperty.call(this.xmlSchema, 'AuthSignature'))
delete this.xmlSchema.AuthSignature;
return this;
},
toXML() {
return js2xmlparser.parse(this.rootName, this.xmlSchema, this.xmlOptions);
},
};

View File

@@ -0,0 +1,93 @@
'use strict';
const zlib = require('zlib');
const crypto = require('crypto');
const js2xmlparser = require('js2xmlparser');
const Crypto = require('../../../crypto/Crypto');
const downloadSerializer = require('./download');
const transKey = crypto.randomBytes(16);
const signatureValue = (document, key) => {
const digested = Crypto.digestWithHash(document.replace(/\n|\r/g, ''));
return Crypto.sign(key, digested);
};
const orderSignature = (ebicsAccount, document, key, xmlOptions) => {
const xmlObj = {
'@': {
xmlns: 'http://www.ebics.org/S001',
'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'xsi:schemaLocation': 'http://www.ebics.org/S001 http://www.ebics.org/S001/ebics_signature.xsd',
},
OrderSignatureData: {
SignatureVersion: 'A006',
SignatureValue: signatureValue(document, key),
PartnerID: ebicsAccount.partnerId,
UserID: ebicsAccount.userId,
},
};
return js2xmlparser.parse('UserSignatureData', xmlObj, xmlOptions);
};
const encryptedOrderSignature = (ebicsAccount, document, transactionKey, key, xmlOptions) => {
const dst = zlib.deflateSync(orderSignature(ebicsAccount, document, key, xmlOptions));
const cipher = crypto.createCipheriv('aes-128-cbc', transactionKey, Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])).setAutoPadding(false);
return Buffer.concat([cipher.update(Crypto.pad(dst)), cipher.final()]).toString('base64');
};
const encryptedOrderData = (document, transactionKey) => {
const dst = zlib.deflateSync(document.replace(/\n|\r/g, ''));
const cipher = crypto.createCipheriv('aes-128-cbc', transactionKey, Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])).setAutoPadding(false);
return Buffer.concat([cipher.update(Crypto.pad(dst)), cipher.final()]).toString('base64');
};
module.exports = {
async use(order, client) {
const keys = await client.keys();
const ebicsAccount = {
partnerId: client.partnerId,
userId: client.userId,
hostId: client.hostId,
};
const { transactionId, document } = order;
const {
rootName, xmlOptions, xmlSchema, transfer,
} = await downloadSerializer.use(order, client);
this.rootName = rootName;
this.xmlOptions = xmlOptions;
this.xmlSchema = xmlSchema;
this.transfer = transfer;
if (transactionId) return this.transfer(encryptedOrderData(document, transKey));
this.xmlSchema.header.static.NumSegments = 1;
this.xmlSchema.body = {
DataTransfer: {
DataEncryptionInfo: {
'@': { authenticate: true },
EncryptionPubKeyDigest: {
'@': { Version: 'E002', Algorithm: 'http://www.w3.org/2001/04/xmlenc#sha256' },
'#': Crypto.digestPublicKey(keys.bankE()),
},
TransactionKey: Crypto.publicEncrypt(keys.bankE(), transKey).toString('base64'),
},
SignatureData: {
'@': { authenticate: true },
'#': encryptedOrderSignature(ebicsAccount, document, transKey, keys.a(), this.xmlOptions),
},
},
};
return this;
},
toXML() {
return js2xmlparser.parse(this.rootName, this.xmlSchema, this.xmlOptions);
},
};

49
lib/orders/H004/signer.js Normal file
View File

@@ -0,0 +1,49 @@
'use strict';
// const crypto = require('crypto');
const Crypto = require('../../crypto/Crypto');
const { DOMParser, XMLSerializer } = require('xmldom');
const xpath = require('xpath');
const C14n = require('xml-crypto/lib/c14n-canonicalization').C14nCanonicalization;
const digest = (doc) => {
// get the xml node, where the digested value is supposed to be
const nodeDigestValue = doc.getElementsByTagName('ds:DigestValue')[0];
// canonicalize the node that has authenticate='true' attribute
const contentToDigest = xpath.select("//*[@authenticate='true']", doc)
.map(x => new C14n().process(x)).join('');
// 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#"');
if (nodeDigestValue)
nodeDigestValue.textContent = Crypto.digestWithHash(fixedContent).toString('base64').trim();
return doc;
};
const sign = (doc, key) => {
const nodeSignatureValue = doc.getElementsByTagName('ds:SignatureValue')[0];
if (nodeSignatureValue) {
const select = xpath.useNamespaces({ 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');
}
return doc;
};
const toXML = doc => new XMLSerializer().serializeToString(doc);
module.exports = {
sign(data, keyX) {
const doc = new DOMParser().parseFromString(data, 'text/xml');
return toXML(sign(digest(doc), keyX));
},
};

View File

@@ -1,43 +0,0 @@
'use strict';
const GenericOrder = require('./GenericOrder');
module.exports = class HAA extends GenericOrder {
constructor (client) {
super(client);
this._schema.header = {
"@": { authenticate: true },
static: {
HostID: this.hostId,
Nonce: this.nonce(),
Timestamp: this.timestamp(),
PartnerID: this.partnerId,
UserID: this.userId,
Product: {
"@": { Language: "de" },
"#": this.productString
},
OrderDetails: {
OrderType: "HAA",
OrderAttribute: "DZHNN",
StandardOrderParams: ""
},
BankPubKeyDigests: {
Authentication: {
"@": { Version: "X002", Algorithm: "http://www.w3.org/2001/04/xmlenc#sha256" },
"#": this.client.bankX().publicDigest()
},
Encryption: {
"@": { Version: "E002", Algorithm: "http://www.w3.org/2001/04/xmlenc#sha256" },
"#": this.client.bankE().publicDigest()
}
},
SecurityMedium: "0000"
},
mutable: {
TransactionPhase: "Initialisation"
}
};
};
};

View File

@@ -1,54 +0,0 @@
'use strict';
const GenericOrder = require('./GenericOrder');
module.exports = class HAC extends GenericOrder {
constructor (client, from = null, to = null) {
super(client);
this._from = from;
this._to = to;
this._schema.header = {
"@" : { authenticate: true },
static: {
HostID : this.hostId,
Nonce : this.nonce(),
Timestamp: this.timestamp(),
PartnerID: this.partnerId,
UserID : this.userId,
Product : {
"@": { Language: "de" },
"#": this.productString,
},
OrderDetails: {
OrderType : "HAC",
OrderAttribute : "DZHNN",
StandardOrderParams: this._hasDateRange() ? {
DateRange: {
Start: this._from,
End : this._to
}
} : ""
},
BankPubKeyDigests: {
Authentication: {
"@": { Version: "X002", Algorithm: "http://www.w3.org/2001/04/xmlenc#sha256" },
"#": this.client.bankX().publicDigest()
},
Encryption: {
"@": { Version: "E002", Algorithm: "http://www.w3.org/2001/04/xmlenc#sha256" },
"#": this.client.bankE().publicDigest()
}
},
SecurityMedium: "0000"
},
mutable: {
TransactionPhase: "Initialisation"
}
};
};
_hasDateRange() {
return this._from && this._to;
}
};

View File

@@ -1,85 +0,0 @@
'use strict';
const zlib = require('zlib');
const js2xmlparser = require('js2xmlparser');
const GenericOrder = require('./GenericOrder');
module.exports = class HIA extends GenericOrder {
constructor(client) {
super(client);
this._schema = {
"@": {
"xmlns:ds": "http://www.w3.org/2000/09/xmldsig#",
xmlns: "urn:org:ebics:H004",
Version: "H004",
Revision: "1"
},
header: {
"@": { authenticate: true },
static: {
HostID: this.hostId,
PartnerID: this.partnerId,
UserID: this.userId,
Product: {
"@": { Language: "de" },
"#": this.productString,
},
OrderDetails: {
OrderType: "HIA",
OrderAttribute: "DZNNN"
},
SecurityMedium: "0000"
},
mutable: {}
},
body: {
DataTransfer: {
OrderData: Buffer.from(zlib.deflateSync(this.orderData())).toString('base64')
}
}
};
}
root() {
return "ebicsUnsecuredRequest";
};
orderData() {
const xmlOrderData = {
"@": {
"xmlns:ds": "http://www.w3.org/2000/09/xmldsig#",
xmlns: "urn:org:ebics:H004"
},
AuthenticationPubKeyInfo: {
PubKeyValue: {
"ds:RSAKeyValue": {
"ds:Modulus": Buffer.from(this.client.x().n(), 'HEX').toString('base64'),
"ds:Exponent": "AQAB"
},
},
AuthenticationVersion: "X002"
},
EncryptionPubKeyInfo: {
PubKeyValue: {
"ds:RSAKeyValue": {
"ds:Modulus": Buffer.from(this.client.e().n(), 'HEX').toString('base64'),
"ds:Exponent": "AQAB"
},
},
EncryptionVersion: "E002"
},
PartnerID: this.partnerId,
UserID: this.userId
};
return js2xmlparser.parse("HIARequestOrderData", xmlOrderData, this.xmlOptions);
};
toXML() {
return js2xmlparser.parse(this.root(), this._schema, this.xmlOptions);
};
};

View File

@@ -1,43 +0,0 @@
'use strict';
const GenericOrder = require('./GenericOrder');
module.exports = class HKD extends GenericOrder {
constructor (client) {
super(client);
this._schema.header = {
"@": { authenticate: true },
static: {
HostID: this.hostId,
Nonce: this.nonce(),
Timestamp: this.timestamp(),
PartnerID: this.partnerId,
UserID: this.userId,
Product: {
"@": { Language: "de" },
"#": this.productString,
},
OrderDetails: {
OrderType: "HKD",
OrderAttribute: "DZHNN",
StandardOrderParams: ""
},
BankPubKeyDigests: {
Authentication: {
"@": { Version: "X002", Algorithm: "http://www.w3.org/2001/04/xmlenc#sha256" },
"#": this.client.bankX().publicDigest()
},
Encryption: {
"@": { Version: "E002", Algorithm: "http://www.w3.org/2001/04/xmlenc#sha256" },
"#": this.client.bankE().publicDigest()
}
},
SecurityMedium: "0000"
},
mutable: {
TransactionPhase: "Initialisation"
}
};
};
};

View File

@@ -1,34 +0,0 @@
'use strict';
const GenericOrder = require('./GenericOrder');
module.exports = class HPB extends GenericOrder {
constructor (client) {
super(client);
this._schema.header = {
"@": { authenticate: true },
static: {
HostID: this.hostId,
Nonce: this.nonce(),
Timestamp: this.timestamp(),
PartnerID: this.partnerId,
UserID: this.userId,
Product: {
"@": { Language: "de" },
"#": this.productString,
},
OrderDetails: {
OrderType: "HPB",
OrderAttribute: "DZHNN"
},
SecurityMedium: "0000"
},
mutable: {}
};
};
root() {
return "ebicsNoPubKeyDigestsRequest";
};
};

View File

@@ -1,43 +0,0 @@
'use strict';
const GenericOrder = require('./GenericOrder');
module.exports = class HTD extends GenericOrder {
constructor (client) {
super(client);
this._schema.header = {
"@": { authenticate: true },
static: {
HostID: this.hostId,
Nonce: this.nonce(),
Timestamp: this.timestamp(),
PartnerID: this.partnerId,
UserID: this.userId,
Product: {
"@": { Language: "de" },
"#": this.productString,
},
OrderDetails: {
OrderType: "HTD",
OrderAttribute: "DZHNN",
StandardOrderParams: ""
},
BankPubKeyDigests: {
Authentication: {
"@": { Version: "X002", Algorithm: "http://www.w3.org/2001/04/xmlenc#sha256" },
"#": this.client.bankX().publicDigest()
},
Encryption: {
"@": { Version: "E002", Algorithm: "http://www.w3.org/2001/04/xmlenc#sha256" },
"#": this.client.bankE().publicDigest()
}
},
SecurityMedium: "0000"
},
mutable: {
TransactionPhase: "Initialisation"
}
};
};
};

View File

@@ -1,77 +0,0 @@
'use strict';
const zlib = require('zlib');
const js2xmlparser = require('js2xmlparser');
const GenericOrder = require('./GenericOrder');
module.exports = class INI extends GenericOrder {
constructor (client) {
super(client);
this._schema = {
"@": {
"xmlns:ds": "http://www.w3.org/2000/09/xmldsig#",
xmlns: "urn:org:ebics:H004",
Version: "H004",
Revision: "1"
},
header: {
"@": { authenticate: true },
static: {
HostID: this.hostId,
PartnerID: this.partnerId,
UserID: this.userId,
Product: {
"@": { Language: "de" },
"#": this.productString,
},
OrderDetails: {
OrderType: "INI",
OrderAttribute: "DZNNN"
},
SecurityMedium: "0000"
},
mutable: {}
},
body: {
DataTransfer: {
OrderData: Buffer.from(zlib.deflateSync(this.keySignature())).toString('base64')
}
}
};
}
root() {
return "ebicsUnsecuredRequest";
};
keySignature() {
const xmlOrderData = {
"@": {
"xmlns:ds": "http://www.w3.org/2000/09/xmldsig#",
xmlns: "http://www.ebics.org/S001"
},
SignaturePubKeyInfo: {
PubKeyValue: {
"ds:RSAKeyValue": {
"ds:Modulus": Buffer.from(this.client.a().n(), 'HEX').toString('base64'),
"ds:Exponent": "AQAB"
},
TimeStamp: this.timestamp()
},
SignatureVersion: "A006"
},
PartnerID: this.partnerId,
UserID: this.userId
};
return js2xmlparser.parse("SignaturePubKeyOrderData", xmlOrderData, this.xmlOptions);
};
toXML() {
return js2xmlparser.parse(this.root(), this._schema, this.xmlOptions);
}
};

View File

@@ -0,0 +1,8 @@
'use strict';
module.exports = document => ({
version: 'h004',
orderDetails: { OrderType: 'AZV', OrderAttribute: 'OZHNN', StandardOrderParams: {} },
operation: 'upload',
document,
});

View File

@@ -0,0 +1,13 @@
'use strict';
const utils = require('../utils');
module.exports = (start = null, end = null) => ({
version: 'h004',
orderDetails: {
OrderType: 'C52',
OrderAttribute: 'DZHNN',
StandardOrderParams: utils.dateRange(start, end),
},
operation: 'download',
});

View File

@@ -0,0 +1,13 @@
'use strict';
const utils = require('../utils');
module.exports = (start = null, end = null) => ({
version: 'h004',
orderDetails: {
OrderType: 'C53',
OrderAttribute: 'DZHNN',
StandardOrderParams: utils.dateRange(start, end),
},
operation: 'download',
});

View File

@@ -0,0 +1,8 @@
'use strict';
module.exports = document => ({
version: 'h004',
orderDetails: { OrderType: 'CCS', OrderAttribute: 'OZHNN', StandardOrderParams: {} },
operation: 'upload',
document,
});

View File

@@ -0,0 +1,8 @@
'use strict';
module.exports = document => ({
version: 'h004',
orderDetails: { OrderType: 'CCT', OrderAttribute: 'OZHNN', StandardOrderParams: {} },
operation: 'upload',
document,
});

View File

@@ -0,0 +1,8 @@
'use strict';
module.exports = document => ({
version: 'h004',
orderDetails: { OrderType: 'CD1', OrderAttribute: 'OZHNN', StandardOrderParams: {} },
operation: 'upload',
document,
});

View File

@@ -0,0 +1,8 @@
'use strict';
module.exports = document => ({
version: 'h004',
orderDetails: { OrderType: 'CDB', OrderAttribute: 'OZHNN', StandardOrderParams: {} },
operation: 'upload',
document,
});

View File

@@ -0,0 +1,8 @@
'use strict';
module.exports = document => ({
version: 'h004',
orderDetails: { OrderType: 'CDD', OrderAttribute: 'OZHNN', StandardOrderParams: {} },
operation: 'upload',
document,
});

View File

@@ -0,0 +1,8 @@
'use strict';
module.exports = document => ({
version: 'h004',
orderDetails: { OrderType: 'CDS', OrderAttribute: 'OZHNN', StandardOrderParams: {} },
operation: 'upload',
document,
});

View File

@@ -0,0 +1,13 @@
'use strict';
const utils = require('../utils');
module.exports = (start = null, end = null) => ({
version: 'h004',
orderDetails: {
OrderType: 'DKI',
OrderAttribute: 'DZHNN',
StandardOrderParams: utils.dateRange(start, end),
},
operation: 'download',
});

View File

@@ -0,0 +1,11 @@
'use strict';
module.exports = {
version: 'h004',
orderDetails: {
OrderType: 'HAA',
OrderAttribute: 'DZHNN',
StandardOrderParams: {},
},
operation: 'download',
};

View File

@@ -0,0 +1,13 @@
'use strict';
const utils = require('../utils');
module.exports = (start = null, end = null) => ({
version: 'h004',
orderDetails: {
OrderType: 'HAC',
OrderAttribute: 'DZHNN',
StandardOrderParams: utils.dateRange(start, end),
},
operation: 'download',
});

View File

@@ -0,0 +1,7 @@
'use strict';
module.exports = {
version: 'h004',
orderDetails: { OrderType: 'HIA', OrderAttribute: 'DZNNN' },
operation: 'ini',
};

View File

@@ -0,0 +1,11 @@
'use strict';
module.exports = {
version: 'h004',
orderDetails: {
OrderType: 'HKD',
OrderAttribute: 'DZHNN',
StandardOrderParams: {},
},
operation: 'download',
};

View File

@@ -0,0 +1,7 @@
'use strict';
module.exports = {
version: 'h004',
orderDetails: { OrderType: 'HPB', OrderAttribute: 'DZHNN' },
operation: 'ini',
};

View File

@@ -0,0 +1,11 @@
'use strict';
module.exports = {
version: 'h004',
orderDetails: {
OrderType: 'HPD',
OrderAttribute: 'DZHNN',
StandardOrderParams: {},
},
operation: 'download',
};

View File

@@ -0,0 +1,11 @@
'use strict';
module.exports = {
version: 'h004',
orderDetails: {
OrderType: 'HTD',
OrderAttribute: 'DZHNN',
StandardOrderParams: {},
},
operation: 'download',
};

View File

@@ -0,0 +1,7 @@
'use strict';
module.exports = {
version: 'h004',
orderDetails: { OrderType: 'INI', OrderAttribute: 'DZNNN' },
operation: 'ini',
};

View File

@@ -0,0 +1,13 @@
'use strict';
const utils = require('../utils');
module.exports = (start = null, end = null) => ({
version: 'h004',
orderDetails: {
OrderType: 'PTK',
OrderAttribute: 'DZHNN',
StandardOrderParams: utils.dateRange(start, end),
},
operation: 'download',
});

View File

@@ -0,0 +1,13 @@
'use strict';
const utils = require('../utils');
module.exports = (start = null, end = null) => ({
version: 'h004',
orderDetails: {
OrderType: 'STA',
OrderAttribute: 'DZHNN',
StandardOrderParams: utils.dateRange(start, end),
},
operation: 'download',
});

View File

@@ -0,0 +1,13 @@
'use strict';
const utils = require('../utils');
module.exports = (start = null, end = null) => ({
version: 'h004',
orderDetails: {
OrderType: 'VMK',
OrderAttribute: 'DZHNN',
StandardOrderParams: utils.dateRange(start, end),
},
operation: 'download',
});

View File

@@ -0,0 +1,8 @@
'use strict';
module.exports = document => ({
version: 'h004',
orderDetails: { OrderType: 'XCT', OrderAttribute: 'OZHNN', StandardOrderParams: {} },
operation: 'upload',
document,
});

View File

@@ -0,0 +1,8 @@
'use strict';
module.exports = document => ({
version: 'h004',
orderDetails: { OrderType: 'XE3', OrderAttribute: 'OZHNN', StandardOrderParams: {} },
operation: 'upload',
document,
});

View File

@@ -0,0 +1,8 @@
'use strict';
module.exports = document => ({
version: 'h004',
orderDetails: { OrderType: 'XG1', OrderAttribute: 'OZHNN', StandardOrderParams: {} },
operation: 'upload',
document,
});

View File

@@ -0,0 +1,13 @@
'use strict';
const utils = require('../utils');
module.exports = (start = null, end = null) => ({
version: 'h004',
orderDetails: {
OrderType: 'Z53',
OrderAttribute: 'DZHNN',
StandardOrderParams: utils.dateRange(start, end),
},
operation: 'download',
});

View File

@@ -0,0 +1,59 @@
'use strict';
const INI = require('./INI');
const HIA = require('./HIA');
const HPB = require('./HPB');
const AZV = require('./AZV');
const CD1 = require('./CD1');
const CDB = require('./CDB');
const CDD = require('./CDD');
const CDS = require('./CDS');
const CCT = require('./CCT');
const CCS = require('./CCS');
const XE3 = require('./XE3');
const XCT = require('./XCT');
const XG1 = require('./XG1');
const STA = require('./STA');
const VMK = require('./VMK');
const HAA = require('./HAA');
const HTD = require('./HTD');
const HPD = require('./HPD');
const HKD = require('./HKD');
const PTK = require('./PTK');
const HAC = require('./HAC');
const Z53 = require('./Z53');
const DKI = require('./DKI');
const C52 = require('./C52');
const C53 = require('./C53');
module.exports = {
INI,
HIA,
HPB,
Z53,
AZV,
CD1,
CDB,
CDD,
CDS,
CCT,
CCS,
XE3,
XCT,
XG1,
STA,
VMK,
HAA,
HTD,
HPD,
HKD,
PTK,
HAC,
DKI,
C52,
C53,
};

View File

@@ -0,0 +1,29 @@
'use strict';
const fs = require('fs');
module.exports = (pathToFile) => {
const path = pathToFile;
return {
write(data) {
return new Promise((resolve, reject) => {
fs.writeFile(path, data, { encoding: 'utf8' }, (error) => {
if (error) reject(error);
return resolve();
});
});
},
read() {
return new Promise((resolve, reject) => {
fs.readFile(path, { encoding: 'utf8' }, (error, data) => {
if (error) reject(error);
return resolve(data);
});
});
},
};
};

View File

@@ -0,0 +1,63 @@
'use strict';
const fs = require('fs');
const uuidv1 = require('uuid/v1');
const traceName = (uuid, label, type, ext = 'xml') => `${uuid}_${label}_${type}.${ext}`;
module.exports = dir => ({
traceData: '',
traceLabel: '',
lastTraceID: null,
connectToLastTrace: false,
label(str) {
this.traceLabel = str;
return this;
},
data(data) {
if (!data)
throw Error('No trace given to be persisted.');
this.traceData = data;
return this;
},
ofType(type) {
this.type = type;
return this;
},
new() {
this.connectToLastTrace = false;
return this;
},
connect() {
this.connectToLastTrace = true;
return this;
},
persist() {
if (!dir)
throw Error('No directory to save the traces to provided.');
this.lastTraceID = this.connectToLastTrace ? this.lastTraceID : uuidv1();
const name = traceName(this.lastTraceID, this.traceLabel, this.type);
const path = `${dir}/${name}`;
try {
fs.writeFileSync(path, this.traceData);
} catch (error) {
throw error;
}
},
});

46
lib/utils.js Normal file
View File

@@ -0,0 +1,46 @@
'use strict';
const prefixNumber = (n) => {
if (n < 10)
return `0${n}`;
return n.toString();
};
const date = {
getDateObject(d = Date.now()) {
const dateObject = new Date(d);
// eslint-disable-next-line no-restricted-globals
if (isNaN(dateObject))
throw new Error(`${d} is invalid date.`);
return dateObject;
},
toISODate(d = Date.now(), utc = true) {
const t = date.getDateObject(d);
if (utc)
return `${t.getUTCFullYear()}-${prefixNumber(t.getUTCMonth() + 1)}-${prefixNumber(t.getUTCDate())}`;
return `${t.getFullYear()}-${prefixNumber(t.getMonth() + 1)}-${prefixNumber(t.getDate())}`;
},
toISOTime(d = Date.now(), utc = true) {
const t = date.getDateObject(d);
if (utc)
return `${prefixNumber(t.getUTCHours())}:${prefixNumber(t.getUTCMinutes())}:${prefixNumber(t.getUTCSeconds())}`;
return `${prefixNumber(t.getHours())}:${prefixNumber(t.getMinutes())}:${prefixNumber(t.getSeconds())}`;
},
};
const dateRange = (start, end) => {
if (start && end)
return {
DateRange: {
Start: date.toISODate(start),
End: date.toISODate(end),
},
};
return {};
};
module.exports = {
dateRange,
date,
};

3866
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,36 +1,74 @@
{
"name": "node-ebics-client",
"version": "0.0.35",
"description": "Node.js ISO 20022 Compliant EBICS Client",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://github.com/eCollect/node-ebics-client"
},
"keywords": [
"EBICS",
"ISO20022",
"nodejs",
"api"
],
"author": "eCollect Sofia Tech Team",
"license": "MIT",
"dependencies": {
"bn.js": "^4.11.8",
"handlebars": "^4.0.11",
"js2xmlparser": "^3.0.0",
"moment": "^2.22.1",
"node-rsa": "^0.4.2",
"xml-c14n": "0.0.6",
"xmldom": "^0.1.27",
"xpath": "0.0.27"
},
"devDependencies": {
"eslint": "^4.19.1",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-plugin-import": "^2.12.0"
"name": "ebics-client",
"version": "0.1.4",
"description": "Node.js ISO 20022 Compliant EBICS Client",
"main": "index.js",
"files": [
"lib/**/*",
"templates/**/*"
],
"scripts": {
"lint": "eslint .",
"test": "nyc mocha test/**/*.js",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"version": "auto-changelog -p -t changelog-template.hbs && git add CHANGELOG.md"
},
"repository": {
"type": "git",
"url": "https://github.com/node-ebics/node-ebics-client"
},
"keywords": [
"EBICS",
"ISO20022",
"nodejs",
"api",
"banking"
],
"author": "eCollect Sofia Tech Team",
"contributors": [
{
"name": "Dimitar Nanov",
"url": "https://nanov.io",
"email": "dimitar@nanov.io"
},
{
"name": "Vladislav Hristov",
"url": "https://github.com/vladhristov",
"email": "vlad.s.ch@gmail.com"
},
{
"name": "Vasyl Stashuk",
"url": "https://github.com/vasyas"
},
{
"name": "Yago",
"url": "https://github.com/yagop"
},
{
"name": "Herman van Hazendonk",
"email": "github.com@herrie.org"
}
],
"license": "GPL-3.0-only",
"dependencies": {
"auto-changelog": "^1.16.2",
"handlebars": "^4.5.3",
"js2xmlparser": "^4.0.0",
"node-forge": "^0.9.1",
"request": "^2.88.0",
"uuid": "^3.3.3",
"xml-crypto": "^1.4.0",
"xmldom": "^0.1.27",
"xpath": "0.0.27"
},
"devDependencies": {
"chai": "^4.2.0",
"coveralls": "^3.0.9",
"eslint": "^6.7.2",
"eslint-config-ecollect-base": "^0.1.2",
"eslint-plugin-import": "^2.18.2",
"libxmljs": "^0.19.7",
"mocha": "^6.2.2",
"nyc": "^14.1.1"
}
}

164
templates/ini_en.hbs Normal file
View File

@@ -0,0 +1,164 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta charset="UTF-8" />
<title>EBICS ini</title>
</head>
<body>
<div>
<h2>EBICS-Initialization letter (INI)</h2>
<table>
<tr>
<td>Date</td>
<td>{{ today }}</td>
</tr>
<tr>
<td>Time</td>
<td>{{ now }}</td>
</tr>
<tr>
<td>Recipient</td>
<td>{{ bankName }}</td>
</tr>
<tr>
<td>User ID</td>
<td>{{ userId }}</td>
</tr>
<tr>
<td>Customer ID</td>
<td>{{ partnerId }}</td>
</tr>
</table>
<p>Public authentication key (A006)</p>
<p>Exponent ({{ keyExponentBits A006 }} Bit):</p>
<p>
<code>{{ keyExponent A006 }}</code>
</p>
<p>Modulus ({{ keyModulusBits A006 }} Bit):</p>
<p>
<code>{{ keyModulus A006 }}</code>
</p>
<p>SHA-256 Hash:</p>
<p>
<code>{{ sha256 A006 }}</code>
</p>
<p>I hereby confirm the above public key for my electronic signature.</p>
<br/>
<br/>
<br/>
<br/>
<table>
<tr>
<td>_________________________</td>
<td>_________________________</td>
<td>_________________________</td>
</tr>
<tr>
<td>City/Date</td>
<td>Name/Company</td>
<td>Signature</td>
</tr>
</table>
</div>
<div style="page-break-after:always"></div>
<h2>EBICS-Initialization letter (HIA) - Page 1/2</h2>
<table>
<tr>
<td>Date</td>
<td>{{ today }}</td>
</tr>
<tr>
<td>Time</td>
<td>{{ now }}</td>
</tr>
<tr>
<td>Recipient</td>
<td>{{ bankName }}</td>
</tr>
<tr>
<td>User ID</td>
<td>{{ userId }}</td>
</tr>
<tr>
<td>Customer ID</td>
<td>{{ partnerId }}</td>
</tr>
</table>
<div>
<p>Public Identification Key (X002)</p>
<p>Exponent ({{ keyExponentBits X002 }} Bit):</p>
<p>
<code>{{ keyExponent X002 }}</code>
</p>
<p>Modulus ({{ keyModulusBits X002 }} Bit):</p>
<p>
<code>{{ keyModulus X002 }}</code>
</p>
<p>SHA-256 Hash:</p>
<p>
<code>{{ sha256 X002 }}</code>
</p>
<p> Continuation on Page 2 ...</p>
<div style="page-break-after:always"></div>
<h2>EBICS-Initialization letter (HIA) - Page 2/2</h2>
<table>
<tr>
<td>Date</td>
<td>{{ today }}</td>
</tr>
<tr>
<td>Time</td>
<td>{{ now }}</td>
</tr>
<tr>
<td>Recipient</td>
<td>{{ bankName }}</td>
</tr>
<tr>
<td>User ID</td>
<td>{{ userId }}</td>
</tr>
<tr>
<td>Customer ID</td>
<td>{{ partnerId }}</td>
</tr>
</table>
</div>
<div>
<p>Public Encryption Key (E002)</p>
<p>Exponent ({{ keyExponentBits E002 }} Bit):</p>
<p>
<code>{{ keyExponent E002 }}</code>
</p>
<p>Modulus ({{ keyModulusBits E002 }} Bit):</p>
<p>
<code>{{ keyModulus E002 }}</code>
</p>
<p>SHA-256 Hash:</p>
<p>
<code>{{ sha256 E002 }}</code>
</p>
<p>I hereby confirm the above public keys for my EBICS access.</p>
<br/>
<br/>
<br/>
<br/>
<table>
<tr>
<td>_________________________</td>
<td>_________________________</td>
<td>_________________________</td>
</tr>
<tr>
<td>City/Date</td>
<td>Name/Company</td>
<td>Signature</td>
</tr>
</table>
</div>
</body>
</html>

21
test/fixtures/HPB_response.xml vendored Normal file
View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<ebicsKeyManagementResponse xmlns="urn:org:ebics:H004" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Revision="1" Version="H004" xsi:schemaLocation="urn:org:ebics:H004 ebics_keymgmt_response_H004.xsd">
<header authenticate="true">
<static/>
<mutable>
<ReturnCode>000000</ReturnCode>
<ReportText>[EBICS_OK] OK</ReportText>
</mutable>
</header>
<body>
<DataTransfer>
<DataEncryptionInfo authenticate="true">
<EncryptionPubKeyDigest Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" Version="E002">nuZyAjQoIvH4MwnrCWB82TC/vcNqigpqPtMXwO1Xk9U=</EncryptionPubKeyDigest>
<TransactionKey>QgfTQqhocARTcpcgySGxn5a+uBuFsHCX0oBINQQIfsgMRR+wEZ3zlXQMUm5GUwwOci2wjkRHJ3PoO4Yd0KzDNWiXFuX/9n00bpnNN5MsTCRgDqJRzZrA1jDxWRBVY/sv3l1gS/ERwyWqub456VZ7DYlXlRA8yp5lormoxftpIt6/Cf0ShmhS8qlUkLYSn/8ugQRl1KRTID5ZSXPjDYAIVZ0izVxblaBYXgs6RH9VvP4uKSn4CFYiFp+alo6EcxFNgMVUnfzaEbsPH/aX9Y3tbdWhATBZSxTT8iMoi2ARhz9VB711aavB8vgzcD6jH3ZtIm+sCb3UC3A/KehEn9GjIA==</TransactionKey>
</DataEncryptionInfo>
<OrderData>xlQf5zxlUo4LwjkoDsh926OISQwn6PhpxF3CkZI34EO84tfK25zLbqriXiqwDycl2YjYrQxbj8B4k1auNe86a+JigQOoBBCL8gvsoxO+NlziPNzf8sP99dQSp436q7KOhVm4gz2ZZT3D3O5FSqpOKsQ0fJku7D76beoxqxUT+hD0UxgsGLsbGgNLzHPalnFJctuaZtqQmo24Uk6hOwLrFJoQwUbPEffU5GGARWrZVxGQr5Zw4zrtLlZzP1RXk9JZFnhx00KvKiaoxAQXxXUC0BeDM97tIMEiaRNbO3ne+mHtK8vXzc305X5yR0B6ocwbFAmzGQ/e6Fyy6XPIGsbXoLjJGod6ayIU1SJGx/JRyOU4cLkjJZLUQMyCdJ6bA5uYf1AePUDvUA3tAOd6W0cC7DPAlwcrWhSZkPLIawequyjO39LTkEcmINEigd4i3sCrG4e1k303gyIKUg79kApl46onW2vruN3DwswunI10JGXxqex7ccvd2JvD6Pc8cAH1RMg8FIucYd//Joh1XK+QkRdIZMWKg4/lkoWmgVN7ssMmrtsKE2pNFAsIRfgxd8bB64sHZ3XKSyZKTk4LnWdJHZopP/xTs9a+/G3Kd/sIsy4W4sIKZCWU8NL11YOaqfwxY/rYNMaQzhAmawFfWvt74iisFG81Wv+Ijja/dPFmbk3Mup2Dfnlfu26MDblC8+aiaPHypmPuzREqGjW/9d2vtLQz0IXX3X6H4pFXneSjKlwXlDk1WGuiOvXH4gmXEpfWTX9rq+rUyskwwL8ssW5eeeyJ5W2LhVKQllOi/bvrAL5kyrfDMxvOuFIBLxBf//xE</OrderData>
</DataTransfer>
<ReturnCode authenticate="true">000000</ReturnCode>
<TimestampBankParameter authenticate="true">2016-08-10T16:16:17.512Z</TimestampBankParameter>
</body>
</ebicsKeyManagementResponse>

22
test/fixtures/HPB_response_data.xml vendored Normal file
View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<HPBResponseOrderData xmlns="urn:org:ebics:H004" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xsi:schemaLocation="urn:org:ebics:H004 ebics_orders_H004.xsd">
<AuthenticationPubKeyInfo>
<PubKeyValue>
<ds:RSAKeyValue>
<ds:Modulus>AJWVHQIfP0H1fr5Y7IjSlDmFksqQ+0E+CjzbEeE6r444LCuSXwbGKF6DJqguyX1qGYxjHRvVtdkNa+GNAtlZnmuPeLHPBUOs5Zx9J5JP4JZOcKd/wnRDIasTkg3NrtZ22tjOrWx26VuR6h7dUH2oJRnFDHmbXoCDMxkqJUNr/TM89p5slJ9Oj5+NAaOzm+7AlwbJ95EI/xc2jEfhp+GdF9CYdS/m2AZaAt79y6QDtBSDdAs0OHTgsOIjbjZkptBF/Gkip2sOordjsChRNLHLDcAOWbsg1NVMuhXs1b6+bCVLXQcGhFydYhqvrXB7pFS0++hlyzqGhbZK5cwEe/v8EJk=</ds:Modulus>
<ds:Exponent>AQAB</ds:Exponent>
</ds:RSAKeyValue>
</PubKeyValue>
<AuthenticationVersion>X002</AuthenticationVersion>
</AuthenticationPubKeyInfo>
<EncryptionPubKeyInfo>
<PubKeyValue>
<ds:RSAKeyValue>
<ds:Modulus>AJWVHQIfP0H1fr5Y7IjSlDmFksqQ+0E+CjzbEeE6r444LCuSXwbGKF6DJqguyX1qGYxjHRvVtdkNa+GNAtlZnmuPeLHPBUOs5Zx9J5JP4JZOcKd/wnRDIasTkg3NrtZ22tjOrWx26VuR6h7dUH2oJRnFDHmbXoCDMxkqJUNr/TM89p5slJ9Oj5+NAaOzm+7AlwbJ95EI/xc2jEfhp+GdF9CYdS/m2AZaAt79y6QDtBSDdAs0OHTgsOIjbjZkptBF/Gkip2sOordjsChRNLHLDcAOWbsg1NVMuhXs1b6+bCVLXQcGhFydYhqvrXB7pFS0++hlyzqGhbZK5cwEe/v8EJk=</ds:Modulus>
<ds:Exponent>AQAB</ds:Exponent>
</ds:RSAKeyValue>
</PubKeyValue>
<EncryptionVersion>E002</EncryptionVersion>
</EncryptionPubKeyInfo>
<HostID>CRSISOTB</HostID>
</HPBResponseOrderData>

13
test/fixtures/INI_reposne.xml vendored Normal file
View File

@@ -0,0 +1,13 @@
<h004:ebicsKeyManagementResponse xmlns:h004="urn:org:ebics:H004" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:org:ebics:H004 ebics_H004.xsd" Version="H004" Revision="1">
<h004:header authenticate="true">
<h004:static />
<h004:mutable>
<h004:OrderID>B004</h004:OrderID>
<h004:ReturnCode>000000</h004:ReturnCode>
<h004:ReportText>[EBICS_OK] OK</h004:ReportText>
</h004:mutable>
</h004:header>
<h004:body>
<h004:ReturnCode authenticate="true">000000</h004:ReturnCode>
</h004:body>
</h004:ebicsKeyManagementResponse>

164
test/fixtures/Letter_DE.html vendored Normal file
View File

@@ -0,0 +1,164 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta charset="UTF-8" />
<title>EBICS ini</title>
</head>
<body>
<div>
<h2>EBICS-Initialisierungsbrief (INI)</h2>
<table>
<tr>
<td>Datum</td>
<td>2019-11-20</td>
</tr>
<tr>
<td>Uhrzeit</td>
<td>14:14:03</td>
</tr>
<tr>
<td>Empfänger</td>
<td>Credit Suisse AG</td>
</tr>
<tr>
<td>User-ID</td>
<td>CRS04381</td>
</tr>
<tr>
<td>Kunden-ID</td>
<td>CRS04381</td>
</tr>
</table>
<p>Öffentlicher Schlüssel für die elektronische Unterschrift (A006)</p>
<p>Exponent (24 Bit):</p>
<p>
<code>010001</code>
</p>
<p>Modulus (2048 Bit):</p>
<p>
<code>AB E6 54 CA EF B8 AD 81 C2 79 74 9C 42 39 82 A7 A4 1C 76 C8 00 69 68 90 79 59 22 DA 6F 1B C6 12 DA B1 32 8F 97 57 D0 71 2F 09 90 63 96 CA 80 00 8B D3 B8 1A D9 D2 91 14 08 84 BA F7 CF 78 6D E6 40 74 7B FF 48 44 9D D6 DE 1D 9A F3 07 F4 48 AC A2 3C E2 2C 79 34 98 59 E3 48 64 EF 9E E2 C4 9C BC CE DA 1E 31 EC 4D 51 85 62 05 6B FC CB 8D B1 09 DF 17 3C 2C 68 D4 C7 3F 15 BF 4C A6 65 E4 D9 A3 CD 3E 3F 1F 20 16 5E A2 27 93 35 86 DE D7 A8 5D 8F E1 0C A5 02 AF C7 7B 51 9C D0 00 5C 2D 46 E8 42 D3 2F 8A 8C 31 C2 ED 61 01 4F B5 B2 E7 3E 11 18 8C D7 83 23 1E D5 24 BE A9 A4 5C 56 8C CC 52 75 E1 15 63 39 90 AB 36 CE 30 1D 3B 50 85 7D CE 7A 72 77 67 77 4C 5E 37 BE 95 31 91 A7 51 A7 BC C6 C6 BD 2B 85 3D 08 29 90 B1 F6 77 2F 23 9F FD 2A 03 03 04 E1 CB F6 66 6A D9 EC B5 66 60 89</code>
</p>
<p>Hash (SHA-256):</p>
<p>
<code>85 F0 4F 8B 97 6A F0 E2 FD 9F 21 2B 0B B3 A2 7A 97 B3 BB 57 B3 1B 55 14 F7 ED B5 CB CD E4 FE 79</code>
</p>
<p>Ich bestätige hiermit den obigen öffentlichen Schlüssel für meine elektronische Unterschrift.</p>
<br/>
<br/>
<br/>
<br/>
<table>
<tr>
<td>_________________________</td>
<td>_________________________</td>
<td>_________________________</td>
</tr>
<tr>
<td>Ort/Datum</td>
<td>Name/Firma</td>
<td>Unterschrift</td>
</tr>
</table>
</div>
<div style="page-break-after:always"></div>
<h2>EBICS-Initialisierungsbrief (HIA) - Seite 1/2</h2>
<table>
<tr>
<td>Datum</td>
<td>2019-11-20</td>
</tr>
<tr>
<td>Uhrzeit</td>
<td>14:14:03</td>
</tr>
<tr>
<td>Empfänger</td>
<td>Credit Suisse AG</td>
</tr>
<tr>
<td>User-ID</td>
<td>CRS04381</td>
</tr>
<tr>
<td>Kunden-ID</td>
<td>CRS04381</td>
</tr>
</table>
<div>
<p>Öffentlicher Authentifikationsschlüssel (X002)</p>
<p>Exponent (24 Bit):</p>
<p>
<code>010001</code>
</p>
<p>Modulus (2048 Bit):</p>
<p>
<code>8A ED FC 0F 2C 86 EA E3 07 D9 0D 75 DD E6 25 4F 20 40 41 43 D6 22 C0 59 A9 38 F6 54 42 74 ED 5C 38 C4 6C 63 F2 0F A2 65 85 D9 58 1A 1E F5 1E C4 9E E3 66 5B 67 6B 37 B0 FA BD 8C FE 12 D3 F0 3A C8 23 22 56 3F 30 B0 FB 70 C5 7B 17 41 15 A8 EE 54 F3 1F AF DE 13 7E 96 38 AF 9B 69 28 22 1F 9A 7C C4 C6 1D 35 59 66 40 81 7F D7 48 DA B9 51 EF A9 0C 96 C5 7A 55 25 66 A5 63 B6 4A 45 F0 97 0F 56 60 18 5B CE 08 A2 7D 87 64 18 A2 4A E1 B3 8E 75 24 3F FC AC 23 A5 33 BF 0C 41 E3 96 B5 D2 54 CA 37 07 40 A9 E3 2B 01 17 A6 21 AE 07 1E 52 0A 00 E1 95 F3 BE BC 19 EA E9 90 48 17 38 60 DE 68 BD 85 36 37 59 14 E0 BC 07 C5 B8 50 0A 87 87 90 13 91 6C 4F 03 2B 40 27 14 2B 7F A9 D7 71 12 57 E5 A9 72 CE 16 B7 7B 5E 7D A2 52 D6 CE 9D 39 DA 44 C0 41 EF 02 37 7B 93 98 CC 35 D8 06 D1 79 57</code>
</p>
<p>Hash (SHA-256):</p>
<p>
<code>F8 3B D7 EB 6F 2B 48 4B 2B 0A BD CA C7 EF F8 56 11 D1 90 32 D6 CC 67 3B 8A 3E 74 F5 F8 B4 E1 AA</code>
</p>
<p> Fortsetzung auf Seite 2 ...</p>
<div style="page-break-after:always"></div>
<h2>EBICS-Initialisierungsbrief (HIA) - Seite 2/2</h2>
<table>
<tr>
<td>Datum</td>
<td>2019-11-20</td>
</tr>
<tr>
<td>Uhrzeit</td>
<td>14:14:03</td>
</tr>
<tr>
<td>Empfänger</td>
<td>Credit Suisse AG</td>
</tr>
<tr>
<td>User-ID</td>
<td>CRS04381</td>
</tr>
<tr>
<td>Kunden-ID</td>
<td>CRS04381</td>
</tr>
</table>
</div>
<div>
<p>Öffentlicher Verschlüsselungsschlüssel (E002)</p>
<p>Exponent (24 Bit):</p>
<p>
<code>010001</code>
</p>
<p>Modulus (2048 Bit):</p>
<p>
<code>93 10 E9 40 2A B1 77 DB 31 C7 EB 13 5A E6 21 FD 6B 18 2B 74 40 67 B1 C9 6D B6 09 4A 1D CD F0 A7 DC 51 A4 53 EF AE 14 0D 9E A0 FB E4 EE D0 2E CF 6C 4C 5E 99 83 F3 A3 8E C3 FE F5 C1 DA E2 E6 52 96 A1 5B 9C 10 2D D1 F1 A2 0F 8E 3E 5D 81 55 DF 19 11 5B 5E 62 DB 95 56 0E 19 4C EF F3 87 50 EB 5F B7 42 3C 5D CB 47 51 81 E7 3F 39 6A 4A CA 6E 29 55 2B 22 14 FE 24 DA 5D DB 0C 99 D2 61 7A FB 40 B7 61 B8 82 89 BE 59 4C 64 92 9A F2 5E 82 62 3A 98 11 61 A3 77 0F 13 38 5D FD 13 BD 9C 4A 96 F9 34 8A 3B 99 4D 25 7B 1B A8 3F 90 F7 6F 9D 09 74 DB 4C B7 27 A3 0B EC 7C 05 E5 F3 1C F7 C2 59 0D 3A 57 86 D5 AD AF 1E CC 84 9F B8 4C DF 8A 35 22 CF D5 B8 12 2B 44 E4 12 BE 26 F7 D7 31 31 31 BB 3D 3C 49 7E C6 AF 44 4D 00 1F FC 04 84 2F 10 8B 2C 78 15 0F 22 46 70 78 2B 25 56 16 36 FB 9F</code>
</p>
<p>Hash (SHA-256):</p>
<p>
<code>9E E6 72 02 34 28 22 F1 F8 33 09 EB 09 60 7C D9 30 BF BD C3 6A 8A 0A 6A 3E D3 17 C0 ED 57 93 D5</code>
</p>
<p>Ich bestätige hiermit die obigen öffentlichen Schlüssel für meinen EBICS-Zugang.</p>
<br/>
<br/>
<br/>
<br/>
<table>
<tr>
<td>_________________________</td>
<td>_________________________</td>
<td>_________________________</td>
</tr>
<tr>
<td>Ort/Datum</td>
<td>Name/Firma</td>
<td>Unterschrift</td>
</tr>
</table>
</div>
</body>
</html>

40
test/fixtures/STA_response_part1.xml vendored Normal file
View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<ebicsResponse xmlns="urn:org:ebics:H004" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:org:ebics:H004 http://www.ebics.org/H004/ebics_response_H004.xsd" Version="H004" Revision="1">
<header authenticate="true">
<static>
<TransactionID>ECD6F062AAEDFA77250526A68CBEC549</TransactionID>
<NumSegments>2</NumSegments>
</static>
<mutable>
<TransactionPhase>Initialisation</TransactionPhase>
<SegmentNumber lastSegment="false">1</SegmentNumber>
<ReturnCode>000000</ReturnCode>
<ReportText>[EBICS_OK] OK</ReportText>
</mutable>
</header>
<AuthSignature>
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#xpointer(//*[@authenticate='true'])">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>OnY4VczV+1XfplpMS/SCKFWn+FDMge40X8TJ9pA6HEI=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>Qiz8clr/Ym+wLpB8CzTuxKP5rgIR2P4SaX8O4gGZx0Di8Lzd4hsBO3koqIuX11NRZo0RYA5E/IKQCoYLepkcM4QHvLT37YidbrXsTZe6cml/yF8OdyRAcd/9U7AoA9GKENFk4lnBFfZYmM4ADK6wVOCE/ABaOk0NeCDDbxrKA75tfTxwxTvQBfUOHZ87LrZxuQf//L9pzmtI07/7GbsQX8ZceQq40SpBFZn/oRvT/Q38Qu2ZO59MZexJn+BTzlhpfv9fQGF/sgXkdhUkPTEW3nL4j7ehasq3CtySGO2RnEppohEvJ4m07RDtEY3OWyAAZoPFVpvnECM6pOLbIuR40w==</ds:SignatureValue>
</AuthSignature>
<body>
<DataTransfer>
<DataEncryptionInfo authenticate="true">
<EncryptionPubKeyDigest Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" Version="E002">IUW05JB5cuCzRYYa4kIAZ2ehYVrLI3lKvxZvo1uP+ug=</EncryptionPubKeyDigest>
<TransactionKey>CPdd6ODXrCxHWpYrwS/5a3NIZ1R2SDscHHJSONc2I7g8ara7qv5Ra7H03W+336DPQLZQxH+EBRWdE2FQ1vrVTcypK6fkFarmO1dEtQt6Ka89AozC7aQMyp4yGl+zlF+XvFkJXZbRXgoEOPsgcG0xM6nKLXnr1ThYRFOoAasigLDGwqxliyhoR94F9dL7YB8Le/uZeRj78mNp4smkE5ZKKyPhwVx+0IW78YuVCSC2c2qbsMmm4evAvycdpLfuuoG4KVlNMIlvkKeBfMZFELcmeKKm85D2zPYkZ7cqG2IfAAp5qdS6K8P7RY4gtjUhOtP5mi8bZt/ZcjioUmIR9dAMnA==</TransactionKey>
</DataEncryptionInfo>
<OrderData>lJ/v1HvOYAZKTPkxULH4mOcUy01+EaAsxLzCPhtieAo9m5AtU5URdyBVfmclHbtWe3ELb02lR37bChynq0Esqpljxx7CiB+kl/bVs7XkNQQmtGPlh08O7ApJ7AHdKPFFvE+SM2OWpSwUrqJGtmyaU6s+RM66o/3Q0wvKi4y3+Q2jm8nkiWZpPfNt52g4ReE9Ge86o+L+EBOBgZm634L5Z/i6pq908mXBAklPdk1ERdEgWONaOeYYmB70FToplR1gINJEF2CnfAtD/wV+DuQFPDth+EWHqhe3a4xO0d9eJ55ou2QCYOPUC2g/qz1sp7GR74W7P5IWJdmnUEuPFsKJ6OvM+1V8S06jZGDa/QhUGDK8F3G/TMP09W1MsrlQTRr3YQ5H43+pMY7I6Jysze+X9OKZZTTpv4GmYm1USjmraGyMuPF3j9hrlmpttK+1gg2WzytCJO44YsZp9ckls8JNK4bT5uwTuomKZoRgmiIOTcMh4VP7V28TWj2XdF+T6rBFILTvQyXMOzvbe1kLwg2O31JDBdJ8X1fHCYJxKsIrYCSDPz4sha8h7QmHrCSFmjoXb+LUElnbLXZVRkY5/Dc73EM6Es3wAgjwSJK2asRqdqU0PhbS5qpIlgYKsNcb+Wes</OrderData>
</DataTransfer>
<ReturnCode authenticate="true">000000</ReturnCode>
<TimestampBankParameter authenticate="true">2013-12-09T15:14:30.000Z</TimestampBankParameter>
</body>
</ebicsResponse>

Some files were not shown because too many files have changed in this diff Show More