reorder file structure

This commit is contained in:
Vladislav Hristov
2018-06-11 15:25:07 +03:00
parent ff9a3a16b4
commit 10111878fa
11 changed files with 60 additions and 118 deletions

View File

@@ -0,0 +1,15 @@
'use strict';
const InitializationSerializer = require('./serializers/InitializationSerializer');
const StatusSerializer = require('./serializers/StatusSerializer');
const PaymentSerializer = require('./serializers/PaymentSerializer');
module.exports = class OrderSerializer {
static serialize(order) {
if (order.type === 'ini') return new InitializationSerializer(order);
if (order.type === 'payment') return new PaymentSerializer(order);
if (order.type === 'status') return new StatusSerializer(order);
throw Error('Incorect order type. Available types: ini, status, payment, statement');
}
};

View File

@@ -0,0 +1,122 @@
'use strict';
const zlib = require('zlib');
const crypto = require('crypto');
const BN = require('bn.js');
const Crypto = require('../../crypto/Crypto');
const { DOMParser, XMLSerializer } = require('xmldom');
const xpath = require('xpath');
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 = class Response {
constructor(data, keys) {
this.keys = keys;
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;
}
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).toString();
}
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 : '';
}
returnCode() {
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 : '';
}
reportText() {
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 : '';
}
bankKeys() {
const orderData = this.orderData();
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 = new BN(Buffer.from(modulus, 'base64'), 2).toBuffer();
const exp = new BN(Buffer.from(exponent, 'base64')).toNumber();
bankKeys[`bank${type}`] = { mod, exp };
// const bank = new NodeRSA();
// bank.importKey({ n: mod, e: exp }, 'components-public');
// this.keys[`${this.hostId}.${type}`] = new Key(bank);
}
return bankKeys;
}
toXML() {
return new XMLSerializer().serializeToString(this.doc);
}
};

View File

@@ -0,0 +1,67 @@
'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;
module.exports = class Signer {
/**
* Contructor.
*
* @param {Keys} keys
* @param {String} data
*/
constructor(data, keys) {
/**
* Keys to operate with
*
* @type {Keys}
*/
this.keys = keys;
/**
* Request data - generated xml
*
* @type {String}
*/
this.doc = new DOMParser().parseFromString(data, 'text/xml');
}
digest() {
// get the xml node, where the digested value is supposed to be
const nodeDigestValue = this.doc.getElementsByTagName('ds:DigestValue')[0];
// canonicalize the node that has authenticate='true' attribute
const contentToDigest = xpath.select("//*[@authenticate='true']", this.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 this;
}
sign() {
const nodeSignatureValue = this.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', this.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(this.keys.x(), contentToSign); // this.keys.x().key.sign(contentToSign, 'base64');
}
return this;
}
toXML() {
return new XMLSerializer().serializeToString(this.doc);
}
};

View File

@@ -0,0 +1,81 @@
'use strict';
const js2xmlparser = require('js2xmlparser');
const xmlOptions = {
declaration: {
include: true,
encoding: 'utf-8',
},
format: {
doubleQuotes: true,
indent: '',
newline: '',
pretty: true,
},
};
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': {},
});
module.exports = class GenericSerializer {
constructor(orderBuilder) {
this._order = orderBuilder;
this._orderDetails = orderBuilder.orderDetails;
this._hostId = orderBuilder.hostId;
this._partnerId = orderBuilder.partnerId;
this._userId = orderBuilder.userId;
this._keys = orderBuilder.keys;
this._transactionId = orderBuilder.transactionId;
this._rootName = orderBuilder.root.nodeName;
this._rootAttributes = orderBuilder.root.nodeAttributes;
this._xmlOptions = xmlOptions;
this._xml = {};
}
static authSignature() {
return authSignature;
}
get keys() {
return this._keys;
}
toXML() {
return js2xmlparser.parse(this._rootName, this._xml, this._xmlOptions);
}
};

