v1.1.0: support ssh public key out

This commit is contained in:
AJ ONeal 2018-11-20 10:43:47 -07:00
parent 6554a8278e
commit e3bd35470e
6 changed files with 71 additions and 14 deletions

View File

@ -43,6 +43,7 @@ eckles.import({ pem: pem }).then(function (jwk) {
* [x] SEC1/X9.62, PKCS#8, SPKI/PKIX * [x] SEC1/X9.62, PKCS#8, SPKI/PKIX
* [x] P-256 (prime256v1, secp256r1), P-384 (secp384r1) * [x] P-256 (prime256v1, secp256r1), P-384 (secp384r1)
* [x] SSH (RFC4716), (RFC 4716/SSH2)
```js ```js
var eckles = require('eckles'); var eckles = require('eckles');
@ -76,6 +77,22 @@ eckles.export({ jwk: jwk, format: 'pkcs8' }).then(function (pem) {
}); });
``` ```
`format: 'ssh'`:
Although SSH uses SEC1 for private keys, it uses ts own special non-ASN1 format
(affectionately known as rfc4716) for public keys. I got curious and then decided
to add this format as well.
To get the same format as you
would get with `ssh-keygen`, pass `ssh` as the format option:
```js
eckles.export({ jwk: jwk, format: 'ssh' }).then(function (pub) {
// Special SSH2 Public Key format (RFC 4716)
console.log(pub);
});
```
`public: 'true'`: `public: 'true'`:
If a private key is used as input, a private key will be output. If a private key is used as input, a private key will be output.

View File

@ -0,0 +1 @@
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCE9Uli8bGnD4hOWdeo5KKQJ/P/vOazI4MgqJK54w37emP2JwOAOdMmXuwpxbKng3KZz27mz+nKWIlXJ3rzSGMo= P-256@localhost

View File

@ -0,0 +1 @@
ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBNsxFNGygmu3oyiyCfKDxpy4aoccor+P8N/CtmtEjwunbEnff4JTSfJXKr9LH3+Rm1Q+I57vN0urHJ+v03gI9xGTkRBmzrOnc6FaWroJ/l0DDMgvTuFS2wwxRgWUyZTLGw== P-384@localhost

View File

