Skip to content

Commit bbf8928

Browse files
committed
Implement H005
2 parents 82b0296 + 8268d45 commit bbf8928

101 files changed

Lines changed: 7645 additions & 128 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

index.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
'use strict';
22

33
const Client = require('./lib/Client');
4-
const Orders = require('./lib/predefinedOrders');
4+
const OrdersH004 = require('./lib/predefinedOrders/H004');
5+
const OrdersH005 = require('./lib/predefinedOrders/H005');
56
const fsKeysStorage = require('./lib/storages/fsKeysStorage');
67
const tracesStorage = require('./lib/storages/tracesStorage');
78
const BankLetter = require('./lib/BankLetter');
89

910
module.exports = {
1011
Client,
11-
Orders,
12+
/** @deprecated Use OrdersH004 or OrdersH005 instead */
13+
Orders: OrdersH004,
14+
OrdersH004,
15+
OrdersH005,
1216
BankLetter,
1317
fsKeysStorage,
1418
tracesStorage,

lib/Client.js

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ module.exports = class Client {
171171

172172
return {
173173
orderData: res.orderData(),
174+
transactionId: res.transactionId(),
174175
orderId: res.orderId(),
175176

176177
technicalCode: returnedTechnicalCode,
@@ -219,11 +220,11 @@ module.exports = class Client {
219220
businessCodeMeaning: res.businessMeaning(returnedBusinessCode),
220221

221222
// for backwards compatibility with the earlier return value [transactionId, orderId]:
222-
'0': transactionId,
223-
'1': orderId,
224-
[Symbol.iterator]: function*() {
225-
yield transactionId
226-
yield orderId
223+
0: transactionId,
224+
1: orderId,
225+
[Symbol.iterator]: function* iterator() {
226+
yield transactionId;
227+
yield orderId;
227228
},
228229
};
229230
}
@@ -232,30 +233,34 @@ module.exports = class Client {
232233
return new Promise(async (resolve, reject) => {
233234
const { version } = order;
234235
const keys = await this.keys();
235-
const r = signer
236+
const unsignedXml = (await serializer.use(order, this)).toXML();
237+
const signedXml = signer
236238
.version(version)
237-
.sign((await serializer.use(order, this)).toXML(), keys.x());
239+
.sign(unsignedXml, keys.x());
238240

239241
if (this.tracesStorage)
240242
this.tracesStorage
241-
.label(`REQUEST.${order.orderDetails.OrderType}`)
242-
.data(r)
243+
.label(`REQUEST.${order.orderDetails.AdminOrderType || order.orderDetails.OrderType}`)
244+
.data(signedXml)
243245
.persist();
244246

245247
rock({
246248
method: 'POST',
247249
url: this.url,
248-
body: r,
250+
body: signedXml,
249251
headers: { 'content-type': 'text/xml;charset=UTF-8' },
250252
},
251253
(err, res, data) => {
252-
if (err) reject(err);
254+
if (err) {
255+
reject(err);
256+
return;
257+
}
253258

254259
const ebicsResponse = response.version(version)(data.toString('utf-8'), keys);
255260

256261
if (this.tracesStorage)
257262
this.tracesStorage
258-
.label(`RESPONSE.${order.orderDetails.OrderType}`)
263+
.label(`RESPONSE.${order.orderDetails.AdminOrderType || order.orderDetails.OrderType}`)
259264
.connect()
260265
.data(ebicsResponse.toXML())
261266
.persist();

lib/keymanagers/Key.js

Lines changed: 92 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,51 @@ const {
77
privateKeyToPem,
88
publicKeyFromPem,
99
privateKeyFromPem,
10+
setRsaPublicKey,
11+
createCertificate,
12+
certificateToPem,
13+
certificateFromPem,
14+
},
15+
md: {
16+
sha256,
1017
},
1118
jsbn: {
1219
BigInteger,
1320
},
1421
} = require('node-forge');
22+
const { X509Certificate } = require('crypto');
1523

1624
const getKeyType = (str) => {
17-
const matches = str.match(/(PRIVATE|PUBLIC) KEY/);
25+
const matches = str.match(/BEGIN (?:RSA )?(PRIVATE|PUBLIC|CERTIFICATE)/);
1826
if (!matches)
1927
return null;
2028
return matches[1].toLowerCase();
2129
};
2230

23-
const keyFromPem = (pem) => {
24-
const type = getKeyType(pem);
25-
const isPublic = type === 'public';
26-
const key = isPublic ? publicKeyFromPem(pem) : privateKeyFromPem(pem);
27-
28-
return {
29-
isPublic,
30-
key,
31-
};
32-
};
33-
34-
/**
35-
* Creates a public key from modulus and exponent
36-
* @param {Buffer} mod - the modulus
37-
* @param {Buffer} exp - the exponent
38-
*/
39-
const keyFromModAndExp = (mod, exp) => {
40-
const bnMod = new BigInteger(mod.toString('hex'), 16);
41-
const bnExp = new BigInteger(exp.toString('hex'), 16);
42-
43-
return {
44-
key: rsa.setPublicKey(bnMod, bnExp),
45-
isPublic: true,
46-
};
31+
const certificateFromPrivateKey = (privateKey) => {
32+
const certificate = createCertificate();
33+
certificate.publicKey = setRsaPublicKey(privateKey.n, privateKey.e);
34+
certificate.validity.notBefore = new Date('2000-01-01');
35+
certificate.validity.notAfter = new Date('9999-12-31');
36+
certificate.setIssuer([
37+
{
38+
shortName: 'CN', value: 'ebics.example.com',
39+
},
40+
]);
41+
certificate.subject = certificate.issuer;
42+
certificate.setExtensions([
43+
{
44+
name: 'keyUsage',
45+
keyCertSign: true,
46+
digitalSignature: true,
47+
nonRepudiation: true,
48+
keyEncipherment: true,
49+
dataEncipherment: true,
50+
},
51+
]);
52+
certificate.sign(privateKey, sha256.create());
53+
54+
return certificate;
4755
};
4856

4957
module.exports = class Key {
@@ -54,39 +62,68 @@ module.exports = class Key {
5462
if (!pem && !mod && !exp) {
5563
const keyPair = rsa.generateKeyPair(size);
5664

57-
this.keyIsPublic = false;
65+
this.type = 'private';
5866
this.privateKey = keyPair.privateKey;
5967
this.publicKey = keyPair.publicKey;
68+
this.certificate = certificateFromPrivateKey(keyPair.privateKey);
6069

6170
return;
6271
}
6372

6473
// new key from pem string
6574
if (pem) {
66-
const { key, isPublic } = keyFromPem(pem);
67-
68-
this.keyIsPublic = isPublic;
69-
this.privateKey = isPublic ? null : key;
70-
this.publicKey = isPublic ? key : null;
71-
75+
this.readFromPem(pem);
7276
return;
7377
}
7478

7579
// new key from mod and exp
7680
if (mod && exp) {
77-
const { key, isPublic } = keyFromModAndExp(mod, exp);
78-
79-
this.keyIsPublic = isPublic;
80-
this.privateKey = isPublic ? null : key;
81-
this.publicKey = isPublic ? key : null;
82-
81+
this.readFromModAndExp(mod, exp); // only used for H004
8382
return;
8483
}
8584

8685
// not good
8786
throw new Error(`Can not create key without ${!mod ? 'modulus' : 'exponent'}.`);
8887
}
8988

89+
readFromPem(pem) {
90+
this.type = getKeyType(pem);
91+
switch (this.type) {
92+
case 'public':
93+
this.publicKey = publicKeyFromPem(pem);
94+
this.privateKey = null;
95+
this.certificate = null;
96+
break;
97+
case 'private':
98+
this.privateKey = privateKeyFromPem(pem);
99+
this.publicKey = setRsaPublicKey(this.privateKey.n, this.privateKey.e);
100+
this.certificate = certificateFromPrivateKey(this.privateKey);
101+
break;
102+
case 'certificate':
103+
this.privateKey = null;
104+
this.certificate = certificateFromPem(pem);
105+
this.publicKey = this.certificate.publicKey;
106+
break;
107+
default:
108+
throw new Error(`Unknown key type: ${this.type}`);
109+
}
110+
}
111+
112+
/**
113+
* Creates a public key from modulus and exponent
114+
* @param {Buffer} mod - the modulus
115+
* @param {Buffer} exp - the exponent
116+
*/
117+
readFromModAndExp(mod, exp) {
118+
const bnMod = new BigInteger(mod.toString('hex'), 16);
119+
const bnExp = new BigInteger(exp.toString('hex'), 16);
120+
121+
this.type = 'public';
122+
this.publicKey = rsa.setPublicKey(bnMod, bnExp);
123+
this.privateKey = null;
124+
this.certificate = null;
125+
}
126+
90127
static generate(size = 2048) {
91128
return new Key({ size });
92129
}
@@ -96,32 +133,31 @@ module.exports = class Key {
96133
}
97134

98135
n(to = 'buff') {
99-
const key = this.keyIsPublic ? this.publicKey : this.privateKey;
136+
const key = this.privateKey || this.publicKey;
100137
const keyN = Buffer.from(key.n.toByteArray());
101138

102139
return to === 'hex' ? keyN.toString('hex', 1) : keyN;
103140
}
104141

105142
e(to = 'buff') {
106-
const key = this.keyIsPublic ? this.publicKey : this.privateKey;
143+
const key = this.privateKey || this.publicKey;
107144
const eKey = Buffer.from(key.e.toByteArray());
108145

109146
return to === 'hex' ? eKey.toString('hex') : eKey;
110147
}
111148

112149
d() {
113-
if (this.keyIsPublic)
150+
if (!this.privateKey)
114151
throw new Error('Can not get d component out of public key.');
115152

116153
return Buffer.from(this.privateKey.d.toByteArray());
117154
}
118155

119-
isPrivate() {
120-
return !this.keyIsPublic;
121-
}
156+
certificateBase64() {
157+
if (!this.certificate)
158+
throw new Error('Certificate is not available.');
122159

123-
isPublic() {
124-
return this.keyIsPublic;
160+
return new X509Certificate(certificateToPem(this.certificate)).raw.toString('base64');
125161
}
126162

127163
// eslint-disable-next-line class-methods-use-this
@@ -133,6 +169,15 @@ module.exports = class Key {
133169
}
134170

135171
toPem() {
136-
return this.keyIsPublic ? publicKeyToPem(this.publicKey) : privateKeyToPem(this.privateKey);
172+
if (this.privateKey)
173+
return privateKeyToPem(this.privateKey);
174+
175+
if (this.certificate)
176+
return certificateToPem(this.certificate);
177+
178+
if (this.publicKey)
179+
return publicKeyToPem(this.publicKey);
180+
181+
throw new Error('No key found');
137182
}
138183
};

lib/keymanagers/defaultKeyEncryptor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ const { encrypt, decrypt } = require('../crypto/encryptDecrypt');
55

66
module.exports = ({ passphrase, iv, algorithm = 'aes-256-cbc' }) => ({
77
encrypt: data => encrypt(data, algorithm, passphrase, iv),
8-
decrypt: data => decrypt(data, algorithm, passphrase),
8+
decrypt: data => decrypt(data, algorithm, passphrase, iv),
99
});

lib/middleware/response.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
'use strict';
22

33
const H004Response = require('../orders/H004/response');
4+
const H005Response = require('../orders/H005/response');
45

56
module.exports = {
67
version(v) {
78
if (v.toUpperCase() === 'H004') return H004Response;
8-
9+
if (v.toUpperCase() === 'H005') return H005Response;
10+
911
throw Error('Error from middleware/response.js: Invalid version number');
1012
},
1113
};

lib/middleware/serializer.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
'use strict';
22

33
const H004Serializer = require('../orders/H004/serializer');
4+
const H005Serializer = require('../orders/H005/serializer');
45

56
module.exports = {
67
use(order, client) {
78
const { version } = order;
89

910
if (version.toUpperCase() === 'H004') return H004Serializer.use(order, client);
11+
if (version.toUpperCase() === 'H005') return H005Serializer.use(order, client);
1012

1113
throw Error('Error middleware/serializer.js: Invalid version number');
1214
},

lib/middleware/signer.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
'use strict';
22

33
const H004Signer = require('../orders/H004/signer');
4+
const H005Signer = require('../orders/H005/signer');
45

56
module.exports = {
67
version(v) {
78
if (v.toUpperCase() === 'H004') return H004Signer;
9+
if (v.toUpperCase() === 'H005') return H005Signer;
810

911
throw Error('Error from middleware/signer.js: Invalid version number');
1012
},

0 commit comments

Comments
 (0)