View File

@@ -0,0 +1,128 @@
'use strict';
const zlib = require('zlib');
const js2xmlparser = require('js2xmlparser');
const consts = require('../../../consts');
const Crypto = require('../../../crypto/Crypto');
const GenericSerializer = require('./GenericSerializer');
module.exports = class InitializationSerializer extends GenericSerializer {
constructor(order) {
super(order);
this._xml = {
'@': this._rootAttributes,
header: {
'@': { authenticate: true },
static: {
HostID: this._hostId,
Nonce: Crypto.nonce(),
Timestamp: Crypto.timestamp(),
PartnerID: this._partnerId,
UserID: this._userId,
Product: {
'@': { Language: 'en' },
'#': consts.productString,
},
OrderDetails: this._orderDetails,
SecurityMedium: '0000',
},
mutable: {},
},
};
if (this._isINI() || this._isHIA()) {
delete this._xml.header.static.Nonce;
delete this._xml.header.static.Timestamp;
this._xml.body = {
DataTransfer: {
OrderData: this.orderData(),
},
};
} else {
this._rootName = 'ebicsNoPubKeyDigestsRequest';
this._xml.AuthSignature = GenericSerializer.authSignature();
this._xml.body = {};
}
}
orderData() {
if (this._isINI()) return this._iniKeySignature();
if (this._isHIA()) return this._hiaOrderData();
return '';
}
_iniKeySignature() {
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._keys.a().n(), 'HEX').toString('base64'),
'ds:Exponent': this._keys.a().e().toString('base64'),
},
TimeStamp: Crypto.timestamp(),
},
SignatureVersion: 'A006',
},
PartnerID: this._partnerId,
UserID: this._userId,
};
const signature = js2xmlparser.parse('SignaturePubKeyOrderData', xmlOrderData, this._xmlOptions);
return Buffer.from(zlib.deflateSync(signature)).toString('base64');
}
_hiaOrderData() {
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._keys.x().n(), 'HEX').toString('base64'),
'ds:Exponent': this._keys.x().e().toString('base64'),
},
},
AuthenticationVersion: 'X002',
},
EncryptionPubKeyInfo: {
PubKeyValue: {
'ds:RSAKeyValue': {
'ds:Modulus': Buffer.from(this.keys.e().n(), 'HEX').toString('base64'),
'ds:Exponent': this._keys.e().e().toString('base64'),
},
},
EncryptionVersion: 'E002',
},
PartnerID: this._partnerId,
UserID: this._userId,
};
const order = js2xmlparser.parse('HIARequestOrderData', xmlOrderData, this._xmlOptions);
return Buffer.from(zlib.deflateSync(order)).toString('base64');
}
_isINI() {
return this._orderDetails.OrderType.toUpperCase() === 'INI';
}
_isHIA() {
return this._orderDetails.OrderType.toUpperCase() === 'HIA';
}
_isHPB() {
return this._orderDetails.OrderType.toUpperCase() === 'HPB';
}
};

View File