@ -150,11 +150,11 @@ EC.parseSec1 = function parseEcOnlyPrivkey(u8, jwk) {
kty: jwk.kty kty: jwk.kty
, crv: jwk.crv , crv: jwk.crv
, d: PEM._toUrlSafeBase64(d) , d: PEM._toUrlSafeBase64(d)
//, dh: d //, dh: toHex(d)
, x: PEM._toUrlSafeBase64(x) , x: PEM._toUrlSafeBase64(x)
//, xh: x //, xh: toHex(x)
, y: PEM._toUrlSafeBase64(y) , y: PEM._toUrlSafeBase64(y)
//, yh: y //, yh: toHex(y)
}; };
}; };
@ -187,11 +187,11 @@ EC.parsePkcs8 = function parseEcPkcs8(u8, jwk) {
kty: jwk.kty kty: jwk.kty
, crv: jwk.crv , crv: jwk.crv
, d: PEM._toUrlSafeBase64(d) , d: PEM._toUrlSafeBase64(d)
//, dh: d //, dh: toHex(d)
, x: PEM._toUrlSafeBase64(x) , x: PEM._toUrlSafeBase64(x)
//, xh: x //, xh: toHex(x)
, y: PEM._toUrlSafeBase64(y) , y: PEM._toUrlSafeBase64(y)
//, yh: y //, yh: toHex(y)
}; };
}; };
@ -219,9 +219,9 @@ EC.parseSpki = function parsePem(u8, jwk) {
kty: jwk.kty kty: jwk.kty
, crv: jwk.crv , crv: jwk.crv
, x: PEM._toUrlSafeBase64(x) , x: PEM._toUrlSafeBase64(x)
//, xh: x //, xh: toHex(x)
, y: PEM._toUrlSafeBase64(y) , y: PEM._toUrlSafeBase64(y)
//, yh: y //, yh: toHex(y)
}; };
}; };
EC.parsePkix = EC.parseSpki; EC.parsePkix = EC.parseSpki;
@ -304,17 +304,20 @@ EC.pack = function (opts) {
} }
var jwk = JSON.parse(JSON.stringify(opts.jwk)); var jwk = JSON.parse(JSON.stringify(opts.jwk));
var format = opts.format; var format = opts.format;
if (opts.public || -1 !== [ 'spki', 'pkix' ].indexOf(format)) { if (opts.public || -1 !== [ 'spki', 'pkix', 'ssh', 'rfc4716' ].indexOf(format)) {
jwk.d = null; jwk.d = null;
} }
if ('EC' !== jwk.kty) { if ('EC' !== jwk.kty) {
throw new Error("options.jwk.kty must be 'EC' for EC keys"); throw new Error("options.jwk.kty must be 'EC' for EC keys");
} }
if (!jwk.d) { if (!jwk.d) {
if (!format || 'pkix' === format) { if (!format || -1 !== [ 'spki', 'pkix' ].indexOf(format)) {
format = 'spki'; format = 'spki';
} else if ('spki' !== format) { } else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) {
throw new Error("options.format must be 'spki' for public EC keys"); format = 'ssh';
} else {
throw new Error("options.format must be 'spki' or 'ssh' for public EC keys, not ("
+ typeof format + ") " + format);
} }
} else { } else {
if (!format || 'sec1' === format) { if (!format || 'sec1' === format) {
@ -334,8 +337,12 @@ EC.pack = function (opts) {
return PEM.packBlock({ type: "EC PRIVATE KEY", bytes: EC.packSec1(jwk) }); return PEM.packBlock({ type: "EC PRIVATE KEY", bytes: EC.packSec1(jwk) });
} else if ('pkcs8' === format) { } else if ('pkcs8' === format) {
return PEM.packBlock({ type: "EC PRIVATE KEY", bytes: EC.packPkcs8(jwk) }); return PEM.packBlock({ type: "EC PRIVATE KEY", bytes: EC.packPkcs8(jwk) });
} else { } else if (-1 !== [ 'spki', 'pkix' ].indexOf(format)) {
return PEM.packBlock({ type: "PUBLIC KEY", bytes: EC.packSpki(jwk) }); return PEM.packBlock({ type: "PUBLIC KEY", bytes: EC.packSpki(jwk) });
} else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) {
return EC.packSsh(jwk);
} else {
throw new Error("Sanity Error: reached unreachable code block with format: " + format);
} }
}); });
}; };
@ -386,6 +393,27 @@ EC.packSpki = function (jwk) {
); );
}; };
EC.packPkix = EC.packSpki; EC.packPkix = EC.packSpki;
EC.packSsh = function (jwk) {
// Custom SSH format
var typ = 'ecdsa-sha2-nistp256';
var a = '32 35 36';
var b = '41';
var comment = jwk.crv + '@localhost';
if ('P-256' !== jwk.crv) {
typ = 'ecdsa-sha2-nistp384';
a = '33 38 34';
b = '61';
}
var x = toHex(base64ToUint8(urlBase64ToBase64(jwk.x)));
var y = toHex(base64ToUint8(urlBase64ToBase64(jwk.y)));
var ssh = Hex.toUint8(
('00 00 00 13 65 63 64 73 61 2d 73 68 61 32 2d 6e 69 73 74 70'
+ a + '00 00 00 08 6e 69 73 74 70' + a + '00 00 00' + b
+ '04' + x + y).replace(/\s+/g, '').toLowerCase()
);
return typ + ' ' + toBase64(ssh) + ' ' + comment;
};
// //
// A dumbed-down, minimal ASN.1 packer // A dumbed-down, minimal ASN.1 packer

View File

