mirror of
https://github.com/node-ebics/node-ebics-client.git
synced 2024-11-23 22:52:08 +00:00
initial commit
This commit is contained in:
parent
cd37de3895
commit
1f947ff148
32
.editorconfig
Normal file
32
.editorconfig
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# @see editorconfig.org
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.json]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.js]
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.tag]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.yml]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# Trailing whitespace is significant in markdown files.
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
max_line_length = 80
|
||||||
|
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
6
.eslintignore
Normal file
6
.eslintignore
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
node_modules/
|
||||||
|
build/
|
||||||
|
static/
|
||||||
|
config/
|
||||||
|
src/vue-router-custom-components
|
||||||
|
src/directives/clickAway
|
34
.eslintrc
Normal file
34
.eslintrc
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
npm-debug.log
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
*.local.json5
|
||||||
|
yarn.lock
|
||||||
|
/project.sublime-workspace
|
||||||
|
/public/css/style.css.map
|
||||||
|
/.idea
|
||||||
|
/.vscode
|
||||||
|
*.pid
|
||||||
|
/coverage
|
||||||
|
package-lock.json
|
||||||
|
*.key
|
||||||
|
*.html
|
20
README.md
Normal file
20
README.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# node-ebics-client v0.0.3
|
||||||
|
---
|
||||||
|
|
||||||
|
Pure node.js ( >=8 ) implementation of the [EBICS](https://en.wikipedia.org/wiki/Electronic_Banking_Internet_Communication_Standard) (Electronic Banking Internet Communication).
|
||||||
|
|
||||||
|
The client is aimed to be 100% ISO 20022 complient, and supports complete initilizations process ( INI, HIA, HPB orders ) and HTML letter generation.
|
||||||
|
|
||||||
|
## Supported Banks
|
||||||
|
The client is tested and verified to work with the following banks:
|
||||||
|
* Credit Suisse
|
||||||
|
* Zürcher Kantonalbank
|
||||||
|
* Raiffeisenbank
|
||||||
|
|
||||||
|
|
||||||
|
## Inspiration
|
||||||
|
|
||||||
|
A lot of the concepts in this library are inspired from the [EPICS](https://github.com/railslove/epics) library.
|
||||||
|
|
||||||
|
## Copyright
|
||||||
|
Copyright (c) 2017 eCollect.
|
4
index.js
Normal file
4
index.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Client = require('./lib/Client');
|
||||||
|
module.exports = Client;
|
66
lib/BankLetter.js
Normal file
66
lib/BankLetter.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const moment = require('moment');
|
||||||
|
const handlebars = require('handlebars');
|
||||||
|
const BN = require("bn.js");
|
||||||
|
|
||||||
|
module.exports = class BankLetter {
|
||||||
|
constructor(client, bankName) {
|
||||||
|
this.client = client;
|
||||||
|
this.bankName = bankName;
|
||||||
|
this.pathToTemplate = './app/ebics/ini.hbs';
|
||||||
|
};
|
||||||
|
|
||||||
|
_registerHelpers() {
|
||||||
|
handlebars.registerHelper("today", () => {
|
||||||
|
return moment().format('DD.MM.YYYY');
|
||||||
|
});
|
||||||
|
|
||||||
|
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(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 data = {
|
||||||
|
bankName : this.bankName,
|
||||||
|
userId : this.client.userId,
|
||||||
|
partnerId: this.client.partnerId,
|
||||||
|
A006 : this.client.a(),
|
||||||
|
X002 : this.client.x(),
|
||||||
|
E002 : this.client.e(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return templ(data);
|
||||||
|
}
|
||||||
|
}
|
233
lib/Client.js
Normal file
233
lib/Client.js
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require("fs");
|
||||||
|
const crypto = require("crypto");
|
||||||
|
const $request = require("request");
|
||||||
|
|
||||||
|
const BN = require('bn.js');
|
||||||
|
const xpath = require("xpath");
|
||||||
|
const NodeRSA = require("node-rsa");
|
||||||
|
|
||||||
|
|
||||||
|
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() : {};
|
||||||
|
};
|
||||||
|
|
||||||
|
a() {
|
||||||
|
return this.keys["A006"];
|
||||||
|
};
|
||||||
|
|
||||||
|
e() {
|
||||||
|
return this.keys["E002"];
|
||||||
|
};
|
||||||
|
|
||||||
|
x() {
|
||||||
|
return this.keys["X002"];
|
||||||
|
}
|
||||||
|
|
||||||
|
bankX() {
|
||||||
|
return this.keys[`${this.hostId}.X002`];
|
||||||
|
}
|
||||||
|
|
||||||
|
bankE() {
|
||||||
|
return this.keys[`${this.hostId}.E002`];
|
||||||
|
}
|
||||||
|
|
||||||
|
encrypt(data) {
|
||||||
|
const cipher = crypto.createCipher(this.encryptAlgorithm, this.passphrase);
|
||||||
|
const encrypted = cipher.update(data, 'utf8', 'hex') + cipher.final('hex');
|
||||||
|
|
||||||
|
return Buffer.from(encrypted).toString('base64');
|
||||||
|
};
|
||||||
|
|
||||||
|
decrypt(data) {
|
||||||
|
data = (new Buffer(data, 'base64')).toString();
|
||||||
|
|
||||||
|
const decipher = crypto.createDecipher(this.encryptAlgorithm, this.passphrase);
|
||||||
|
const decrypted = decipher.update(data, 'hex', 'utf8') + decipher.final('utf8');
|
||||||
|
|
||||||
|
return decrypted;
|
||||||
|
};
|
||||||
|
|
||||||
|
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());
|
||||||
|
console.log("Data written to file");
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
saveKeys(path) {
|
||||||
|
const data = {};
|
||||||
|
|
||||||
|
for (let key in this.keys) {
|
||||||
|
data[key] = this.encrypt(this.keys[key].toPem());
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(path, JSON.stringify(data));
|
||||||
|
console.log("Data written to file");
|
||||||
|
} catch(error) {
|
||||||
|
console.log(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
|
||||||
|
|
||||||
|
order.transactionId = res.transactionId();
|
||||||
|
|
||||||
|
if (res.isSegmented() && res.isLastSegment()) {
|
||||||
|
const receipt = await this.ebicsRequest(order.toReceiptXML());
|
||||||
|
|
||||||
|
const receiptXML = order.toReceiptXML(); // keep this for debugging purposes
|
||||||
|
const rX = receipt.toXML(); // keep this for debugging purposes
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.orderData();
|
||||||
|
};
|
||||||
|
|
||||||
|
async downloadAndUnzip(order) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ebicsRequest(order) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const bbb = XMLSign.go(this, order);
|
||||||
|
$request.post({
|
||||||
|
url : this.url,
|
||||||
|
body : bbb,
|
||||||
|
headers: { 'Content-Type': 'text/xml' }
|
||||||
|
}, (err, res, data) => {
|
||||||
|
const b = data; // keep this for debugging purposes
|
||||||
|
const r = ParseResponse.go(this, data); // keep this for debugging purposes
|
||||||
|
const rXML = r.toXML(); // keep this for debugging purposes
|
||||||
|
|
||||||
|
return err ? reject(err): resolve(ParseResponse.go(this, data));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
async INI() {
|
||||||
|
return this.ebicsRequest((new EBICSINI(this)).toXML());
|
||||||
|
};
|
||||||
|
|
||||||
|
async HIA() {
|
||||||
|
return this.ebicsRequest((new EBICSHIA(this)).toXML());
|
||||||
|
};
|
||||||
|
|
||||||
|
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 [this.bankX(), this.bankE()];
|
||||||
|
};
|
||||||
|
|
||||||
|
HKD() {
|
||||||
|
return this.download(new EBICSHKD(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
HAA() {
|
||||||
|
return this.download(new EBICSHAA(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
HTD() {
|
||||||
|
return this.download(new EBICSHTD(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
HAC(from = null, to = null) {
|
||||||
|
return this.download(new EBICSHAC(this, from, to));
|
||||||
|
};
|
||||||
|
|
||||||
|
C52(from, to) {
|
||||||
|
return this.downloadAndUnzip(new EBICSC52(this, from, to));
|
||||||
|
}
|
||||||
|
};
|
38
lib/Key.js
Normal file
38
lib/Key.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
const NodeRSA = require("node-rsa");
|
||||||
|
const BN = require('bn.js');
|
||||||
|
|
||||||
|
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(str) {
|
||||||
|
return this.key.encrypt(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
};
|
57
lib/Response.js
Normal file
57
lib/Response.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
'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);
|
||||||
|
}
|
||||||
|
};
|
103
lib/Signer.js
Normal file
103
lib/Signer.js
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
'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);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
12
lib/consts.js
Normal file
12
lib/consts.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const packageJson = require('../package.json');
|
||||||
|
|
||||||
|
const name = 'eCollect Node Ebics Client';
|
||||||
|
const version = packageJson.version;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name,
|
||||||
|
version,
|
||||||
|
productString: `${name} ${version}`,
|
||||||
|
};
|
164
lib/ini.hbs
Normal file
164
lib/ini.hbs
Normal 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>{{ today }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Uhrzeit</td>
|
||||||
|
<td>{{ now }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Empfänger</td>
|
||||||
|
<td>{{ bankName }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>User-ID</td>
|
||||||
|
<td>{{ userId }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Kunden-ID</td>
|
||||||
|
<td>{{ partnerId }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<p>Öffentlicher Schlüssel für die elektronische Unterschrift (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>Hash (SHA-256):</p>
|
||||||
|
<p>
|
||||||
|
<code>{{ sha256 A006 }}</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>{{ today }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Uhrzeit</td>
|
||||||
|
<td>{{ now }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Empfänger</td>
|
||||||
|
<td>{{ bankName }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>User-ID</td>
|
||||||
|
<td>{{ userId }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Kunden-ID</td>
|
||||||
|
<td>{{ partnerId }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div>
|
||||||
|
<p>Öffentlicher Authentifikationsschlüssel (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>Hash (SHA-256):</p>
|
||||||
|
<p>
|
||||||
|
<code>{{ sha256 X002 }}</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>{{ today }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Uhrzeit</td>
|
||||||
|
<td>{{ now }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Empfänger</td>
|
||||||
|
<td>{{ bankName }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>User-ID</td>
|
||||||
|
<td>{{ userId }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Kunden-ID</td>
|
||||||
|
<td>{{ partnerId }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>Öffentlicher Verschlüsselungsschlüssel (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>Hash (SHA-256):</p>
|
||||||
|
<p>
|
||||||
|
<code>{{ sha256 E002 }}</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>
|
22
lib/middleware/ParseResponse.js
Normal file
22
lib/middleware/ParseResponse.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
'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;
|
||||||
|
}
|
||||||
|
};
|
22
lib/middleware/XMLSign.js
Normal file
22
lib/middleware/XMLSign.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
'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;
|
||||||
|
}
|
||||||
|
};
|
50
lib/orders/C52.js
Normal file
50
lib/orders/C52.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
'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"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
143
lib/orders/GenericOrder.js
Normal file
143
lib/orders/GenericOrder.js
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
'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: '',
|
||||||
|
// indent: "\t",
|
||||||
|
// newline: "\r\n",
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
toXML() {
|
||||||
|
return js2xmlparser.parse(this.root(), this._schema, this.xmlOptions);
|
||||||
|
}
|
||||||
|
};
|
63
lib/orders/GenericUploadOrder.js
Normal file
63
lib/orders/GenericUploadOrder.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
'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: Buffer.from(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).digest();
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
const encrypted = cipher.update(dst) + cipher.final();
|
||||||
|
|
||||||
|
return Buffer.from(encrypted).toString('base64');
|
||||||
|
};
|
||||||
|
};
|
43
lib/orders/HAA.js
Normal file
43
lib/orders/HAA.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
'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"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
54
lib/orders/HAC.js
Normal file
54
lib/orders/HAC.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
'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;
|
||||||
|
}
|
||||||
|
};
|
85
lib/orders/HIA.js
Normal file
85
lib/orders/HIA.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
'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);
|
||||||
|
};
|
||||||
|
};
|
43
lib/orders/HKD.js
Normal file
43
lib/orders/HKD.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
'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"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
34
lib/orders/HPB.js
Normal file
34
lib/orders/HPB.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
'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";
|
||||||
|
};
|
||||||
|
};
|
43
lib/orders/HTD.js
Normal file
43
lib/orders/HTD.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
'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"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
77
lib/orders/INI.js
Normal file
77
lib/orders/INI.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
'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);
|
||||||
|
}
|
||||||
|
};
|
36
package.json
Normal file
36
package.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "node-ebics-client",
|
||||||
|
"version": "0.0.3",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user