Compare commits
2 Commits
a2bfbf2308
...
e3bd35470e
Author | SHA1 | Date | |
---|---|---|---|
e3bd35470e | |||
6554a8278e |
17
README.md
17
README.md
@ -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.
|
||||||
|
@ -16,7 +16,8 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ('string' === typeof key) {
|
if ('string' === typeof key) {
|
||||||
eckles.import({ pem: key }).then(function (jwk) {
|
var pub = (-1 !== [ 'public', 'spki', 'pkix' ].indexOf(format));
|
||||||
|
eckles.import({ pem: key, public: (pub || format) }).then(function (jwk) {
|
||||||
console.log(JSON.stringify(jwk, null, 2));
|
console.log(JSON.stringify(jwk, null, 2));
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
1
fixtures/pub-ec-p256.ssh.pub
Normal file
1
fixtures/pub-ec-p256.ssh.pub
Normal file
@ -0,0 +1 @@
|
|||||||
|
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCE9Uli8bGnD4hOWdeo5KKQJ/P/vOazI4MgqJK54w37emP2JwOAOdMmXuwpxbKng3KZz27mz+nKWIlXJ3rzSGMo= P-256@localhost
|
1
fixtures/pub-ec-p384.ssh.pub
Normal file
1
fixtures/pub-ec-p384.ssh.pub
Normal file
@ -0,0 +1 @@
|
|||||||
|
ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBNsxFNGygmu3oyiyCfKDxpy4aoccor+P8N/CtmtEjwunbEnff4JTSfJXKr9LH3+Rm1Q+I57vN0urHJ+v03gI9xGTkRBmzrOnc6FaWroJ/l0DDMgvTuFS2wwxRgWUyZTLGw== P-384@localhost
|
@ -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;
|
||||||
@ -244,15 +244,15 @@ EC.parse = function parseEc(opts) {
|
|||||||
// PKCS8
|
// PKCS8
|
||||||
if (0x02 === u8[3] && 0x30 === u8[6] && 0x06 === u8[8]) {
|
if (0x02 === u8[3] && 0x30 === u8[6] && 0x06 === u8[8]) {
|
||||||
//console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
|
//console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
|
||||||
return EC.parsePkcs8(u8, jwk);
|
jwk = EC.parsePkcs8(u8, jwk);
|
||||||
// EC-only
|
// EC-only
|
||||||
} else if (0x02 === u8[2] && 0x04 === u8[5] && 0xA0 === u8[39]) {
|
} else if (0x02 === u8[2] && 0x04 === u8[5] && 0xA0 === u8[39]) {
|
||||||
//console.log("EC---", u8[2].toString(16), u8[5].toString(16), u8[39].toString(16));
|
//console.log("EC---", u8[2].toString(16), u8[5].toString(16), u8[39].toString(16));
|
||||||
return EC.parseSec1(u8, jwk);
|
jwk = EC.parseSec1(u8, jwk);
|
||||||
// SPKI/PKIK (Public)
|
// SPKI/PKIK (Public)
|
||||||
} else if (0x30 === u8[2] && 0x06 === u8[4] && 0x06 === u8[13]) {
|
} else if (0x30 === u8[2] && 0x06 === u8[4] && 0x06 === u8[13]) {
|
||||||
//console.log("SPKI-", u8[2].toString(16), u8[4].toString(16), u8[13].toString(16));
|
//console.log("SPKI-", u8[2].toString(16), u8[4].toString(16), u8[13].toString(16));
|
||||||
return EC.parseSpki(u8, jwk);
|
jwk = EC.parseSpki(u8, jwk);
|
||||||
// Error
|
// Error
|
||||||
} else {
|
} else {
|
||||||
//console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
|
//console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
|
||||||
@ -266,15 +266,15 @@ EC.parse = function parseEc(opts) {
|
|||||||
// PKCS8
|
// PKCS8
|
||||||
if (0x02 === u8[3] && 0x30 === u8[6] && 0x06 === u8[8]) {
|
if (0x02 === u8[3] && 0x30 === u8[6] && 0x06 === u8[8]) {
|
||||||
//console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
|
//console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
|
||||||
return EC.parsePkcs8(u8, jwk);
|
jwk = EC.parsePkcs8(u8, jwk);
|
||||||
// EC-only
|
// EC-only
|
||||||
} else if (0x02 === u8[3] && 0x04 === u8[6] && 0xA0 === u8[56]) {
|
} else if (0x02 === u8[3] && 0x04 === u8[6] && 0xA0 === u8[56]) {
|
||||||
//console.log("EC---", u8[3].toString(16), u8[6].toString(16), u8[56].toString(16));
|
//console.log("EC---", u8[3].toString(16), u8[6].toString(16), u8[56].toString(16));
|
||||||
return EC.parseSec1(u8, jwk);
|
jwk = EC.parseSec1(u8, jwk);
|
||||||
// SPKI/PKIK (Public)
|
// SPKI/PKIK (Public)
|
||||||
} else if (0x30 === u8[2] && 0x06 === u8[4] && 0x06 === u8[13]) {
|
} else if (0x30 === u8[2] && 0x06 === u8[4] && 0x06 === u8[13]) {
|
||||||
//console.log("SPKI-", u8[2].toString(16), u8[4].toString(16), u8[13].toString(16));
|
//console.log("SPKI-", u8[2].toString(16), u8[4].toString(16), u8[13].toString(16));
|
||||||
return EC.parseSpki(u8, jwk);
|
jwk = EC.parseSpki(u8, jwk);
|
||||||
// Error
|
// Error
|
||||||
} else {
|
} else {
|
||||||
//console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
|
//console.log("PKCS8", u8[3].toString(16), u8[6].toString(16), u8[8].toString(16));
|
||||||
@ -285,6 +285,14 @@ EC.parse = function parseEc(opts) {
|
|||||||
} else {
|
} else {
|
||||||
throw new Error("Supported key types are P-256 and P-384");
|
throw new Error("Supported key types are P-256 and P-384");
|
||||||
}
|
}
|
||||||
|
if (opts.public) {
|
||||||
|
if (true !== opts.public) {
|
||||||
|
throw new Error("options.public must be either `true` or `false` not ("
|
||||||
|
+ typeof opts.public + ") '" + opts.public + "'");
|
||||||
|
}
|
||||||
|
delete jwk.d;
|
||||||
|
}
|
||||||
|
return jwk;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
EC.toJwk = EC.import = EC.parse;
|
EC.toJwk = EC.import = EC.parse;
|
||||||
@ -296,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) {
|
||||||
@ -326,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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -378,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
|
||||||
|
@ -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
10
test.sh
@ -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
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user