Skip to content

Commit 80333f9

Browse files
authored
Merge pull request #30 from Mastercard/feature/support_p8_p1
Adding support for PKCS8 and PCKS1 private key files
2 parents 0a07d06 + 2627ff5 commit 80333f9

File tree

7 files changed

+158
-14
lines changed

7 files changed

+158
-14
lines changed

lib/mcapi/crypto/crypto.js

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,7 @@ function Crypto(config) {
1818
this.encryptionCertificate = readPublicCertificate(config.encryptionCertificate);
1919

2020
// Load private key (for decryption)
21-
if (config.privateKey) {
22-
this.privateKey = loadPrivateKey(config.privateKey);
23-
} else if (config.keyStore) {
24-
this.privateKey = getPrivateKey(config.keyStore, config.keyStoreAlias, config.keyStorePassword);
25-
}
21+
this.privateKey = getPrivateKey(config);
2622

2723
this.encoding = config.dataEncoding;
2824

@@ -196,7 +192,27 @@ function readPublicCertificate(publicCertificatePath) {
196192
/**
197193
* @private
198194
*/
199-
function getPrivateKey(p12Path, alias, password) {
195+
function getPrivateKey(config) {
196+
if (config.privateKey) {
197+
return loadPrivateKey(config.privateKey);
198+
} else if (config.keyStore) {
199+
if (config.keyStore.includes(".p12")) {
200+
return getPrivateKey12(config.keyStore, config.keyStoreAlias, config.keyStorePassword);
201+
}
202+
if (config.keyStore.includes(".pem")) {
203+
return getPrivateKeyPem(config.keyStore);
204+
}
205+
if (config.keyStore.includes(".der")) {
206+
return getPrivateKeyDer(config.keyStore);
207+
}
208+
}
209+
return null;
210+
}
211+
212+
/**
213+
* @private
214+
*/
215+
function getPrivateKey12(p12Path, alias, password) {
200216
const p12Content = fs.readFileSync(p12Path, 'binary');
201217

202218
if (!p12Content || p12Content.length <= 1) {
@@ -230,6 +246,38 @@ function getPrivateKey(p12Path, alias, password) {
230246
return keyObj.key;
231247
}
232248

249+
/**
250+
* Load a PKCS#1 PEM (starts with "-----BEGIN RSA PRIVATE KEY-----")
251+
* or a PKCS#8 PEM (starts with "-----BEGIN PRIVATE KEY-----")"
252+
*/
253+
function getPrivateKeyPem(pemPath) {
254+
let pemContent = fs.readFileSync(pemPath, 'binary');
255+
256+
if (!pemContent || pemContent.length <= 1) {
257+
throw new Error('pem keystore content is empty');
258+
}
259+
260+
pemContent = pemContent.replace("\n", "");
261+
pemContent = pemContent.replace("\r\n", "");
262+
263+
return forge.pki.privateKeyFromPem(pemContent);
264+
}
265+
266+
/**
267+
* Load a Binary DER-encoded PKCS#8
268+
*/
269+
function getPrivateKeyDer(derPath) {
270+
const derContent = fs.readFileSync(derPath, 'binary');
271+
272+
if (!derContent || derContent.length <= 1) {
273+
throw new Error('der keystore content is empty');
274+
}
275+
276+
const pkeyAsn1 = forge.asn1.fromDer(derContent);
277+
return forge.pki.privateKeyFromAsn1(pkeyAsn1);
278+
}
279+
280+
233281
/**
234282
* @private
235283
* @param config Configuration object

test/crypto.test.js

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ describe("Crypto", () => {
5757
it("with valid config with private keystore", () => {
5858
const config = JSON.parse(JSON.stringify(testConfig));
5959
delete config.privateKey;
60-
config.keyStore = "./test/res/test_key.p12";
60+
config.keyStore = "./test/res/keys/pkcs12/test_key.p12";
6161
config.keyStoreAlias = "mykeyalias";
6262
config.keyStorePassword = "Password1";
6363
assert.doesNotThrow(() => {
@@ -66,6 +66,36 @@ describe("Crypto", () => {
6666
);
6767
});
6868

69+
it("with valid config with private pkcs1 pem keystore", () => {
70+
const config = JSON.parse(JSON.stringify(testConfig));
71+
delete config.privateKey;
72+
config.keyStore = "./test/res/keys/pkcs1/test_key.pem";
73+
assert.doesNotThrow(() => {
74+
new Crypto(config);
75+
}
76+
);
77+
});
78+
79+
it("with valid config with private pkcs8 pem keystore", () => {
80+
const config = JSON.parse(JSON.stringify(testConfig));
81+
delete config.privateKey;
82+
config.keyStore = "./test/res/keys/pkcs8/test_key.pem";
83+
assert.doesNotThrow(() => {
84+
new Crypto(config);
85+
}
86+
);
87+
});
88+
89+
it("with valid config with private pkcs8 der keystore", () => {
90+
const config = JSON.parse(JSON.stringify(testConfig));
91+
delete config.privateKey;
92+
config.keyStore = "./test/res/keys/pkcs8/test_key.der";
93+
assert.doesNotThrow(() => {
94+
new Crypto(config);
95+
}
96+
);
97+
});
98+
6999
it("with valid config header", () => {
70100
assert.doesNotThrow(() =>
71101
new Crypto(testConfigHeader)
@@ -233,8 +263,8 @@ describe("Crypto", () => {
233263
});
234264
});
235265

236-
describe("#getPrivateKey", () => {
237-
const getPrivateKey = Crypto.__get__("getPrivateKey");
266+
describe("#getPrivateKey12", () => {
267+
const getPrivateKey = Crypto.__get__("getPrivateKey12");
238268

239269
it("not valid key", () => {
240270
assert.throws(() => {
@@ -244,30 +274,65 @@ describe("Crypto", () => {
244274

245275
it("empty alias", () => {
246276
assert.throws(() => {
247-
getPrivateKey("./test/res/test_key_container.p12");
277+
getPrivateKey("./test/res/keys/pkcs12/test_key_container.p12");
248278
}, /Key alias is not set/);
249279
});
250280

251281
it("empty password", () => {
252282
assert.throws(() => {
253-
getPrivateKey("./test/res/test_key_container.p12", "keyalias");
283+
getPrivateKey("./test/res/keys/pkcs12/test_key_container.p12", "keyalias");
254284
}, /Keystore password is not set/);
255285
});
256286

257287
it("valid p12", () => {
258-
const pk = getPrivateKey("./test/res/test_key_container.p12", "mykeyalias", "Password1");
288+
const pk = getPrivateKey("./test/res/keys/pkcs12/test_key_container.p12", "mykeyalias", "Password1");
259289
assert.ok(pk);
260290
});
261291

262292
it("valid p12, alias not found", () => {
263293
assert.throws(() => {
264-
getPrivateKey("./test/res/test_key_container.p12", "mykeyalias1", "Password1");
294+
getPrivateKey("./test/res/keys/pkcs12/test_key_container.p12", "mykeyalias1", "Password1");
265295
}, /No key found for alias \[mykeyalias1\]/);
266296
});
267297
});
268298

299+
describe("#getPrivateKeyPem", () => {
300+
const getPrivateKeyPem = Crypto.__get__("getPrivateKeyPem");
301+
302+
it("valid pkcs8 pem", () => {
303+
const pk = getPrivateKeyPem("./test/res/keys/pkcs8/test_key.pem");
304+
assert.ok(pk);
305+
});
306+
307+
it("valid pkcs1 pem", () => {
308+
const pk = getPrivateKeyPem("./test/res/keys/pkcs1/test_key.pem");
309+
assert.ok(pk);
310+
});
311+
312+
it("not valid key", () => {
313+
assert.throws(() => {
314+
getPrivateKeyPem("./test/res/empty.key");
315+
}, /pem keystore content is empty/);
316+
});
317+
});
318+
319+
describe("#getPrivateKeyDer", () => {
320+
const getPrivateKeyDer = Crypto.__get__("getPrivateKeyDer");
321+
322+
it("valid pkcs8 der", () => {
323+
const pk = getPrivateKeyDer("./test/res/keys/pkcs8/test_key.der");
324+
assert.ok(pk);
325+
});
326+
327+
it("not valid key", () => {
328+
assert.throws(() => {
329+
getPrivateKeyDer("./test/res/empty.key");
330+
}, /der keystore content is empty/);
331+
});
332+
333+
})
269334

270-
describe("#newEncryptionParams", () => {
335+
describe("#newEncryptionParams", () => {
271336
let crypto;
272337
before(() => {
273338
crypto = new Crypto(testConfig);

test/res/keys/pkcs1/test_key.pem

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
MIICXQIBAAKBgQDHul8A7MM3ynx1XRxmLCRZ1Lr66QppWP5ZaEotsQPpFtyq3w/x
3+
dLWZd5XXQcIb/wUQWcbxBWYJPMk/GVQ3pvkIKEqC+h08VdXtfbBlM+RE8OYaQW5O
4+
Fg7YjHgL/WvCka6VJ990RuX9Zdj9TUu8GyqERll/XUDACs85atbSW6PBQQIDAQAB
5+
AoGBAL9acs0LCZn5OLalB6FoJ0edhasA/MWjysRUI8WU898s1SwsXDUEkTxAk2HR
6+
kayK7woUSYL/nhu5jkIS/Vn4clvH7XRkch22cjAAs4oGZXUUlVrcXFDiR0o2OqAI
7+
Xh24ESM/tpDWmn/N30afn31TBAKxgY5Ej2SrmK5gjSeMGI9xAkEA+gPM/P+jLiwy
8+
wuiS4QRYxeDFtluaafl7UHR/I6vL9VaqOptwV1e/JlytKNMCwoMqadd739/BAX9d
9+
qNgpniHC9QJBAMyCZBAc8PIjfM1h4seEDeb2mxl1Y2DmcDUVWIt+E4Zs3m1I59we
10+
VgY/BwX4UIElhgsVjNo6qXYPbWiql4/tzZ0CQQCLJ6RnyO2NXIJgY8yku6Ohd6r0
11+
BeZbR8XwEPdW5l8eTb9v4WZU5vz4oCqtB02I8DKiOJK1F7g4WijKOo5neoklAkBt
12+
G9/w7M/sD9zk4qWQVrboE4faRFPZ/fe9in7sJT6biHf/DFePi6vPt06y87FXxcJH
13+
JZ85SvTgZQi1P9aO1ovNAkAVxgJq94CKCZjOiks5xTro6V0pYdsx0tuuIlx/vGVG
14+
H3bcmpSm/S54nSovzbEDN7gKr4CSSsAQqC6oPwJSQN6m
15+
-----END RSA PRIVATE KEY-----
File renamed without changes.
File renamed without changes.

test/res/keys/pkcs8/test_key.der

634 Bytes
Binary file not shown.

test/res/keys/pkcs8/test_key.pem

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALCcqlioDdtVqSIW
3+
/Y0PUM0YX5Us76B6VSS5e9LwFeK9g/jsrfGs72WdRxEX1uHTzZXHGimMmmODqv4M
4+
bZ5Qbbs7dCZwSKlqNlSKLx40zwy4xZh1ie/UJ2CgUhu62wXDnUMg4HE0l9oY5g/P
5+
BXvSbuGM87gMnNOk0E+pfWHjQbvlAgMBAAECgYBggfuD3rFTvYdinXWH82qP6FWy
6+
yo9W/gIwwzqqlY8gC7dl+s9CVOGsgTkoWgKN/JNG2Tmuoqpq3rQ9hsUP0Ztj32ty
7+
VfEWqKipDtC0x2xi2D7I25gxVLu521tqLhoaWN1oWdPraflXEkEY9p8CD8tzxc6Q
8+
wi6ShZ8TSacuLvivgQJBAOd/X4iNmmPZ0AY+ZVri4Ic+YXOOapagkXuR2Y5pcRfw
9+
3jueEwZMIo142L+dFZMDORsqPhFwwCzOcbn2NEID4ckCQQDDTh607HbkvXAwYHDZ
10+
6gpczrhSAkpjQ0fffOFuF8sI5A64KjnTGte+fm8ic5Mess1oP6LdaS0HvJs4n+m7
11+
nPc9AkEApTwbOmKoQoEjpHFA8wBhducls8+BcQYnEWZnPOkyGf6JAVCxD5ukRgpt
12+
20cKMSbpyeP67YPnB5RLRIrhfgU7UQJAX3d5NRD9UPR0uYD6yNpJNHJr0NKD0B+c
13+
K1dczjbdLTxlIYqqd1GAsgIViu6ZtIDMPTAWCUqXE1gTO8uXMfkZNQJASgdEiTiI
14+
EnZLto+1hRr/fYNzIJHDelJ2oGtzbRmUtXQ8d489obsZusK2kSPWgX1lzXjvrv07
15+
jznkRk0QtDG8Vw==
16+
-----END PRIVATE KEY-----

0 commit comments

Comments
 (0)