Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code refactoring, removed dependencies #6

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
9 changes: 3 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "rsa-pem-to-jwk",
"version": "1.1.3",
"version": "2.1.0",
"description": "Converts PEM encoded RSA public and private keys to the JWK (JSON Web Key) format.",
"main": "rsa-pem-to-jwk.js",
"scripts": {
Expand All @@ -20,7 +20,7 @@
"jwk",
"jwks"
],
"author": "Andrew Balmos <[email protected]> (https://github.com/abalmos)",
"author": "xxxzsx <[email protected]> (https://github.com/xxxzsx)",
"license": "Apache 2.0",
"bugs": {
"url": "https://github.com/OADA/rsa-pem-to-jwk/issues"
Expand All @@ -36,8 +36,5 @@
"mocha": "^2.0.1",
"coveralls": "~2.11.2"
},
"dependencies": {
"object-assign": "^2.0.0",
"rsa-unpack": "0.0.6"
}
"dependencies": {}
}
139 changes: 87 additions & 52 deletions rsa-pem-to-jwk.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copyright 2014 Open Ag Data Alliance
/* Copyright 2021 Open Ag Data Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -12,10 +12,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';

var rsaUnpack = require('rsa-unpack');
var objectAssign = require('object-assign');
function base64toArrayBuffer(base64) {
return typeof atob !== 'undefined' ?
Uint8Array.from(atob(base64), byte => byte.charCodeAt(0)) : // Browser API
Uint8Array.from(Buffer.from(base64, 'base64')); // Node.js API
}

function arrayBufferToBase64(buffer) {
return typeof btoa !== 'undefined' ?
btoa(String.fromCharCode(...new Uint8Array(buffer))) : // Browser API
Buffer.from(buffer).toString('base64'); // Node.js API
}

/*
* Parameters:
Expand All @@ -29,59 +37,86 @@ var objectAssign = require('object-assign');
* - rsaPemToJwk('...', 'private');
* - rsaPemToJwk('...', {...});
*/
module.exports = function rsaPemToJwk(pem, extraKeys, type) {
// Unpack the PEM
var key = rsaUnpack(pem);
if (key === undefined) {
return undefined;
}
module.exports = function rsaPemToJwk(pemKey, extraKeys, type) {
// Process parameters
if (typeof extraKeys === 'string') {
type = extraKeys;
extraKeys = {};
}

// Process parameters
if (typeof extraKeys === 'string') {
type = extraKeys;
extraKeys = {};
}
type = type || (key.privateExponent !== undefined ? 'private' : 'public');
// Unpack the PEM
pemKey = String(pemKey).trim().split("\n");

// Requested private JWK but gave a public PEM
if (type == 'private' && key.privateExponent === undefined) {
return undefined;
}
// Check and remove RSA key header/footer
let keyType = (/-----BEGIN(?: RSA)? (PRIVATE|PUBLIC) KEY-----/.exec(pemKey.shift()) || [])[1];
if (!keyType || !RegExp(`-----END( RSA)? ${keyType} KEY-----`).exec(pemKey.pop())) {
//throw Error('Headers not supported.');
return;
}

// Make the public exponent into a buffer of minimal size
var expSize = Math.ceil(Math.log(key.publicExponent) / Math.log(256));
var exp = new Buffer(expSize);
var v = key.publicExponent;
// Check requested JWK and given PEM types
keyType = keyType.toLowerCase();
if (!type) {
type = keyType;
} else if (type === 'private' && keyType === 'public') {
//throw Error(`RSA type mismatch: requested ${type}, given ${keyType}.`);
return;
}

for (var i = expSize - 1; i >= 0; i--) {
exp.writeUInt8(v % 256, i);
v = Math.floor(v / 256);
}
// PEM base64 to ArrayBuffer
const derKey = new Uint8Array(base64toArrayBuffer(pemKey.join('')));

// The public portion is always present
var r = objectAssign({kty: 'RSA'}, extraKeys, {
n: base64url(key.modulus),
e: base64url(exp),
});
// DER reading offset
let offset = {
private: derKey[1] & 0x80 ? derKey[1] - 0x80 + 5 : 7,
public: derKey[1] & 0x80 ? derKey[1] - 0x80 + 2 : 2
}[keyType];

// Add private
if (type === 'private') {
objectAssign(r, {
d: base64url(key.privateExponent),
p: base64url(key.prime1),
q: base64url(key.prime2),
dp: base64url(key.exponent1),
dq: base64url(key.exponent2),
qi: base64url(key.coefficient),
});
}
function read() {
let s = derKey[offset + 1];

return r;
};
if (s & 0x80) {
let n = s - 0x80;
s = new DataView(derKey.buffer)[ ['getUint8', 'getUint16'][n - 1] ](offset + 2);
offset += n;
}
offset += 2;

function base64url(b) {
return b.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
return derKey.slice(offset, offset += s);
}

const key = {
modulus: read(),
publicExponent: read(),
privateExponent: read(),
prime1: read(),
prime2: read(),
exponent1: read(),
exponent2: read(),
coefficient: read()
};

function base64Url(buffer) {
return arrayBufferToBase64(buffer)
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/g, '');
}

return {
kty: 'RSA',
...extraKeys,
// The public portion is always present
n: base64Url(key.modulus),
e: base64Url(key.publicExponent),
// Read private part
...type === "private" && {
d: base64Url(key.privateExponent),
p: base64Url(key.prime1),
q: base64Url(key.prime2),
dp: base64Url(key.exponent1),
dq: base64Url(key.exponent2),
qi: base64Url(key.coefficient)
}
};
};
9 changes: 4 additions & 5 deletions test/rsa-pem-to-jwk.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2014 Open Ag Data Alliance
* Copyright 2021 Open Ag Data Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -20,7 +20,6 @@
var fs = require('fs');
var path = require('path');
var expect = require('chai').expect;
var objectAssign = require('object-assign');

var rsaPemToJwk = require('../rsa-pem-to-jwk');

Expand Down Expand Up @@ -51,13 +50,13 @@ describe('rsa-pem-to-jwk', function() {
it('should return a public JWK with extra keys', function() {
var jwk = rsaPemToJwk(publicPem, {use: 'sig'});

expect(jwk).to.eql(objectAssign({}, expectedPublic, {use: 'sig'}));
expect(jwk).to.eql({...expectedPublic, use: 'sig'});
});

it('should return a private JWK with extra keys', function() {
var jwk = rsaPemToJwk(privatePem, {use: 'sig'});

expect(jwk).to.eql(objectAssign({}, expectedPrivate, {use: 'sig'}));
expect(jwk).to.eql({...expectedPrivate, use: 'sig'});
});

it('should return a public JWK from a private PEM', function() {
Expand All @@ -70,7 +69,7 @@ describe('rsa-pem-to-jwk', function() {
function() {
var jwk = rsaPemToJwk(privatePem, {use: 'sig'}, 'public');

expect(jwk).to.eql(objectAssign({}, expectedPublic, {use: 'sig'}));
expect(jwk).to.eql({...expectedPublic, use: 'sig'});
});

it('should fail to return a private JWK from a public PEM', function() {
Expand Down