21 Commits

Author SHA1 Message Date
nanov
6d601f4186 chore: fix package json syntax 2019-11-07 10:42:11 +02:00
nanov
2d05bf88f9 chore: add contributors to package json 2019-11-07 10:41:43 +02:00
nanov
10e0d602bd chore: update license expression in package.json 2019-11-07 10:38:01 +02:00
nanov
eea0a49130 feat: drop bn.js 2019-11-07 10:37:31 +02:00
nanov
3e95478b3b feat: add nyc and cleanup .gitignore 2019-11-07 10:35:31 +02:00
nanov
4496cbf560 feat: remove toBuffer from number 2019-11-07 10:30:03 +02:00
nanov
0efc46b014 feat add BigNumber tests 2019-11-07 10:27:20 +02:00
nanov
eafe2f9f55 chore: restructure BigNumber 2019-11-07 10:27:08 +02:00
nanov
24afdeb257 chroe: import consts 2019-11-07 10:26:53 +02:00
nanov
34051f0a9f feat: migrate Crypto to own BigNumber 2019-11-07 08:58:52 +02:00
nanov
14779088f1 chore: lint 2019-11-07 08:57:54 +02:00
nanov
aad0bd97c8 feat: implement all needed BigNumber methods 2019-11-07 08:57:44 +02:00
nanov
429e807994 fix number generarion 2019-11-06 17:27:48 +02:00
nanov
bb8d1cfaa0 wip: migrate MFG1 to BigNumber 2019-11-06 17:16:56 +02:00
nanov
cda36bfcb3 wip: implement BigNumber wrapper 2019-11-06 17:16:15 +02:00
Dimitar Nanov
d21d89fb36 Merge pull request #20 from eCollect/feat/drop-moment
Drop moment dependency
2019-11-06 16:55:15 +02:00
nanov
df9c330411 feat: drop moment dependency 2019-11-06 16:52:31 +02:00
nanov
2a17ff6056 chore: write tests for date range 2019-11-06 16:52:20 +02:00
nanov
33ac6ac60f feat: implement date handling and formatting 2019-11-06 16:51:59 +02:00
nanov
e9f7c11bbb v0.1.1 2019-11-05 06:48:24 +02:00
nanov
9aabe933e9 chore: update license 2019-11-05 06:41:31 +02:00
12 changed files with 3997 additions and 40 deletions

26
.gitignore vendored
View File

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

View File

