diff --git a/lib/x509-ec-parser.js b/lib/x509-ec-parser.js new file mode 100644 index 0000000..a59e20a --- /dev/null +++ b/lib/x509-ec-parser.js @@ -0,0 +1,119 @@ +'use strict'; + +var Enc = require('./encoding.js'); +var x509 = module.exports; + +// 1.2.840.10045.3.1.7 +// prime256v1 (ANSI X9.62 named elliptic curve) +var OBJ_ID_EC_256 = '06 08 2A8648CE3D030107'.replace(/\s+/g, '').toLowerCase(); +// 1.3.132.0.34 +// secp384r1 (SECG (Certicom) named elliptic curve) +var OBJ_ID_EC_384 = '06 05 2B81040022'.replace(/\s+/g, '').toLowerCase(); + +x509.parseSec1 = function parseEcOnlyPrivkey(u8, jwk) { + var index = 7; + var len = 32; + var olen = OBJ_ID_EC_256.length/2; + + if ("P-384" === jwk.crv) { + olen = OBJ_ID_EC_384.length/2; + index = 8; + len = 48; + } + if (len !== u8[index - 1]) { + throw new Error("Unexpected bitlength " + len); + } + + // private part is d + var d = u8.slice(index, index + len); + // compression bit index + var ci = index + len + 2 + olen + 2 + 3; + var c = u8[ci]; + var x, y; + + if (0x04 === c) { + y = u8.slice(ci + 1 + len, ci + 1 + len + len); + } else if (0x02 !== c) { + throw new Error("not a supported EC private key"); + } + x = u8.slice(ci + 1, ci + 1 + len); + + return { + kty: jwk.kty + , crv: jwk.crv + , d: Enc.bufToUrlBase64(d) + //, dh: Enc.bufToHex(d) + , x: Enc.bufToUrlBase64(x) + //, xh: Enc.bufToHex(x) + , y: Enc.bufToUrlBase64(y) + //, yh: Enc.bufToHex(y) + }; +}; + +x509.parsePkcs8 = function parseEcPkcs8(u8, jwk) { + var index = 24 + (OBJ_ID_EC_256.length/2); + var len = 32; + if ("P-384" === jwk.crv) { + index = 24 + (OBJ_ID_EC_384.length/2) + 2; + len = 48; + } + + //console.log(index, u8.slice(index)); + if (0x04 !== u8[index]) { + //console.log(jwk); + throw new Error("privkey not found"); + } + var d = u8.slice(index+2, index+2+len); + var ci = index+2+len+5; + var xi = ci+1; + var x = u8.slice(xi, xi + len); + var yi = xi+len; + var y; + if (0x04 === u8[ci]) { + y = u8.slice(yi, yi + len); + } else if (0x02 !== u8[ci]) { + throw new Error("invalid compression bit (expected 0x04 or 0x02)"); + } + + return { + kty: jwk.kty + , crv: jwk.crv + , d: Enc.bufToUrlBase64(d) + //, dh: Enc.bufToHex(d) + , x: Enc.bufToUrlBase64(x) + //, xh: Enc.bufToHex(x) + , y: Enc.bufToUrlBase64(y) + //, yh: Enc.bufToHex(y) + }; +}; + +x509.parseSpki = function parsePem(u8, jwk) { + var ci = 16 + OBJ_ID_EC_256.length/2; + var len = 32; + + if ("P-384" === jwk.crv) { + ci = 16 + OBJ_ID_EC_384.length/2; + len = 48; + } + + var c = u8[ci]; + var xi = ci + 1; + var x = u8.slice(xi, xi + len); + var yi = xi + len; + var y; + if (0x04 === c) { + y = u8.slice(yi, yi + len); + } else if (0x02 !== c) { + throw new Error("not a supported EC private key"); + } + + return { + kty: jwk.kty + , crv: jwk.crv + , x: Enc.bufToUrlBase64(x) + //, xh: Enc.bufToHex(x) + , y: Enc.bufToUrlBase64(y) + //, yh: Enc.bufToHex(y) + }; +}; +x509.parsePkix = x509.parseSpki; diff --git a/lib/x509-parser.js b/lib/x509-parser.js index 870d5ba..84691da 100644 --- a/lib/x509-parser.js +++ b/lib/x509-parser.js @@ -1,119 +1,76 @@ 'use strict'; -var Enc = require('./encoding.js'); var x509 = module.exports; -// 1.2.840.10045.3.1.7 -// prime256v1 (ANSI X9.62 named elliptic curve) -var OBJ_ID_EC = '06 08 2A8648CE3D030107'.replace(/\s+/g, '').toLowerCase(); -// 1.3.132.0.34 -// secp384r1 (SECG (Certicom) named elliptic curve) -var OBJ_ID_EC_384 = '06 05 2B81040022'.replace(/\s+/g, '').toLowerCase(); +var PEM = require('./pem-parser.js'); +var EC = require('./x509-ec-parser.js'); +var RSA = require('./x509-rsa-parser.js'); -x509.parseSec1 = function parseEcOnlyPrivkey(u8, jwk) { - var index = 7; - var len = 32; - var olen = OBJ_ID_EC.length/2; - - if ("P-384" === jwk.crv) { - olen = OBJ_ID_EC_384.length/2; - index = 8; - len = 48; +x509.parse = function (opts) { + var pem = opts.pem; + var der = opts.der; + var typ; + var pub; + var prv; + var ec; + var rsa; + if ('string' === opts.key) { + pem = opts.key; + } else if (opts.key && opts.key.length) { + der = opts.key; } - if (len !== u8[index - 1]) { - throw new Error("Unexpected bitlength " + len); + if (pem) { + pem = PEM.parseBlock(pem); + der = pem.bytes; + typ = pem.type; + pub = /PUBLIC KEY/.test(typ); + prv = /PRIVATE KEY/.test(typ); + ec = /EC P/.test(typ); + rsa = /RSA P/.test(typ); } - // private part is d - var d = u8.slice(index, index + len); - // compression bit index - var ci = index + len + 2 + olen + 2 + 3; - var c = u8[ci]; - var x, y; - - if (0x04 === c) { - y = u8.slice(ci + 1 + len, ci + 1 + len + len); - } else if (0x02 !== c) { - throw new Error("not a supported EC private key"); + // Try EC Private and Public keys + if (!rsa) { + if (!pub) { + try { + return EC.parsePkcs8(der); + } catch(e) { + try { + return EC.parseSec1(der); + } catch(e) { + // ignore + } + } + } + if (!prv) { + return EC.parseSpki(der); + } } - x = u8.slice(ci + 1, ci + 1 + len); - return { - kty: jwk.kty - , crv: jwk.crv - , d: Enc.bufToUrlBase64(d) - //, dh: Enc.bufToHex(d) - , x: Enc.bufToUrlBase64(x) - //, xh: Enc.bufToHex(x) - , y: Enc.bufToUrlBase64(y) - //, yh: Enc.bufToHex(y) - }; + // Try RSA Private and Public keys + if (!ec) { + if (!pub) { + try { + return RSA.parsePkcs8(der); + } catch(e) { + try { + return RSA.parsePkcs1(der); + } catch(e) { + // ignore + } + } + } + if (!prv) { + try { + return RSA.parseSpki(der); + } catch(e) { + try { + return RSA.parsePublicPkcs1(der); + } catch(e) { + // ignore + } + } + } + } + throw new Error("doesn't appear to be a valid key file. Tried PKCS1, SEC1, PKCS8, and SPKI/PKIX"); }; - -x509.parsePkcs8 = function parseEcPkcs8(u8, jwk) { - var index = 24 + (OBJ_ID_EC.length/2); - var len = 32; - if ("P-384" === jwk.crv) { - index = 24 + (OBJ_ID_EC_384.length/2) + 2; - len = 48; - } - - //console.log(index, u8.slice(index)); - if (0x04 !== u8[index]) { - //console.log(jwk); - throw new Error("privkey not found"); - } - var d = u8.slice(index+2, index+2+len); - var ci = index+2+len+5; - var xi = ci+1; - var x = u8.slice(xi, xi + len); - var yi = xi+len; - var y; - if (0x04 === u8[ci]) { - y = u8.slice(yi, yi + len); - } else if (0x02 !== u8[ci]) { - throw new Error("invalid compression bit (expected 0x04 or 0x02)"); - } - - return { - kty: jwk.kty - , crv: jwk.crv - , d: Enc.bufToUrlBase64(d) - //, dh: Enc.bufToHex(d) - , x: Enc.bufToUrlBase64(x) - //, xh: Enc.bufToHex(x) - , y: Enc.bufToUrlBase64(y) - //, yh: Enc.bufToHex(y) - }; -}; - -x509.parseSpki = function parsePem(u8, jwk) { - var ci = 16 + OBJ_ID_EC.length/2; - var len = 32; - - if ("P-384" === jwk.crv) { - ci = 16 + OBJ_ID_EC_384.length/2; - len = 48; - } - - var c = u8[ci]; - var xi = ci + 1; - var x = u8.slice(xi, xi + len); - var yi = xi + len; - var y; - if (0x04 === c) { - y = u8.slice(yi, yi + len); - } else if (0x02 !== c) { - throw new Error("not a supported EC private key"); - } - - return { - kty: jwk.kty - , crv: jwk.crv - , x: Enc.bufToUrlBase64(x) - //, xh: Enc.bufToHex(x) - , y: Enc.bufToUrlBase64(y) - //, yh: Enc.bufToHex(y) - }; -}; -x509.parsePkix = x509.parseSpki;