v0.8.0: JWK-to-PEM for PKCS#1 and SSH

This commit is contained in:
AJ ONeal 2018-11-22 17:21:54 -07:00
parent 895a29bf71
commit 607e352b17
7 changed files with 74 additions and 8 deletions

View file

@ -17,7 +17,8 @@ It is considered to be complete, but if you find a bug please open an issue. -->
## PEM-to-JWK
* [x] PKCS#1 (traditional), PKCS#8, SPKI/PKIX
* [x] PKCS#1 (traditional)
* [x] PKCS#8, SPKI/PKIX
* [x] 2048-bit, 4096-bit (and ostensibily all others)
* [x] SSH (RFC4716), (RFC 4716/SSH2)
@ -45,16 +46,16 @@ Rasha.import({ pem: pem }).then(function (jwk) {
}
```
<!--
## JWK-to-PEM
* [x] PKCS#1 (traditional), PKCS#8, SPKI/PKIX
* [x] PKCS#1 (traditional)
* [ ] PKCS#8, SPKI/PKIX
* [x] 2048-bit, 4096-bit (and ostensibily all others)
* [x] SSH (RFC4716), (RFC 4716/SSH2)
```js
var Rasha = require('rasha');
var jwk = require('rasha/fixtures/privkey-rsa-2038.jwk.json');
var jwk = require('rasha/fixtures/privkey-rsa-2048.jwk.json');
Rasha.export({ jwk: jwk }).then(function (pem) {
// PEM in PKCS1 (traditional) format

View file

@ -0,0 +1,5 @@
{
"kty": "RSA",
"n": "m2ttVBxPlWw06ZmGBWVDlfjkPAJ4DgnY0TrDwtCohHzLxGhDNzUJefLukC-xu0LBKylYojT5vTkxaOhxeSYo31syu4WhxbkTBLICOFcCGMob6pSQ38P8LdAIlb0pqDHxEJ9adWomjuFf0SUhN1cP7s9m8Yk9trkpEqjskocn2BOnTB57qAZM6-I70on0_iDZm7-jcqOPgADAmbWHhy67BXkk4yy_YzD4yOGZFXZcNp915_TW5bRd__AKPHUHxJasPiyEFqlNKBR2DSD-LbX5eTmzCh2ikrwTMja7mUdBJf2bK3By5AB0Qi49OykUCfNZeQlEz7UNNj9RGps_50-CNw",
"e": "AQAB"
}

View file

@ -15,6 +15,10 @@ Enc.bufToHex = function toHex(u8) {
return hex.join('').toLowerCase();
};
Enc.hexToBase64 = function (hex) {
return Buffer.from(hex, 'hex').toString('base64');
};
Enc.hexToBuf = function (hex) {
return Buffer.from(hex, 'hex');
};
@ -29,7 +33,7 @@ Enc.numToHex = function numToHex(d) {
Enc.base64ToHex = function base64ToHex(b64) {
return Enc.bufToHex(Enc.base64ToBuf(b64));
}
};
Enc.bufToBase64 = function toHex(u8) {
// we want to maintain api compatability with browser APIs,
@ -37,11 +41,25 @@ Enc.bufToBase64 = function toHex(u8) {
return Buffer.from(u8).toString('base64');
};
/*
Enc.bufToUint8 = function bufToUint8(buf) {
return new Uint8Array(buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength));
};
*/
Enc.bufToUrlBase64 = function toHex(u8) {
return Enc.bufToBase64(u8)
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
};
Enc.strToHex = function strToHex(str) {
return Buffer.from(str).toString('hex');
};
Enc.strToBuf = function strToBuf(str) {
return Buffer.from(str);
};
/*
Enc.strToBin = function strToBin(str) {
var escstr = encodeURIComponent(str);

View file

@ -100,7 +100,7 @@ RSA.pack = function (opts) {
} else if (-1 !== [ 'spki', 'pkix' ].indexOf(format)) {
return PEM.packBlock({ type: "PUBLIC KEY", bytes: x509.packSpki(jwk) });
} else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) {
return SSH.packSsh(jwk);
return SSH.pack({ jwk: jwk, comment: opts.comment });
} else {
throw new Error("Sanity Error: reached unreachable code block with format: " + format);
}

View file

@ -17,6 +17,7 @@ SSH.parse = function (pem, jwk) {
var offset = (buf.byteOffset || 0);
// using dataview to be browser-compatible (I do want _some_ code reuse)
var dv = new DataView(buf.buffer.slice(offset, offset + buf.byteLength));
var el;
if (SSH.RSA !== Enc.bufToHex(buf.slice(0, SSH.RSA.length/2))) {
throw new Error("does not lead with ssh header");
@ -27,7 +28,12 @@ SSH.parse = function (pem, jwk) {
if (i > 3) { throw new Error("15+ elements, probably not a public ssh key"); }
len = dv.getUint32(index, false);
index += 4;
els.push(buf.slice(index, index + len));
el = buf.slice(index, index + len);
// remove BigUInt '00' prefix
if (0x00 === el[0]) {
el = el.slice(1);
}
els.push(el);
index += len;
}
@ -36,3 +42,39 @@ SSH.parse = function (pem, jwk) {
return jwk;
};
SSH.pack = function (opts) {
var jwk = opts.jwk;
var header = 'ssh-rsa';
var comment = opts.comment || 'rsa@localhost';
var e = SSH._padHexInt(Enc.base64ToHex(jwk.e));
var n = SSH._padHexInt(Enc.base64ToHex(jwk.n));
var hex = [
SSH._numToUint32Hex(header.length)
, Enc.strToHex(header)
, SSH._numToUint32Hex(e.length/2)
, e
, SSH._numToUint32Hex(n.length/2)
, n
].join('');
return [ header, Enc.hexToBase64(hex), comment ].join(' ');
};
SSH._numToUint32Hex = function (num) {
var hex = num.toString(16);
while (hex.length < 8) {
hex = '0' + hex;
}
return hex;
};
SSH._padHexInt = function (hex) {
// BigInt is negative if the high order bit 0x80 is set,
// so ASN1, SSH, and many other formats pad with '0x00'
// to signifiy a positive number.
var i = parseInt(hex.slice(0, 2), 16);
if (0x80 & i) {
return '00' + hex;
}
return hex;
};

View file

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