@ -1,6 +1,6 @@
{ {
"name": "eckles", "name": "eckles",
"version": "1.0.1", "version": "1.1.0",
"description": "PEM-to-JWK and JWK-to-PEM for ECDSA keys in a lightweight, zero-dependency library focused on perfect universal compatibility.", "description": "PEM-to-JWK and JWK-to-PEM for ECDSA keys in a lightweight, zero-dependency library focused on perfect universal compatibility.",
"homepage": "https://git.coolaj86.com/coolaj86/eckles.js", "homepage": "https://git.coolaj86.com/coolaj86/eckles.js",
"main": "index.js", "main": "index.js",

10
test.sh
View File

@ -29,10 +29,15 @@ echo "Testing JWK-to-PEM P-256"
echo "" echo ""
node bin/eckles.js fixtures/privkey-ec-p256.jwk.json sec1 | tee fixtures/privkey-ec-p256.sec1.pem.2 node bin/eckles.js fixtures/privkey-ec-p256.jwk.json sec1 | tee fixtures/privkey-ec-p256.sec1.pem.2
diff fixtures/privkey-ec-p256.sec1.pem fixtures/privkey-ec-p256.sec1.pem.2 diff fixtures/privkey-ec-p256.sec1.pem fixtures/privkey-ec-p256.sec1.pem.2
#
node bin/eckles.js fixtures/privkey-ec-p256.jwk.json pkcs8 | tee fixtures/privkey-ec-p256.pkcs8.pem.2 node bin/eckles.js fixtures/privkey-ec-p256.jwk.json pkcs8 | tee fixtures/privkey-ec-p256.pkcs8.pem.2
diff fixtures/privkey-ec-p256.pkcs8.pem fixtures/privkey-ec-p256.pkcs8.pem.2 diff fixtures/privkey-ec-p256.pkcs8.pem fixtures/privkey-ec-p256.pkcs8.pem.2
#
node bin/eckles.js fixtures/pub-ec-p256.jwk.json spki | tee fixtures/pub-ec-p256.spki.pem.2 node bin/eckles.js fixtures/pub-ec-p256.jwk.json spki | tee fixtures/pub-ec-p256.spki.pem.2
diff fixtures/pub-ec-p256.spki.pem fixtures/pub-ec-p256.spki.pem.2 diff fixtures/pub-ec-p256.spki.pem fixtures/pub-ec-p256.spki.pem.2
# ssh-keygen -f fixtures/pub-ec-p256.spki.pem -i -mPKCS8 > fixtures/pub-ec-p256.ssh.pub
node bin/eckles.js fixtures/pub-ec-p256.jwk.json ssh | tee fixtures/pub-ec-p256.ssh.pub.2
diff fixtures/pub-ec-p256.ssh.pub fixtures/pub-ec-p256.ssh.pub.2
echo "" echo ""
echo "" echo ""
@ -40,10 +45,15 @@ echo "Testing JWK-to-PEM P-384"
echo "" echo ""
node bin/eckles.js fixtures/privkey-ec-p384.jwk.json sec1 | tee fixtures/privkey-ec-p384.sec1.pem.2 node bin/eckles.js fixtures/privkey-ec-p384.jwk.json sec1 | tee fixtures/privkey-ec-p384.sec1.pem.2
diff fixtures/privkey-ec-p384.sec1.pem fixtures/privkey-ec-p384.sec1.pem.2 diff fixtures/privkey-ec-p384.sec1.pem fixtures/privkey-ec-p384.sec1.pem.2
#
node bin/eckles.js fixtures/privkey-ec-p384.jwk.json pkcs8 | tee fixtures/privkey-ec-p384.pkcs8.pem.2 node bin/eckles.js fixtures/privkey-ec-p384.jwk.json pkcs8 | tee fixtures/privkey-ec-p384.pkcs8.pem.2
diff fixtures/privkey-ec-p384.pkcs8.pem fixtures/privkey-ec-p384.pkcs8.pem.2 diff fixtures/privkey-ec-p384.pkcs8.pem fixtures/privkey-ec-p384.pkcs8.pem.2
#
node bin/eckles.js fixtures/pub-ec-p384.jwk.json spki | tee fixtures/pub-ec-p384.spki.pem.2 node bin/eckles.js fixtures/pub-ec-p384.jwk.json spki | tee fixtures/pub-ec-p384.spki.pem.2
diff fixtures/pub-ec-p384.spki.pem fixtures/pub-ec-p384.spki.pem.2 diff fixtures/pub-ec-p384.spki.pem fixtures/pub-ec-p384.spki.pem.2
# ssh-keygen -f fixtures/pub-ec-p384.spki.pem -i -mPKCS8 > fixtures/pub-ec-p384.ssh.pub
node bin/eckles.js fixtures/pub-ec-p384.jwk.json ssh | tee fixtures/pub-ec-p384.ssh.pub.2
diff fixtures/pub-ec-p384.ssh.pub fixtures/pub-ec-p384.ssh.pub.2
rm fixtures/*.2 rm fixtures/*.2