Browse Source

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

tags/v0.8.0
AJ ONeal 1 year ago
parent
commit
607e352b17
7 changed files with 74 additions and 8 deletions
  1. +5
    -4
      README.md
  2. +0
    -0
      fixtures/privkey-rsa-2048.jwk.json
  3. +5
    -0
      fixtures/pub-rsa-2048.jwk.json
  4. +19
    -1
      lib/encoding.js
  5. +1
    -1
      lib/rasha.js
  6. +43
    -1
      lib/ssh.js
  7. +1
    -1
      package.json

+ 5
- 4
README.md 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


fixtures/privkey-rsa-2048.jwt.json → fixtures/privkey-rsa-2048.jwk.json View File


+ 5
- 0
fixtures/pub-rsa-2048.jwk.json View File

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

+ 19
- 1
lib/encoding.js 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);


+ 1
- 1
lib/rasha.js 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);
}


+ 43
- 1
lib/ssh.js 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;
};

+ 1
- 1
package.json 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",


Loading…
Cancel
Save