Skip to content

Commit 71d382e

Browse files
committed
Update: export EC keys as PEM
1 parent d190cf2 commit 71d382e

File tree

3 files changed

+99
-14
lines changed

3 files changed

+99
-14
lines changed

lib/jwk/eckey.js

+77-14
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,32 @@ var WRAP_ALGS = [
2626
"ECDH-ES+A256KW"
2727
];
2828

29+
var EC_OID = "1.2.840.10045.2.1";
30+
function oidToCurveName(oid) {
31+
switch (oid) {
32+
case "1.2.840.10045.3.1.7":
33+
return "P-256";
34+
case "1.3.132.0.34":
35+
return "P-384";
36+
case "1.3.132.0.35":
37+
return "P-521";
38+
default:
39+
return null;
40+
}
41+
}
42+
function curveNameToOid(crv) {
43+
switch (crv) {
44+
case "P-256":
45+
return "1.2.840.10045.3.1.7";
46+
case "P-384":
47+
return "1.3.132.0.34";
48+
case "P-521":
49+
return "1.3.132.0.35";
50+
default:
51+
return null;
52+
}
53+
}
54+
2955
var JWKEcCfg = {
3056
publicKey: function(props) {
3157
var fields = JWK.helpers.COMMON_PROPS.concat([
@@ -113,12 +139,62 @@ var JWKEcCfg = {
113139
},
114140
verifyKey: function(alg, keys) {
115141
return keys.public;
142+
},
143+
144+
convertToPEM: function(key, isPrivate) {
145+
// curveName to OID
146+
var oid = key.crv;
147+
oid = curveNameToOid(oid);
148+
oid = forge.asn1.oidToDer(oid);
149+
// key as bytes
150+
var type,
151+
pub,
152+
asn1;
153+
if (isPrivate) {
154+
type = "EC PRIVATE KEY";
155+
pub = Buffer.concat([
156+
new Buffer([0x00, 0x04]),
157+
key.x,
158+
key.y
159+
]).toString("binary");
160+
key = key.d.toString("binary");
161+
asn1 = forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.SEQUENCE, true, [
162+
forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.INTEGER, false, "\u0001"),
163+
forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.OCTETSTRING, false, key),
164+
forge.asn1.create(forge.asn1.Class.CONTEXT_SPECIFIC, 0, true, [
165+
forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.OID, false, oid.bytes())
166+
]),
167+
forge.asn1.create(forge.asn1.Class.CONTEXT_SPECIFIC, 1, true, [
168+
forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.BITSTRING, false, pub)
169+
])
170+
]);
171+
} else {
172+
type = "PUBLIC KEY";
173+
key = Buffer.concat([
174+
new Buffer([0x00, 0x04]),
175+
key.x,
176+
key.y
177+
]).toString("binary");
178+
asn1 = forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.SEQUENCE, true, [
179+
forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.SEQUENCE, true, [
180+
forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.OID, false, forge.asn1.oidToDer(EC_OID).bytes()),
181+
forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.OID, false, oid.bytes())
182+
]),
183+
forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.BITSTRING, false, key)
184+
]);
185+
}
186+
asn1 = forge.asn1.toDer(asn1).bytes();
187+
var pem = forge.pem.encode({
188+
type: type,
189+
body: asn1
190+
});
191+
return pem;
116192
}
117193
};
118194

119195
// Inspired by digitalbaazar/node-forge/js/rsa.js
120196
var validators = {
121-
oid: "1.2.840.10045.2.1",
197+
oid: EC_OID,
122198
privateKey: {
123199
// ECPrivateKey
124200
name: "ECPrivateKey",
@@ -215,19 +291,6 @@ var validators = {
215291
}
216292
};
217293

218-
function oidToCurveName(oid) {
219-
switch (oid) {
220-
case "1.2.840.10045.3.1.7":
221-
return "P-256";
222-
case "1.3.132.0.34":
223-
return "P-384";
224-
case "1.3.132.0.35":
225-
return "P-521";
226-
default:
227-
return null;
228-
}
229-
}
230-
231294
var JWKEcFactory = {
232295
kty: "EC",
233296
validators: validators,

test/jwk/eckey-test.js

+12
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,18 @@ describe("jwk/EC", function() {
233233
algs = JWK.EC.config.algorithms(keys, "verify");
234234
assert.deepEqual(algs, []);
235235
});
236+
it("exports PEM for public key", function() {
237+
var pem = JWK.EC.config.convertToPEM(keyPair.public, false);
238+
assert.isString(pem);
239+
assert.match(pem, /^-----BEGIN PUBLIC KEY-----\r\n/);
240+
assert.match(pem, /\r\n-----END PUBLIC KEY-----\r\n$/);
241+
});
242+
it("exports PEM for private key", function() {
243+
var pem = JWK.EC.config.convertToPEM(keyPair.private, true);
244+
assert.isString(pem);
245+
assert.match(pem, /^-----BEGIN EC PRIVATE KEY-----\r\n/);
246+
assert.match(pem, /\r\n-----END EC PRIVATE KEY-----\r\n$/);
247+
});
236248
});
237249
describe("keystore integration", function() {
238250
it("generates a 'EC' JWK", function() {

test/jwk/keystore-test.js

+10
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,11 @@ describe("jwk/keystore", function() {
570570
var promise = JWK.store.KeyStore.asKey(pem.rawPrivate, "pem");
571571
promise = promise.then(function(jwk) {
572572
assert.ok(JWK.store.KeyStore.asKey(jwk));
573+
return jwk.toPEM(true);
574+
});
575+
promise = promise.then(function(pem) {
576+
assert.match(pem, /^-----BEGIN EC PRIVATE KEY-----\r\n/);
577+
assert.match(pem, /\r\n-----END EC PRIVATE KEY-----\r\n$/);
573578
});
574579

575580
return promise;
@@ -594,6 +599,11 @@ describe("jwk/keystore", function() {
594599
var promise = JWK.store.KeyStore.asKey(pem.spki, "pem");
595600
promise = promise.then(function(jwk) {
596601
assert.ok(JWK.store.KeyStore.asKey(jwk));
602+
return jwk.toPEM(false);
603+
});
604+
promise = promise.then(function(pem) {
605+
assert.match(pem, /^-----BEGIN PUBLIC KEY-----\r\n/);
606+
assert.match(pem, /\r\n-----END PUBLIC KEY-----\r\n$/);
597607
});
598608

599609
return promise;

0 commit comments

Comments
 (0)