@@ -0,0 +1,131 @@
'use strict';
const zlib = require('zlib');
const crypto = require('crypto');
const js2xmlparser = require('js2xmlparser');
const consts = require('../../../consts');
const Crypto = require('../../../crypto/Crypto');
const GenericSerializer = require('./GenericSerializer');
module.exports = class PaymentSerializer extends GenericSerializer {
constructor(order) {
super(order);
this._transactionKey = order.transactionKey;
this._xml = {
'@': this._rootAttributes,
header: {
'@': { authenticate: true },
static: {
HostID: this._hostId,
Nonce: Crypto.nonce(),
Timestamp: Crypto.timestamp(),
PartnerID: this._partnerId,
UserID: this._userId,
Product: {
'@': { Language: 'en' },
'#': consts.productString,
},
OrderDetails: this._orderDetails,
BankPubKeyDigests: {
Authentication: {
'@': { Version: 'X002', Algorithm: 'http://www.w3.org/2001/04/xmlenc#sha256' },
'#': Crypto.digestPublicKey(this._keys.bankX()),
},
Encryption: {
'@': { Version: 'E002', Algorithm: 'http://www.w3.org/2001/04/xmlenc#sha256' },
'#': Crypto.digestPublicKey(this._keys.bankE()),
},
},
SecurityMedium: '0000',
NumSegments: 1,
},
mutable: {
TransactionPhase: 'Initialisation',
},
},
AuthSignature: GenericSerializer.authSignature(),
body: {
DataTransfer: {
DataEncryptionInfo: {
'@': { authenticate: true },
EncryptionPubKeyDigest: {
'@': { Version: 'E002', Algorithm: 'http://www.w3.org/2001/04/xmlenc#sha256' },
'#': Crypto.digestPublicKey(this._keys.bankE()),
},
TransactionKey: Crypto.publicEncrypt(this._keys.bankE(), this._transactionKey).toString('base64'),
},
SignatureData: {
'@': { authenticate: true },
'#': this.encryptedOrderSignature(),
},
},
},
};
if (order.hasTransactionId()) {
this._xml.header = {
'@': { authenticate: true },
static: {
HostID: this._hostId,
TransactionID: this._transactionId,
},
mutable: {
TransactionPhase: 'Transfer',
SegmentNumber: {
'@': { lastSegment: true },
'#': 1,
},
},
};
this._xml.body = {
DataTransfer: {
OrderData: this.encryptedOrderData(),
},
};
}
}
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.digestWithHash(this._order.document.replace(/\n|\r/g, ''));
return Crypto.sign(this._keys.a(), digested);
}
encryptedOrderData() {
const dst = zlib.deflateSync(this._order.document.replace(/\n|\r/g, ''));
const cipher = crypto.createCipheriv('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);
return Buffer.concat([cipher.update(Crypto.pad(dst)), cipher.final()]).toString('base64');
}
encryptedOrderSignature() {
const dst = zlib.deflateSync(this.orderSignature());
const cipher = crypto.createCipheriv('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);
return Buffer.concat([cipher.update(Crypto.pad(dst)), cipher.final()]).toString('base64');
}
};

View File

@@ -0,0 +1,67 @@
'use strict';
const consts = require('../../../consts');
const Crypto = require('../../../crypto/Crypto');
const GenericSerializer = require('./GenericSerializer');
module.exports = class StatusSerializer extends GenericSerializer {
constructor(order) {
super(order);
this._xml = {
'@': this._rootAttributes,
header: {
'@': { authenticate: true },
static: {
HostID: this._hostId,
Nonce: Crypto.nonce(),
Timestamp: Crypto.timestamp(),
PartnerID: this._partnerId,
UserID: this._userId,
Product: {
'@': { Language: 'en' },
'#': consts.productString,
},
OrderDetails: this._orderDetails,
BankPubKeyDigests: {
Authentication: {
'@': { Version: 'X002', Algorithm: 'http://www.w3.org/2001/04/xmlenc#sha256' },
'#': Crypto.digestPublicKey(this._keys.bankX()),
},
Encryption: {
'@': { Version: 'E002', Algorithm: 'http://www.w3.org/2001/04/xmlenc#sha256' },
'#': Crypto.digestPublicKey(this._keys.bankE()),
},
},
SecurityMedium: '0000',
},
mutable: {
TransactionPhase: 'Initialisation',
},
},
AuthSignature: GenericSerializer.authSignature(),
body: {},
};
if (order.hasTransactionId()) {
this._xml.header = {
'@': { authenticate: true },
static: {
HostID: this._hostId,
TransactionID: this._transactionId,
},
mutable: {
TransactionPhase: 'Receipt',
},
};
this._xml.body = {
TransferReceipt: {
'@': { authenticate: true },
ReceiptCode: 0,
},
};
}
}
};