From 8d2ebe77fee95ab261b499c67fa6551a2ce2eed0 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 22 Nov 2018 00:39:43 -0700 Subject: [PATCH] a squeaky clean start --- bin/rasha.js | 89 ++++++++++----------------------------- index.js | 2 + lib/{uasn1.js => asn1.js} | 24 +++++------ lib/encoding.js | 56 ++++++++++++++++++++++++ lib/pem.js | 39 +++++++++++++++++ lib/rasha.js | 56 ++++++++++++++++++++++++ lib/ssh.js | 10 +++++ 7 files changed, 196 insertions(+), 80 deletions(-) create mode 100644 index.js rename lib/{uasn1.js => asn1.js} (78%) create mode 100644 lib/encoding.js create mode 100644 lib/pem.js create mode 100644 lib/ssh.js diff --git a/bin/rasha.js b/bin/rasha.js index 9485428..145d05c 100755 --- a/bin/rasha.js +++ b/bin/rasha.js @@ -1,76 +1,33 @@ #!/usr/bin/env node 'use strict'; -var fs = require('fs'); -var infile = process.argv[2]; - -var pem = fs.readFileSync(infile, 'ascii'); -var b64 = pem.split(/\n/).filter(function (line) { - // TODO test if RSA key - if (/^---/.test(line)) { - return false; - } - return true; -}).join(''); -var buf = Buffer.from(b64, 'base64'); - -var ELOOP = "uASN1.js Error: iterated over 100+ elements (probably a malformed file)"; -var EDEEP = "uASN1.js Error: element nested 100+ layers deep (probably a malformed file)"; -var ASN1 = require('../lib/uasn1.js'); -/* -function ASN1(buf, depth) { - if (depth >= 100) { - throw new Error(EDEEP); - } - // start after type (0) and lengthSize (1) - var index = 2; - var asn1 = { - type: buf[0] - , lengthSize: 0 - , length: buf[1] - }; - var child; - var i = 0; - if (0x80 & asn1.length) { - asn1.lengthSize = 0x7f & asn1.length; - // I think that buf->hex->int solves the problem of Endianness... not sure - asn1.length = parseInt(buf.slice(index, index + asn1.lengthSize).toString('hex'), 16); - // add back the original byte indicating lengthSize - index += 1; - } +var fs = require('fs'); +var Rasha = require('../index.js'); - // this is a primitive value type - if (asn1.type <= 0x06) { - i += 1; - asn1.value = buf.slice(index, index + asn1.length); - return asn1; - } +var infile = process.argv[2]; +var format = process.argv[3]; - asn1.children = []; - while (i < 100 && index < buf.byteLength) { - child = ASN1(buf.slice(index), (depth || 0) + 1); - index += (2 + child.lengthSize + child.length); - asn1.children.push(child); - } - if (i >= 100) { throw new Error(ELOOP); } +var key = fs.readFileSync(infile, 'ascii'); - return asn1; +try { + key = JSON.parse(key); +} catch(e) { + // ignore } -*/ -var asn1 = ASN1.parse(buf); -var ws = ''; -function write(asn1) { - console.log(ws, 'ch', Buffer.from([asn1.type]).toString('hex'), asn1.length); - if (!asn1.children) { - return; - } - asn1.children.forEach(function (a, i) { - ws += '\t'; - write(a); - ws = ws.slice(1); +if ('string' === typeof key) { + var pub = (-1 !== [ 'public', 'spki', 'pkix' ].indexOf(format)); + Rasha.import({ pem: key, public: (pub || format) }).then(function (jwk) { + console.log(JSON.stringify(jwk, null, 2)); + }).catch(function (err) { + console.error(err); + process.exit(1); + }); +} else { + Rasha.export({ jwk: key, format: format }).then(function (pem) { + console.log(pem); + }).catch(function (err) { + console.error(err); + process.exit(2); }); } -console.log(JSON.stringify(asn1, null, 2)); -//console.log(asn1); -write(asn1); diff --git a/index.js b/index.js new file mode 100644 index 0000000..c538483 --- /dev/null +++ b/index.js @@ -0,0 +1,2 @@ +'use strict'; +module.exports = require('./lib/rasha.js'); diff --git a/lib/uasn1.js b/lib/asn1.js similarity index 78% rename from lib/uasn1.js rename to lib/asn1.js index 872a4fe..504dafc 100644 --- a/lib/uasn1.js +++ b/lib/asn1.js @@ -1,18 +1,16 @@ 'use strict'; -var ELOOP = "uASN1.js Error: iterated over 100+ elements (probably a malformed file)"; -var EDEEP = "uASN1.js Error: element nested 100+ layers deep (probably a malformed file)"; -// Container Types are Sequence 0x30, Octect String 0x04, Array? (0xA0, 0xA1) -// Value Types are Integer 0x02, Bit String 0x03, Null 0x05, Object ID 0x06, -// Sometimes Bit String is used as a container (RSA Pub Spki) -var VTYPES = [ 0x02, 0x03, 0x05, 0x06 ]; - var ASN1 = module.exports = function ASN1() { }; +ASN1.ELOOP = "uASN1.js Error: iterated over 15+ elements (probably a malformed file)"; +ASN1.EDEEP = "uASN1.js Error: element nested 10+ layers deep (probably a malformed file)"; +// Container Types are Sequence 0x30, Octect String 0x04, Array? (0xA0, 0xA1) +// Value Types are Integer 0x02, Bit String 0x03, Null 0x05, Object ID 0x06, +// Sometimes Bit String is used as a container (RSA Pub Spki) +ASN1.VTYPES = [ 0x02, 0x03, 0x05, 0x06 ]; ASN1.parse = function parseAsn1(buf, depth) { - console.log(''); - if (depth >= 100) { throw new Error(EDEEP); } + if (depth >= 10) { throw new Error(ASN1.EDEEP); } var index = 2; // we know, at minimum, data starts after type (0) and lengthSize (1) var asn1 = { type: buf[0], lengthSize: 0, length: buf[1] }; @@ -30,27 +28,25 @@ ASN1.parse = function parseAsn1(buf, depth) { // High-order bit Integers have a leading 0x00 to signify that they are positive. // Bit Streams use the first byte to signify padding, which x.509 doesn't use. - console.log(buf[index], asn1.type); if (0x00 === buf[index] && (0x02 === asn1.type || 0x03 === asn1.type)) { - console.log('chomp'); index += 1; adjust = -1; } // this is a primitive value type - if (-1 !== VTYPES.indexOf(asn1.type)) { + if (-1 !== ASN1.VTYPES.indexOf(asn1.type)) { asn1.value = buf.slice(index, index + asn1.length + adjust); return asn1; } asn1.children = []; - while (iters < 100 && index < buf.byteLength) { + while (iters < 15 && index < buf.byteLength) { iters += 1; child = ASN1.parse(buf.slice(index, index + asn1.length), (depth || 0) + 1); index += (2 + child.lengthSize + child.length); asn1.children.push(child); } - if (iters >= 100) { throw new Error(ELOOP); } + if (iters >= 15) { throw new Error(ASN1.ELOOP); } return asn1; }; diff --git a/lib/encoding.js b/lib/encoding.js new file mode 100644 index 0000000..753afaa --- /dev/null +++ b/lib/encoding.js @@ -0,0 +1,56 @@ +'use strict'; + +var Enc = module.exports; + +Enc.bufToHex = function toHex(u8) { + var hex = []; + var i, h; + + for (i = 0; i < u8.byteLength; i += 1) { + h = u8[i].toString(16); + if (2 !== h.length) { h = '0' + h; } + hex.push(h); + } + + return hex.join('').toLowerCase(); +}; + +/* +Enc.strToBin = function strToBin(str) { + var escstr = encodeURIComponent(str); + // replaces any uri escape sequence, such as %0A, + // with binary escape, such as 0x0A + var binstr = escstr.replace(/%([0-9A-F]{2})/g, function(match, p1) { + return String.fromCharCode(parseInt(p1, 16)); + }); + + return binstr; +}; +*/ + +/* +Enc.strToBase64 = function strToBase64(str) { + // node automatically can tell the difference + // between uc2 (utf-8) strings and binary strings + // so we don't have to re-encode the strings + return Buffer.from(str).toString('base64'); +}; +*/ + +/* +Enc.urlBase64ToBase64 = function urlsafeBase64ToBase64(str) { + var r = str % 4; + if (2 === r) { + str += '=='; + } else if (3 === r) { + str += '='; + } + return str.replace(/-/g, '+').replace(/_/g, '/'); +}; +*/ + +Enc.base64ToBuf = function base64ToBuf(str) { + // always convert from urlsafe base64, just in case + //return Buffer.from(Enc.urlBase64ToBase64(str)).toString('base64'); + return Buffer.from(str, 'base64'); +}; diff --git a/lib/pem.js b/lib/pem.js new file mode 100644 index 0000000..0cad9e5 --- /dev/null +++ b/lib/pem.js @@ -0,0 +1,39 @@ +'use strict'; + +var PEM = module.exports; +var Enc = require('./encoding.js'); + +PEM.RSA_OBJID = '06 09 2A864886F70D010101' + .replace(/\s+/g, '').toLowerCase(); + +PEM.parseBlock = function pemToDer(pem) { + var typ; + var pub; + var hex; + var der = Enc.base64ToBuf(pem.split(/\n/).filter(function (line, i) { + if (0 === i) { + if (/ PUBLIC /.test(line)) { + pub = true; + } else if (/ PRIVATE /.test(line)) { + pub = false; + } + if (/ RSA /.test(line)) { + typ = 'RSA'; + } + } + return !/---/.test(line); + }).join('')); + + if (!typ) { + hex = Enc.bufToHex(der); + if (-1 !== hex.indexOf(PEM.RSA_OBJID)) { + typ = 'RSA'; + } + } + if (!typ) { + console.warn("Definitely not an RSA PKCS#8 because there's no RSA Object ID in the DER body."); + console.warn("Probably not an RSA PKCS#1 because 'RSA' wasn't in the PEM type string."); + } + + return { kty: typ, pub: pub, der: der }; +}; diff --git a/lib/rasha.js b/lib/rasha.js index 8aa5e1e..d630ca8 100644 --- a/lib/rasha.js +++ b/lib/rasha.js @@ -1,3 +1,59 @@ 'use strict'; var RSA = module.exports; +var ASN1 = require('./asn1.js'); +//var Enc = require('./encoding.js'); +var PEM = require('./pem.js'); +var SSH = require('./ssh.js'); + + +/* +RSAPrivateKey ::= SEQUENCE { + version Version, + modulus INTEGER, -- n + publicExponent INTEGER, -- e + privateExponent INTEGER, -- d + prime1 INTEGER, -- p + prime2 INTEGER, -- q + exponent1 INTEGER, -- d mod (p-1) + exponent2 INTEGER, -- d mod (q-1) + coefficient INTEGER, -- (inverse of q) mod p + otherPrimeInfos OtherPrimeInfos OPTIONAL +} +*/ + +/*global Promise*/ +RSA.parse = function parseEc(opts) { + return Promise.resolve().then(function () { + if (!opts || !opts.pem || 'string' !== typeof opts.pem) { + throw new Error("must pass { pem: pem } as a string"); + } + if (0 === opts.pem.indexOf('ssh-rsa ')) { + return SSH.parse(opts.pem); + } + var pem = opts.pem; + var block = PEM.parseBlock(pem); + //var hex = toHex(u8); + //var jwk = { kty: 'RSA' }; + + var asn1 = ASN1.parse(block.der); + var ws = ''; + function write(asn1) { + console.log(ws, 'ch', Buffer.from([asn1.type]).toString('hex'), asn1.length); + if (!asn1.children) { + return; + } + asn1.children.forEach(function (a) { + ws += '\t'; + write(a); + ws = ws.slice(1); + }); + } + //console.log(JSON.stringify(asn1, null, 2)); + console.log(asn1); + write(asn1); + + return { kty: 'RSA' }; + }); +}; +RSA.toJwk = RSA.import = RSA.parse; diff --git a/lib/ssh.js b/lib/ssh.js new file mode 100644 index 0000000..28fa201 --- /dev/null +++ b/lib/ssh.js @@ -0,0 +1,10 @@ +'use strict'; + +var SSH = module.exports; + + // 7 s s h - r s a +SSH.RSA = '00000007 73 73 68 2d 72 73 61'.replace(/\s+/g, '').toLowerCase(); + +SSH.parse = function (pem) { + +};