mirror of
				https://github.com/node-ebics/node-ebics-client.git
				synced 2025-10-31 03:27:05 +00:00 
			
		
		
		
	code optimization
This commit is contained in:
		
							
								
								
									
										12
									
								
								index.js
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								index.js
									
									
									
									
									
								
							| @@ -1,5 +1,15 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const Client = require('./lib/Client'); | ||||
| const OrderBuilder = require('./lib/OrderBuilder'); | ||||
| const ISO20022Builder = require('./lib/ISO20022OrderBuilder'); | ||||
| const keysManager = require('./lib/keymanagers/keysManager'); | ||||
| const fsKeysStorage = require('./lib/keymanagers/fsKeysStorage'); | ||||
|  | ||||
| module.exports = Client; | ||||
| module.exports = { | ||||
| 	Client, | ||||
| 	OrderBuilder, | ||||
| 	ISO20022Builder, | ||||
| 	keysManager, | ||||
| 	fsKeysStorage, | ||||
| }; | ||||
|   | ||||
| @@ -2,11 +2,12 @@ | ||||
|  | ||||
| const $request = require('request'); | ||||
|  | ||||
| const XMLSign = require('./middleware/XMLSign'); | ||||
| const ParseResponse = require('./middleware/ParseResponse'); | ||||
| const signer = require('./middleware/signer'); | ||||
| const serializer = require('./middleware/serializer'); | ||||
| const response = require('./middleware/response'); | ||||
|  | ||||
| module.exports = class Client { | ||||
| 	constructor({ url }) { | ||||
| 	constructor(url) { | ||||
| 		this.url = url; | ||||
| 	} | ||||
|  | ||||
| @@ -52,25 +53,31 @@ module.exports = class Client { | ||||
| 		return [transactionId, orderId]; | ||||
| 	} | ||||
|  | ||||
| 	async downloadAndUnzip(order) { // eslint-disable-line | ||||
|  | ||||
| 	} | ||||
|  | ||||
| 	ebicsRequest(order) { | ||||
| 		return new Promise((resolve, reject) => { | ||||
| 			const { version, keys } = order; | ||||
| 			// const s = signer.version(version).use(serializer.use(order).toXML(), keys).digest().sign().toXML(); // new (signer.version(version))(serializer.use(order).toXML(), keys).digest().sign().toXML(); | ||||
| 			$request.post({ | ||||
| 				url: this.url, | ||||
| 				body: XMLSign.sign(order), | ||||
| 				body: signer.version(version).sign(serializer.use(order).toXML(), keys), // s, // new (signer.version(version))(serializer.use(order).toXML(), keys).digest().sign().toXML(), | ||||
| 				headers: { 'content-type': 'text/xml;charset=UTF-8' }, | ||||
| 			}, (err, res, data) => (err ? reject(err) : resolve(ParseResponse.parse(data, order.keys, order.version)))); | ||||
| 			}, (err, res, data) => (err ? reject(err) : resolve(response.version(version)(data, keys)))); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	request(order) { | ||||
| 		if (order.type.toLowerCase() === 'ini') return this.initialization(order); | ||||
| 		if (order.type.toLowerCase() === 'payment') return this.upload(order); | ||||
| 		if (order.type.toLowerCase() === 'status') return this.download(order); | ||||
| 	ini(order) { | ||||
| 		return this.initialization(order); | ||||
| 	} | ||||
|  | ||||
| 		throw Error('Invalid order type'); | ||||
| 	payment(order) { | ||||
| 		return this.upload(order); | ||||
| 	} | ||||
|  | ||||
| 	statement(order) { | ||||
| 		return this.download(order); | ||||
| 	} | ||||
|  | ||||
| 	status(order) { | ||||
| 		return this.download(order); | ||||
| 	} | ||||
| }; | ||||
|   | ||||
							
								
								
									
										101
									
								
								lib/ISO20022OrderBuilder.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								lib/ISO20022OrderBuilder.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const OrderBuilder = require('./OrderBuilder'); | ||||
|  | ||||
| module.exports = class ISO20022OrderBuilder extends OrderBuilder { | ||||
| 	get document() { return this._document; } | ||||
| 	get ebicsData() { return this._ebicsData; } | ||||
|  | ||||
| 	use(dataToUse) { | ||||
| 		if (Object.prototype.hasOwnProperty.call(dataToUse, 'ebicsData')) this._ebicsData = dataToUse.ebicsData; | ||||
| 		if (Object.prototype.hasOwnProperty.call(dataToUse, 'document')) this._document = dataToUse.document; | ||||
|  | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| 	static h004() { | ||||
| 		const builder = new ISO20022OrderBuilder(); | ||||
|  | ||||
| 		builder._version = 'H004'; | ||||
|  | ||||
| 		return builder; | ||||
| 	} | ||||
|  | ||||
| 	INI() { | ||||
| 		return this.details({ | ||||
| 			ebicsData: this.ebicsData, | ||||
| 			orderDetails: { OrderType: 'INI', OrderAttribute: 'DZNNN' }, | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	HIA() { | ||||
| 		return this.details({ | ||||
| 			ebicsData: this.ebicsData, | ||||
| 			orderDetails: { OrderType: 'HIA', OrderAttribute: 'DZNNN' }, | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	HPB() { | ||||
| 		return this.details({ | ||||
| 			ebicsData: this.ebicsData, | ||||
| 			orderDetails: { OrderType: 'HPB', OrderAttribute: 'DZHNN' }, | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	HKD() { | ||||
| 		return this.details({ | ||||
| 			ebicsData: this.ebicsData, | ||||
| 			orderDetails: { OrderType: 'HKD', OrderAttribute: 'DZHNN', StandardOrderParams: {} }, | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	HPD() { | ||||
| 		return this.details({ | ||||
| 			ebicsData: this.ebicsData, | ||||
| 			orderDetails: { OrderType: 'HPD', OrderAttribute: 'DZHNN', StandardOrderParams: {} }, | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	HTD() { | ||||
| 		return this.details({ | ||||
| 			ebicsData: this.ebicsData, | ||||
| 			orderDetails: { OrderType: 'HTD', OrderAttribute: 'DZHNN', StandardOrderParams: {} }, | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	HAA() { | ||||
| 		return this.details({ | ||||
| 			ebicsData: this.ebicsData, | ||||
| 			orderDetails: { OrderType: 'HAA', OrderAttribute: 'DZHNN', StandardOrderParams: {} }, | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	HAC(start = null, end = null) { | ||||
| 		const params = start && end | ||||
| 			? { DateRange: { Start: start, End: end } } | ||||
| 			: {}; | ||||
|  | ||||
| 		return this.details({ | ||||
| 			ebicsData: this.ebicsData, | ||||
| 			orderDetails: { OrderType: 'HAC', OrderAttribute: 'DZHNN', StandardOrderParams: params }, | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	PTK(start = null, end = null) { | ||||
| 		const params = start && end | ||||
| 			? { DateRange: { Start: start, End: end } } | ||||
| 			: {}; | ||||
|  | ||||
| 		return this.details({ | ||||
| 			ebicsData: this.ebicsData, | ||||
| 			orderDetails: { OrderType: 'PTK', OrderAttribute: 'DZHNN', StandardOrderParams: params }, | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	Z52() { | ||||
| 		return this.details({ | ||||
| 			ebicsData: this.ebicsData, | ||||
| 			orderDetails: { OrderType: 'Z52', OrderAttribute: 'DZHNN', StandardOrderParams: {} }, | ||||
| 		}); | ||||
| 	} | ||||
| }; | ||||
| @@ -2,10 +2,11 @@ | ||||
|  | ||||
| const crypto = require('crypto'); | ||||
|  | ||||
| // const orderTypes = ['ini', 'download', 'upload', 'zip']; | ||||
| const constants = require('./consts'); | ||||
|  | ||||
| module.exports = class OrderBuilder { | ||||
| 	constructor() { | ||||
| 		this._productString = constants.productString; | ||||
| 		this._transactionKey = crypto.randomBytes(16); | ||||
| 	} | ||||
|  | ||||
| @@ -16,24 +17,6 @@ module.exports = class OrderBuilder { | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| 	payment() { | ||||
| 		this._type = 'payment'; | ||||
|  | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| 	status() { | ||||
| 		this._type = 'status'; | ||||
|  | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| 	ini() { | ||||
| 		this._type = 'ini'; | ||||
|  | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| 	static h004() { | ||||
| 		const builder = new OrderBuilder(); | ||||
|  | ||||
| @@ -45,7 +28,6 @@ module.exports = class OrderBuilder { | ||||
| 	/** | ||||
| 	 * Getters | ||||
| 	 */ | ||||
| 	get type() { return this._type; } | ||||
| 	get data() { return this._data; } | ||||
| 	get orderDetails() { return this._data.orderDetails; } | ||||
| 	get transactionId() { return this._data.transactionId; } | ||||
| @@ -57,6 +39,8 @@ module.exports = class OrderBuilder { | ||||
| 	get userId() { return this._data.ebicsData.userId; } | ||||
| 	get keys() { return this._data.ebicsData.keysManager.keys(); } | ||||
| 	get version() { return this._version; } | ||||
| 	get productString() { return this._productString; } | ||||
| 	get orderType() { return this.orderDetails.OrderType; } | ||||
|  | ||||
| 	set transactionId(tid) { | ||||
| 		this._data.transactionId = tid === '' ? null : tid; | ||||
|   | ||||
| @@ -1,40 +0,0 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const fs = require('fs'); | ||||
| /* const extractKeys = (keysObject, encryptAlgorithm, passphrase) => Object.entries(keysObject).reduce((keys, [key, data]) => { | ||||
| 	keys[key] = decrypt(data, encryptAlgorithm, passphrase); | ||||
| 	return keys; | ||||
| }, {}); */ | ||||
|  | ||||
| module.exports = class FsKeyStorage { | ||||
| 	/** | ||||
| 	 * @param {String} path - destingiton file to save the keys | ||||
| 	 */ | ||||
| 	constructor({ path }) { | ||||
| 		if (!path) | ||||
| 			throw new Error('Invalid path provided'); | ||||
|  | ||||
| 		this._path = path; | ||||
| 	} | ||||
|  | ||||
| 	get path() { | ||||
| 		return this._path; | ||||
| 	} | ||||
|  | ||||
| 	read() { | ||||
| 		return fs.readFileSync(this._path, { encoding: 'utf8' }); | ||||
| 		// return extractKeys(JSON.parse(fs.readFileSync(this._path, { encoding: 'utf8' })), this.algorithm, this.passphrase); | ||||
| 	} | ||||
|  | ||||
| 	save(data) { | ||||
| 		fs.writeFileSync(this._path, data, { encoding: 'utf8' }); | ||||
| 		// fs.writeFileSync(this._path, encrypt(JSON.stringify(data), this.algorithm, this.passphrase), { encoding: 'utf8' }); | ||||
| 	} | ||||
|  | ||||
| 	hasData() { | ||||
| 		if (fs.existsSync(this._path)) | ||||
| 			return this.read() !== ''; | ||||
|  | ||||
| 		return false; | ||||
| 	} | ||||
| }; | ||||
| @@ -19,76 +19,50 @@ const decrypt = (data, algorithm, passphrase) => { | ||||
| 	return decrypted; | ||||
| }; | ||||
|  | ||||
| module.exports = class KeysManager { | ||||
| 	constructor(keysStorage, passphrase, algorithm = 'aes-256-cbc', createIfNone = true) { | ||||
| 		this._storage = keysStorage; | ||||
| 		this._passphrase = passphrase; | ||||
| 		this._algorithm = algorithm; | ||||
| module.exports = (keysStorage, passphrase, algorithm = 'aes-256-cbc') => { | ||||
| 	const storage = keysStorage; | ||||
| 	const pass = passphrase; | ||||
| 	const algo = algorithm; | ||||
| 	// const createIfNone = createIfNone; | ||||
|  | ||||
| 		if (createIfNone && !this._storage.hasData()) | ||||
| 			this.generate(); | ||||
| 	} | ||||
| 	return { | ||||
| 		generate(save = true) { | ||||
| 			const keys = Keys.generate(); | ||||
|  | ||||
| 	/** | ||||
| 	 * Generates the keys to work with. Then either | ||||
| 	 * saves them to the storage or returnes the keys generated | ||||
| 	 * | ||||
| 	 * @param {Boolean} save | ||||
| 	 * @default true | ||||
| 	 * | ||||
| 	 * @returns void | Keys object | ||||
| 	 */ | ||||
| 	generate(save = true) { | ||||
| 		const keys = Keys.generate(); | ||||
| 			if (save) { | ||||
| 				this.write(keys); | ||||
|  | ||||
| 		if (save) this.write(keys); | ||||
| 				return this; | ||||
| 			} | ||||
|  | ||||
| 		return keys; | ||||
| 	} | ||||
| 			return keys; | ||||
| 		}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Writes the keys to the storage | ||||
| 	 * | ||||
| 	 * @param {Keys} keysObject | ||||
| 	 * | ||||
| 	 * @returns void | ||||
| 	 */ | ||||
| 	write(keysObject) { | ||||
| 		keysObject = keysObject.keys; | ||||
| 		write(keysObject) { | ||||
| 			keysObject = keysObject.keys; | ||||
|  | ||||
| 		Object.keys(keysObject).map((key) => { | ||||
| 			keysObject[key] = keysObject[key] === null ? null : keysObject[key].toPem(); | ||||
| 			Object.keys(keysObject).map((key) => { | ||||
| 				keysObject[key] = keysObject[key] === null ? null : keysObject[key].toPem(); | ||||
|  | ||||
| 			return key; | ||||
| 		}); | ||||
| 				return key; | ||||
| 			}); | ||||
|  | ||||
| 		this._storage.save(encrypt(JSON.stringify(keysObject), this._algorithm, this._passphrase)); | ||||
| 	} | ||||
| 			storage.save(encrypt(JSON.stringify(keysObject), algo, pass)); | ||||
|  | ||||
| 	setBankKeys(bankKeys) { | ||||
| 		const keys = this.keys(); | ||||
| 			return this; | ||||
| 		}, | ||||
|  | ||||
| 		keys.setBankKeys(bankKeys); | ||||
| 		this.write(keys); | ||||
| 	} | ||||
| 		setBankKeys(bankKeys) { | ||||
| 			const keys = this.keys(); | ||||
|  | ||||
| 	/** | ||||
| 	 * Gets the keys | ||||
| 	 * | ||||
| 	 * @returns Keys object | ||||
| 	 */ | ||||
| 	keys() { | ||||
| 		return this._read(); | ||||
| 	} | ||||
| 			keys.setBankKeys(bankKeys); | ||||
| 			this.write(keys); | ||||
| 		}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Reads the keys from the storage | ||||
| 	 * | ||||
| 	 * @returns Keys object | ||||
| 	 */ | ||||
| 	_read() { | ||||
| 		const keysString = this._storage.read(); | ||||
| 		keys() { | ||||
| 			const keysString = storage.read(); | ||||
|  | ||||
| 		return new Keys(JSON.parse(decrypt(keysString, this._algorithm, this._passphrase))); | ||||
| 	} | ||||
| 			return new Keys(JSON.parse(decrypt(keysString, algo, pass))); | ||||
| 		}, | ||||
| 	}; | ||||
| }; | ||||
|   | ||||
							
								
								
									
										26
									
								
								lib/keymanagers/fsKeysStorage.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								lib/keymanagers/fsKeysStorage.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const fs = require('fs'); | ||||
|  | ||||
| module.exports = (pathToFile) => { | ||||
| 	const path = pathToFile; | ||||
|  | ||||
| 	return { | ||||
| 		read() { | ||||
| 			return fs.readFileSync(path, { encoding: 'utf8' }); | ||||
| 		}, | ||||
|  | ||||
| 		save(data) { | ||||
| 			fs.writeFileSync(path, data, { encoding: 'utf8' }); | ||||
|  | ||||
| 			return this; | ||||
| 		}, | ||||
|  | ||||
| 		hasData() { | ||||
| 			if (fs.existsSync(path)) | ||||
| 				return this.read() !== ''; | ||||
|  | ||||
| 			return false; | ||||
| 		}, | ||||
| 	}; | ||||
| }; | ||||
| @@ -1,11 +0,0 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const H004Response = require('../versions/H004/Response'); | ||||
|  | ||||
| module.exports = class ParseResponse { | ||||
| 	static parse(data, keys, version) { | ||||
| 		if (version.toUpperCase() === 'H004') return new H004Response(data, keys); | ||||
|  | ||||
| 		throw Error('Unknow EBICS response version'); | ||||
| 	} | ||||
| }; | ||||
| @@ -1,15 +0,0 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const H004Signer = require('../versions/H004/Signer'); | ||||
|  | ||||
| const H004Serializer = require('../versions/H004/OrderSerializer'); | ||||
|  | ||||
| module.exports = class XMLSign { | ||||
| 	static sign(order) { | ||||
| 		const { keys } = order; | ||||
|  | ||||
| 		if (order.version.toUpperCase() === 'H004') return new H004Signer(H004Serializer.serialize(order).toXML(), keys).digest().sign().toXML(); | ||||
|  | ||||
| 		throw Error('Error from XMLSign class: Invalid version number'); | ||||
| 	} | ||||
| }; | ||||
							
								
								
									
										11
									
								
								lib/middleware/response.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								lib/middleware/response.js
									
									
									
									
									
										Normal 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'); | ||||
| 	}, | ||||
| }; | ||||
							
								
								
									
										13
									
								
								lib/middleware/serializer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								lib/middleware/serializer.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const H004Serializer = require('../orders/H004/serializer'); | ||||
|  | ||||
| module.exports = { | ||||
| 	use(order) { | ||||
| 		const { version } = order; | ||||
|  | ||||
| 		if (version.toUpperCase() === 'H004') return H004Serializer.use(order); | ||||
|  | ||||
| 		throw Error('Error middleware/serializer.js: Invalid version number'); | ||||
| 	}, | ||||
| }; | ||||
							
								
								
									
										11
									
								
								lib/middleware/signer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								lib/middleware/signer.js
									
									
									
									
									
										Normal 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'); | ||||
| 	}, | ||||
| }; | ||||
| @@ -19,25 +19,23 @@ const lastChild = (node) => { | ||||
| 	return y; | ||||
| }; | ||||
| 
 | ||||
| module.exports = class Response { | ||||
| 	constructor(data, keys) { | ||||
| 		this.keys = keys; | ||||
| 		this.doc = new DOMParser().parseFromString(data, 'text/xml'); | ||||
| 	} | ||||
| 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'); | ||||
| @@ -49,41 +47,41 @@ module.exports = class Response { | ||||
| 		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(); | ||||
| @@ -108,9 +106,9 @@ module.exports = class Response { | ||||
| 		} | ||||
| 
 | ||||
| 		return bankKeys; | ||||
| 	} | ||||
| 	}, | ||||
| 
 | ||||
| 	toXML() { | ||||
| 		return new XMLSerializer().serializeToString(this.doc); | ||||
| 	} | ||||
| }; | ||||
| 	}, | ||||
| }); | ||||
							
								
								
									
										19
									
								
								lib/orders/H004/serializer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								lib/orders/H004/serializer.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const orders = require('../orders'); | ||||
|  | ||||
| const iniSerializer = require('./serializers/ini'); | ||||
| const downloadSerializer = require('./serializers/download'); | ||||
| const uploadSerializer = require('./serializers/upload'); | ||||
|  | ||||
| module.exports = { | ||||
| 	use(order) { | ||||
| 		const { version, orderType } = order; | ||||
|  | ||||
| 		if (orders.version(version).isIni(orderType)) return iniSerializer.use(order); | ||||
| 		if (orders.version(version).isDownload(orderType)) return downloadSerializer.use(order); | ||||
| 		if (orders.version(version).isUpload(orderType)) return uploadSerializer.use(order); | ||||
|  | ||||
| 		throw Error('Error from orders/orders.js: Wrong order version/type.'); | ||||
| 	}, | ||||
| }; | ||||
							
								
								
									
										62
									
								
								lib/orders/H004/serializers/download.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								lib/orders/H004/serializers/download.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const js2xmlparser = require('js2xmlparser'); | ||||
|  | ||||
| const Crypto = require('../../../crypto/Crypto'); | ||||
|  | ||||
| const genericSerializer = require('./generic'); | ||||
|  | ||||
| module.exports = { | ||||
| 	use(orderBuilder) { | ||||
| 		const { | ||||
| 			ebicsData, orderDetails, keys, productString, transactionId, | ||||
| 		} = orderBuilder; | ||||
| 		const { | ||||
| 			rootName, xmlOptions, xmlSchema, receipt, transfer, | ||||
| 		} = genericSerializer(orderBuilder); | ||||
|  | ||||
| 		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: ebicsData.hostId, | ||||
| 				Nonce: Crypto.nonce(), | ||||
| 				Timestamp: Crypto.timestamp(), | ||||
| 				PartnerID: ebicsData.partnerId, | ||||
| 				UserID: ebicsData.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); | ||||
| 	}, | ||||
| }; | ||||
							
								
								
									
										133
									
								
								lib/orders/H004/serializers/generic.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								lib/orders/H004/serializers/generic.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| 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 = (orderBuilder) => { | ||||
| 	const { ebicsData, transactionId } = orderBuilder; | ||||
|  | ||||
| 	return { | ||||
| 		rootName, | ||||
| 		xmlOptions, | ||||
| 		xmlSchema: { | ||||
| 			'@': rootAttributes, | ||||
| 			header, | ||||
| 			AuthSignature: authSignature, | ||||
| 			body, | ||||
| 		}, | ||||
|  | ||||
| 		receipt() { | ||||
| 			this.xmlSchema = { | ||||
| 				'@': rootAttributes, | ||||
|  | ||||
| 				header: { | ||||
| 					'@': { authenticate: true }, | ||||
| 					static: { | ||||
| 						HostID: ebicsData.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: ebicsData.hostId, | ||||
| 						TransactionID: transactionId, | ||||
| 					}, | ||||
| 					mutable: { | ||||
| 						TransactionPhase: 'Transfer', | ||||
| 						SegmentNumber: { | ||||
| 							'@': { lastSegment: true }, | ||||
| 							'#': 1, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
|  | ||||
| 				AuthSignature: authSignature, | ||||
|  | ||||
| 				body: { | ||||
| 					DataTransfer: { | ||||
| 						OrderData: encryptedOrderData, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}; | ||||
|  | ||||
| 			return this; | ||||
| 		}, | ||||
| 	}; | ||||
| }; | ||||
							
								
								
									
										144
									
								
								lib/orders/H004/serializers/ini.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								lib/orders/H004/serializers/ini.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,144 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const zlib = require('zlib'); | ||||
|  | ||||
| const js2xmlparser = require('js2xmlparser'); | ||||
|  | ||||
| const Crypto = require('../../../crypto/Crypto'); | ||||
|  | ||||
| const genericSerializer = require('./generic'); | ||||
|  | ||||
| const keySignature = (ebicsData, 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: ebicsData.partnerId, | ||||
| 		UserID: ebicsData.userId, | ||||
| 	}; | ||||
|  | ||||
| 	return js2xmlparser.parse('SignaturePubKeyOrderData', xmlOrderData, xmlOptions); | ||||
| }; | ||||
| const orderData = (ebicsData, 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: ebicsData.partnerId, | ||||
| 		UserID: ebicsData.userId, | ||||
| 	}; | ||||
|  | ||||
| 	return js2xmlparser.parse('HIARequestOrderData', xmlOrderData, xmlOptions); | ||||
| }; | ||||
| const commonHeader = (ebicsData, orderDetails, productString) => ({ | ||||
| 	'@': { authenticate: true }, | ||||
| 	static: { | ||||
| 		HostID: ebicsData.hostId, | ||||
| 		Nonce: Crypto.nonce(), | ||||
| 		Timestamp: Crypto.timestamp(), | ||||
| 		PartnerID: ebicsData.partnerId, | ||||
| 		UserID: ebicsData.userId, | ||||
| 		Product: { | ||||
| 			'@': { Language: 'en' }, | ||||
| 			'#': productString, | ||||
| 		}, | ||||
| 		OrderDetails: orderDetails, | ||||
| 		SecurityMedium: '0000', | ||||
| 	}, | ||||
| 	mutable: {}, | ||||
| }); | ||||
| const process = { | ||||
| 	INI: { | ||||
| 		rootName: 'ebicsUnsecuredRequest', | ||||
| 		header: (ebicsData, orderDetails, productString) => { | ||||
| 			const ch = commonHeader(ebicsData, orderDetails, productString); | ||||
|  | ||||
| 			delete ch.static.Nonce; | ||||
| 			delete ch.static.Timestamp; | ||||
|  | ||||
| 			return ch; | ||||
| 		}, | ||||
| 		body: (ebicsData, keys, xmlOptions) => ({ | ||||
| 			DataTransfer: { | ||||
| 				OrderData: Buffer.from(zlib.deflateSync(keySignature(ebicsData, keys.a(), xmlOptions))).toString('base64'), | ||||
| 			}, | ||||
| 		}), | ||||
| 	}, | ||||
| 	HIA: { | ||||
| 		rootName: 'ebicsUnsecuredRequest', | ||||
| 		header: (ebicsData, orderDetails, productString) => { | ||||
| 			const ch = commonHeader(ebicsData, orderDetails, productString); | ||||
|  | ||||
| 			delete ch.static.Nonce; | ||||
| 			delete ch.static.Timestamp; | ||||
|  | ||||
| 			return ch; | ||||
| 		}, | ||||
| 		body: (ebicsData, keys, xmlOptions) => ({ | ||||
| 			DataTransfer: { | ||||
| 				OrderData: Buffer.from(zlib.deflateSync(orderData(ebicsData, keys, xmlOptions))).toString('base64'), | ||||
| 			}, | ||||
| 		}), | ||||
| 	}, | ||||
| 	HPB: { | ||||
| 		rootName: 'ebicsNoPubKeyDigestsRequest', | ||||
| 		header: (ebicsData, orderDetails, productString) => commonHeader(ebicsData, orderDetails, productString), | ||||
| 		body: () => ({}), | ||||
| 	}, | ||||
| }; | ||||
|  | ||||
| module.exports = { | ||||
| 	use(orderBuilder) { | ||||
| 		const { xmlOptions, xmlSchema } = genericSerializer(orderBuilder); | ||||
| 		const { | ||||
| 			ebicsData, orderDetails, keys, productString, | ||||
| 		} = orderBuilder; | ||||
| 		const orderType = orderDetails.OrderType.toUpperCase(); | ||||
|  | ||||
| 		this.rootName = process[orderType].rootName; | ||||
| 		this.xmlOptions = xmlOptions; | ||||
| 		this.xmlSchema = xmlSchema; | ||||
|  | ||||
| 		this.xmlSchema.header = process[orderType].header(ebicsData, orderDetails, productString); | ||||
| 		this.xmlSchema.body = process[orderType].body(ebicsData, 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); | ||||
| 	}, | ||||
| }; | ||||
							
								
								
									
										88
									
								
								lib/orders/H004/serializers/upload.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								lib/orders/H004/serializers/upload.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const zlib = require('zlib'); | ||||
| const crypto = require('crypto'); | ||||
|  | ||||
| const js2xmlparser = require('js2xmlparser'); | ||||
|  | ||||
| const Crypto = require('../../../crypto/Crypto'); | ||||
|  | ||||
| const downloadSerializer = require('./download'); | ||||
|  | ||||
| const signatureValue = (document, key) => { | ||||
| 	const digested = Crypto.digestWithHash(document.replace(/\n|\r/g, '')); | ||||
|  | ||||
| 	return Crypto.sign(key, digested); | ||||
| }; | ||||
| const orderSignature = (ebicsData, 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: ebicsData.partnerId, | ||||
| 			UserID: ebicsData.userId, | ||||
| 		}, | ||||
| 	}; | ||||
|  | ||||
| 	return js2xmlparser.parse('UserSignatureData', xmlObj, xmlOptions); | ||||
| }; | ||||
| const encryptedOrderSignature = (ebicsData, document, transactionKey, key, xmlOptions) => { | ||||
| 	const dst = zlib.deflateSync(orderSignature(ebicsData, 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 = { | ||||
| 	use(orderBuilder) { | ||||
| 		const { | ||||
| 			ebicsData, keys, transactionId, transactionKey, document, | ||||
| 		} = orderBuilder; | ||||
| 		const { | ||||
| 			rootName, xmlOptions, xmlSchema, transfer, | ||||
| 		} = downloadSerializer.use(orderBuilder); | ||||
|  | ||||
| 		this.rootName = rootName; | ||||
| 		this.xmlOptions = xmlOptions; | ||||
| 		this.xmlSchema = xmlSchema; | ||||
| 		this.transfer = transfer; | ||||
|  | ||||
| 		if (transactionId) return this.transfer(encryptedOrderData(document, transactionKey)); | ||||
|  | ||||
| 		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(), transactionKey).toString('base64'), | ||||
| 				}, | ||||
| 				SignatureData: { | ||||
| 					'@': { authenticate: true }, | ||||
| 					'#': encryptedOrderSignature(ebicsData, document, transactionKey, keys.a(), this.xmlOptions), | ||||
| 				}, | ||||
| 			}, | ||||
| 		}; | ||||
|  | ||||
| 		return this; | ||||
| 	}, | ||||
|  | ||||
| 	toXML() { | ||||
| 		return js2xmlparser.parse(this.rootName, this.xmlSchema, this.xmlOptions); | ||||
| 	}, | ||||
| }; | ||||
							
								
								
									
										50
									
								
								lib/orders/H004/signer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								lib/orders/H004/signer.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
|  | ||||
| '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, keys) { | ||||
| 		const keyX = keys.x(); | ||||
| 		const doc = new DOMParser().parseFromString(data, 'text/xml'); | ||||
|  | ||||
| 		return toXML(sign(digest(doc), keyX)); | ||||
| 	}, | ||||
| }; | ||||
							
								
								
									
										35
									
								
								lib/orders/orders.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								lib/orders/orders.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const orders = { | ||||
| 	H004: { | ||||
| 		ini: ['INI', 'HIA', 'HPB'], | ||||
| 		download: ['HAA', 'HTD', 'XTD', 'HPD', 'HKD', 'PTK', 'HAC', 'STA', 'VMK', 'C52', 'C53', 'C54', 'Z01'], | ||||
| 		upload: ['AZV', 'CD1', 'CDB', 'CDD', 'CDS', 'CCT', 'CCS', 'XE3'], | ||||
| 	}, | ||||
| }; | ||||
|  | ||||
| module.exports = { | ||||
| 	version(v) { | ||||
| 		this.orders = orders[v.toUpperCase()]; | ||||
|  | ||||
| 		return this; | ||||
| 	}, | ||||
|  | ||||
| 	isIni(orderType) { | ||||
| 		const { ini } = this.orders; | ||||
|  | ||||
| 		return ini.includes(orderType.toUpperCase()); | ||||
| 	}, | ||||
|  | ||||
| 	isDownload(orderType) { | ||||
| 		const { download } = this.orders; | ||||
|  | ||||
| 		return download.includes(orderType.toUpperCase()); | ||||
| 	}, | ||||
|  | ||||
| 	isUpload(orderType) { | ||||
| 		const { upload } = this.orders; | ||||
|  | ||||
| 		return upload.includes(orderType.toUpperCase()); | ||||
| 	}, | ||||
| }; | ||||
| @@ -1,15 +0,0 @@ | ||||
| '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'); | ||||
| 	} | ||||
| }; | ||||
| @@ -1,67 +0,0 @@ | ||||
| '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); | ||||
| 	} | ||||
| }; | ||||
| @@ -1,87 +0,0 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const js2xmlparser = require('js2xmlparser'); | ||||
|  | ||||
| const consts = require('../../../consts'); | ||||
| 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._rootName = 'ebicsRequest'; | ||||
| 		this._rootAttributes = { | ||||
| 			'xmlns:ds': 'http://www.w3.org/2000/09/xmldsig#', | ||||
| 			xmlns: 'urn:org:ebics:H004', | ||||
| 			Version: 'H004', | ||||
| 			Revision: '1', | ||||
| 		}; | ||||
| 		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._xmlOptions = xmlOptions; | ||||
| 		this._xml = {}; | ||||
| 		this._productString = consts.productString; | ||||
| 	} | ||||
|  | ||||
| 	static authSignature() { | ||||
| 		return authSignature; | ||||
| 	} | ||||
|  | ||||
| 	get keys() { | ||||
| 		return this._keys; | ||||
| 	} | ||||
|  | ||||
| 	toXML() { | ||||
| 		return js2xmlparser.parse(this._rootName, this._xml, this._xmlOptions); | ||||
| 	} | ||||
| }; | ||||
| @@ -1,128 +0,0 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const zlib = require('zlib'); | ||||
| const js2xmlparser = require('js2xmlparser'); | ||||
|  | ||||
| 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' }, | ||||
| 						'#': this._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._rootName = 'ebicsUnsecuredRequest'; | ||||
| 			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'; | ||||
| 	} | ||||
| }; | ||||
| @@ -1,130 +0,0 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const zlib = require('zlib'); | ||||
| const crypto = require('crypto'); | ||||
|  | ||||
| const js2xmlparser = require('js2xmlparser'); | ||||
|  | ||||
| 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' }, | ||||
| 						'#': this._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'); | ||||
| 	} | ||||
| }; | ||||
| @@ -1,66 +0,0 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| 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' }, | ||||
| 						'#': this._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, | ||||
| 				}, | ||||
| 			}; | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user