@@ -1,6 +1,13 @@
# node-ebics-client
<p align="center">
<a href="https://travis-ci.org/eCollect/node-ebics-client" title="Build Status"><img src="https://travis-ci.org/eCollect/node-ebics-client.svg?branch=master" alt="Build Status" /></a>
<a href="https://www.npmjs.com/package/ebics-client" title="Build Status">
<img alt="ebics-client" src="https://img.shields.io/npm/v/ebics-client">
</a>
<a href="https://snyk.io/test/github/ecollect/node-ebics-client" title="Known Vulnerabilities">
<img src="https://snyk.io/test/github/ecollect/node-ebics-client/badge.svg" alt="Known Vulnerabilities">
</a>
<a href="/eCollect/node-ebics-client/blob/master/LICENSE" title="GPL-3.0"><img alt="GPL-3.0" src="https://img.shields.io/github/license/eCollect/node-ebics-client"></a>
</p>
Pure node.js ( >=8 ) implementation of [EBICS](https://en.wikipedia.org/wiki/Electronic_Banking_Internet_Communication_Standard) ( Electronic Banking Internet Communication ).
@@ -14,6 +21,7 @@ The client is currently tested and verified to work with the following banks:
* [Credit Suisse (Schweiz) AG](https://www.credit-suisse.com/ch/en.html)
* [Zürcher Kantonalbank](https://www.zkb.ch/en/lg/ew.html)
* [Raiffeisen Schweiz](https://www.raiffeisen.ch/rch/de.html)
* [BW Bank](https://www.bw-bank.de/de/home.html)
## Inspiration
@@ -24,3 +32,5 @@ The basic concept of this library was inspired by the [EPICS](https://github.com
## Copyright
Copyright: eCollect AG, 2018-9.
Licensed under the [GPLv3](LICENSE) license.

View File

@@ -2,15 +2,14 @@
const fs = require('fs');
const moment = require('moment');
const handlebars = require('handlebars');
const Crypto = require('./crypto/Crypto');
// const BN = require('bn.js');
const { date } = require('./utils.js');
const registerHelpers = () => {
handlebars.registerHelper('today', () => moment().format('DD.MM.YYYY'));
handlebars.registerHelper('today', () => date.toISODate(Date.now(), false));
handlebars.registerHelper('now', () => moment().format('HH:mm:ss'));
handlebars.registerHelper('now', () => date.toISOTime(Date.now(), false));
handlebars.registerHelper('keyExponentBits', k => Buffer.byteLength(k.e()) * 8);

View File

@@ -1,9 +1,8 @@
'use strict';
const packageJson = require('../package.json');
const { version } = require('../package.json');
const name = 'Node Ebics Client';
const { version } = packageJson;
const orderOperations = {
ini: 'INI',
upload: 'UPLOAD',

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

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

View File

@@ -2,15 +2,14 @@
const crypto = require('crypto');
const BN = require('bn.js');
const BigNumber = require('./BigNumber.js');
const mgf1 = require('./MGF1');
const modPow = (base, power, mod) => {
let result = new BN(1);
let result = new BigNumber(1);
while (power > 0) {
result = power.and(new BN(1)) == 1 ? (result.mul(base)).mod(mod) : result; // eslint-disable-line
result = power.and(new BigNumber(1)) == 1 ? (result.mul(base)).mod(mod) : result; // eslint-disable-line
base = (base.mul(base)).mod(mod);
power = power.shrn(1);
}
@@ -28,10 +27,13 @@ const emsaPSS = (msg, salt) => {
const dbMask = mgf1.generate(mTickHash, db.length);
const maskedDb = mgf1.xor(db, dbMask);
let maskedDbMsb = mgf1.rjust(new BN(maskedDb.slice(0, 1), 2).toString(2), 8, '0');
let maskedDbMsb = mgf1.rjust(new BigNumber(maskedDb.slice(0, 1)).toString(2), 8, '0');
maskedDbMsb = `0${maskedDbMsb.substr(1)}`;
maskedDb[0] = (new BN(maskedDbMsb, 2).toBuffer())[0]; // eslint-disable-line
// console.log((new BN(maskedDbMsb, 2).toBuffer())[0], new BigNumber(maskedDbMsb, 2).toBuffer()[0]);
// maskedDb[0] = (new BN(maskedDbMsb, 2).toBuffer())[0]; // eslint-disable-line
maskedDb[0] = new BigNumber(maskedDbMsb, 2).toBEBuffer()[0]; // eslint-disable-line
return Buffer.concat([maskedDb, mTickHash, Buffer.from('BC', 'hex')]);
};
@@ -65,11 +67,12 @@ module.exports = class Crypto {
}
static sign(key, msg, salt = crypto.randomBytes(32)) {
const base = new BN(emsaPSS(msg, salt));
const power = new BN(key.d());
const mod = new BN(key.n());
// console.log(key.d());
const base = new BigNumber(emsaPSS(msg, salt));
const power = new BigNumber(key.d());
const mod = new BigNumber(key.n());
return (modPow(base, power, mod)).toBuffer().toString('base64');
return (modPow(base, power, mod)).toBEBuffer().toString('base64');
}
static pad(d) {

View File

@@ -1,13 +1,13 @@
'use strict';
const crypto = require('crypto');
const BN = require('bn.js');
const BigNumber = require('./BigNumber.js');
const MFG_LEN = 32;
const divceil = (a, b) => ~~(((a + b) - 1) / b); // eslint-disable-line no-bitwise
const rjust = (string, width, padding) => {
padding = padding || ' ';
const rjust = (string, width, padding = ' ') => {
padding = padding.substr(0, 1);
if (string.length < width)
return padding.repeat(width - string.length) + string;
@@ -26,7 +26,7 @@ const i2osp = (x, len) => {
if (x >= 256 ** len)
throw new Error('Integer too large');
return Buffer.from(rjust((Buffer.from((new BN(x)).toArray('be', 4)).toString()).replace(/\x00/gi, ''), len, '\x00')); // eslint-disable-line no-control-regex
return Buffer.from(rjust(new BigNumber(x).toBEBuffer(4).toString().replace(/\x00/gi, ''), len, '\x00')); // eslint-disable-line no-control-regex
};
module.exports = {

View File

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

3761
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
{
"name": "ebics-client",
"version": "0.1.0",
"version": "0.1.1",
"description": "Node.js ISO 20022 Compliant EBICS Client",
"main": "index.js",
"scripts": {
"lint": "eslint .",
"test": "mocha test/**/*.js"
"test": "nyc mocha test/**/*.js"
},
"repository": {
"type": "git",
@@ -19,6 +19,16 @@
],
"author": "eCollect Sofia Tech Team",
"contributors": [
{
"name": "Dimitar Nanov",
"url": "https://nanov.io",
"email": "dimitar@nanov.io"
},
{
"name": "Vladislav Hristov",
"url": "https://github.com/vladhristov",
"email": "vlad.s.ch@gmail.com"
},
{
"name": "Vasyl Stashuk",
"url": "https://github.com/vasyas"
@@ -28,12 +38,10 @@
"url": "https://github.com/yagop"
}
],
"license": "MIT",
"license": "GPL-3.0-only",
"dependencies": {
"bn.js": "^5.0.0",
"handlebars": "^4.4.3",
"js2xmlparser": "^4.0.0",
"moment": "^2.24.0",
"node-forge": "^0.9.1",
"request": "^2.88.0",
"uuid": "^3.3.3",
@@ -47,6 +55,7 @@
"eslint-config-ecollect-base": "^0.1.2",
"eslint-plugin-import": "^2.18.2",
"libxmljs": "^0.19.7",
"mocha": "^6.2.2"
"mocha": "^6.2.2",
"nyc": "^14.1.1"
}
}

47
test/unit/BigNumber.js Normal file
View File

@@ -0,0 +1,47 @@
'use strict';
/* eslint-env node, mocha */
const { assert } = require('chai');
const { jsbn: { BigInteger } } = require('node-forge');
const BigNumber = require('../../lib/crypto/BigNumber');
const types = [
{ name: 'BigNumber', value: new BigNumber(11) },
{ name: 'BigInteger', value: new BigInteger('11') },
{ name: 'string with default radix', value: '11' },
{ name: 'string with radix 16', value: '0b', radix: 16 },
{ name: 'Buffer', value: Buffer.from('0b', 'hex') },
{ name: 'Array', value: [11] },
];
describe('BigNumber', () => {
describe('creating an instance', () => {
it('should throw with no value given', () => assert.throws(() => new BigNumber()));
it('should throw wrong value type', () => assert.throws(() => new BigNumber({})));
for (const { name, value, radix } of types) {
let instance;
describe(`out of ${name}`, () => {
it('create instance', () => assert.doesNotThrow(() => {
instance = new BigNumber(value, radix);
}));
it('toString with radix 10', () => assert.equal(instance.toString(), '11'));
it('toString with radix 16', () => assert.equal(instance.toString(16), '0b'));
it('toBEBuffer without length', () => assert.equal(instance.toBEBuffer().toString('hex'), '0b'));
it('toBEBuffer with length', () => assert.equal(instance.toBEBuffer(4).toString('hex'), '0000000b'));
});
}
});
describe('exports', () => {
it('toBEBuffer with too short length should throw', () => assert.throw(() => new BigNumber(837462187362).toBEBuffer(1)));
});
describe('operators', () => {
const num = new BigNumber(1);
it('and', () => assert.equal(num.and(1), 1));
it('mul', () => assert.equal(num.mul(2), 2));
it('mod', () => assert.equal(num.mod(1), 0));
it('shrn', () => assert.equal(num.shrn(1), 0));
});
});

29
test/unit/utils.js Normal file
View File

@@ -0,0 +1,29 @@
'use strict';
/* eslint-env node, mocha */
const { assert } = require('chai');
const utils = require('../../lib/utils');
describe('utils', () => {
describe('dateRange', () => {
describe('dateRange', () => {
it('should generate empty object with partial parameters', () => {
assert.isEmpty(utils.dateRange());
});
it('should throw with invalid date', () => {
assert.throws(() => utils.dateRange('2018-15-15', '2018-20-20'));
});
it('should work for valid string input', () => {
assert.containsAllDeepKeys(utils.dateRange('2018-01-15', '2018-01-20'), { DateRange: { Start: '2018-01-15', End: '2018-01-20' } });
});
it('should work for Date string input', () => {
assert.containsAllDeepKeys(utils.dateRange(new Date('2018-01-15'), new Date('2018-01-20')), { DateRange: { Start: '2018-01-15', End: '2018-01-20' } });
});
it('should work for timestamp string input', () => {
assert.containsAllDeepKeys(utils.dateRange(new Date('2018-01-15').getTime(), new Date('2018-01-20').getTime()), { DateRange: { Start: '2018-01-15', End: '2018-01-20' } });
});
});
});
});