v0.8.0: JWK-to-PEM for PKCS#1 and SSH
This commit is contained in:
parent
895a29bf71
commit
607e352b17
7 changed files with 74 additions and 8 deletions
|
@ -17,7 +17,8 @@ It is considered to be complete, but if you find a bug please open an issue. -->
|
||||||
|
|
||||||
## PEM-to-JWK
|
## 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] 2048-bit, 4096-bit (and ostensibily all others)
|
||||||
* [x] SSH (RFC4716), (RFC 4716/SSH2)
|
* [x] SSH (RFC4716), (RFC 4716/SSH2)
|
||||||
|
|
||||||
|
@ -45,16 +46,16 @@ Rasha.import({ pem: pem }).then(function (jwk) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
<!--
|
|
||||||
## JWK-to-PEM
|
## 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] 2048-bit, 4096-bit (and ostensibily all others)
|
||||||
* [x] SSH (RFC4716), (RFC 4716/SSH2)
|
* [x] SSH (RFC4716), (RFC 4716/SSH2)
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var Rasha = require('rasha');
|
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) {
|
Rasha.export({ jwk: jwk }).then(function (pem) {
|
||||||
// PEM in PKCS1 (traditional) format
|
// PEM in PKCS1 (traditional) format
|
||||||
|
|
5
fixtures/pub-rsa-2048.jwk.json
Normal file
5
fixtures/pub-rsa-2048.jwk.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"kty": "RSA",
|
||||||
|
"n": "m2ttVBxPlWw06ZmGBWVDlfjkPAJ4DgnY0TrDwtCohHzLxGhDNzUJefLukC-xu0LBKylYojT5vTkxaOhxeSYo31syu4WhxbkTBLICOFcCGMob6pSQ38P8LdAIlb0pqDHxEJ9adWomjuFf0SUhN1cP7s9m8Yk9trkpEqjskocn2BOnTB57qAZM6-I70on0_iDZm7-jcqOPgADAmbWHhy67BXkk4yy_YzD4yOGZFXZcNp915_TW5bRd__AKPHUHxJasPiyEFqlNKBR2DSD-LbX5eTmzCh2ikrwTMja7mUdBJf2bK3By5AB0Qi49OykUCfNZeQlEz7UNNj9RGps_50-CNw",
|
||||||
|
"e": "AQAB"
|
||||||
|
}
|
|
@ -15,6 +15,10 @@ Enc.bufToHex = function toHex(u8) {
|
||||||
return hex.join('').toLowerCase();
|
return hex.join('').toLowerCase();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Enc.hexToBase64 = function (hex) {
|
||||||
|
return Buffer.from(hex, 'hex').toString('base64');
|
||||||
|
};
|
||||||
|
|
||||||
Enc.hexToBuf = function (hex) {
|
Enc.hexToBuf = function (hex) {
|
||||||
return Buffer.from(hex, 'hex');
|
return Buffer.from(hex, 'hex');
|
||||||
};
|
};
|
||||||
|
@ -29,7 +33,7 @@ Enc.numToHex = function numToHex(d) {
|
||||||
|
|
||||||
Enc.base64ToHex = function base64ToHex(b64) {
|
Enc.base64ToHex = function base64ToHex(b64) {
|
||||||
return Enc.bufToHex(Enc.base64ToBuf(b64));
|
return Enc.bufToHex(Enc.base64ToBuf(b64));
|
||||||
}
|
};
|
||||||
|
|
||||||
Enc.bufToBase64 = function toHex(u8) {
|
Enc.bufToBase64 = function toHex(u8) {
|
||||||
// we want to maintain api compatability with browser APIs,
|
// we want to maintain api compatability with browser APIs,
|
||||||
|
@ -37,11 +41,25 @@ Enc.bufToBase64 = function toHex(u8) {
|
||||||
return Buffer.from(u8).toString('base64');
|
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) {
|
Enc.bufToUrlBase64 = function toHex(u8) {
|
||||||
return Enc.bufToBase64(u8)
|
return Enc.bufToBase64(u8)
|
||||||
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
.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) {
|
Enc.strToBin = function strToBin(str) {
|
||||||
var escstr = encodeURIComponent(str);
|
var escstr = encodeURIComponent(str);
|
||||||
|
|
|
@ -100,7 +100,7 @@ RSA.pack = function (opts) {
|
||||||
} else if (-1 !== [ 'spki', 'pkix' ].indexOf(format)) {
|
} else if (-1 !== [ 'spki', 'pkix' ].indexOf(format)) {
|
||||||
return PEM.packBlock({ type: "PUBLIC KEY", bytes: x509.packSpki(jwk) });
|
return PEM.packBlock({ type: "PUBLIC KEY", bytes: x509.packSpki(jwk) });
|
||||||
} else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) {
|
} else if (-1 !== [ 'ssh', 'rfc4716' ].indexOf(format)) {
|
||||||
return SSH.packSsh(jwk);
|
return SSH.pack({ jwk: jwk, comment: opts.comment });
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Sanity Error: reached unreachable code block with format: " + format);
|
throw new Error("Sanity Error: reached unreachable code block with format: " + format);
|
||||||
}
|
}
|
||||||
|
|
44
lib/ssh.js
44
lib/ssh.js
|
@ -17,6 +17,7 @@ SSH.parse = function (pem, jwk) {
|
||||||
var offset = (buf.byteOffset || 0);
|
var offset = (buf.byteOffset || 0);
|
||||||
// using dataview to be browser-compatible (I do want _some_ code reuse)
|
// using dataview to be browser-compatible (I do want _some_ code reuse)
|
||||||
var dv = new DataView(buf.buffer.slice(offset, offset + buf.byteLength));
|
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))) {
|
if (SSH.RSA !== Enc.bufToHex(buf.slice(0, SSH.RSA.length/2))) {
|
||||||
throw new Error("does not lead with ssh header");
|
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"); }
|
if (i > 3) { throw new Error("15+ elements, probably not a public ssh key"); }
|
||||||
len = dv.getUint32(index, false);
|
len = dv.getUint32(index, false);
|
||||||
index += 4;
|
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;
|
index += len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,3 +42,39 @@ SSH.parse = function (pem, jwk) {
|
||||||
|
|
||||||
return 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;
|
||||||
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "rasha",
|
"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.",
|
"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",
|
"homepage": "https://git.coolaj86.com/coolaj86/rasha.js",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
|
Loading…
Reference in a new issue