(function (exports) { 'use strict'; // 1.2.840.113549.1.1.1 // rsaEncryption (PKCS #1) var OBJ_ID_RSA = '06 09 2A864886F70D010101'.replace(/\s+/g, '').toLowerCase(); // 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(); // 30 sequence // 03 bit string // 05 null // 06 object id function parsePem(pem) { var typ; var pub; var crv; var der = fromBase64(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'; } else if (/ EC/.test(line)) { typ = 'EC'; } } return !/---/.test(line); }).join('')); if (!typ || 'EC' === typ) { var hex = toHex(der).toLowerCase(); // This is the RSA object ID if (-1 !== hex.indexOf(OBJ_ID_RSA)) { typ = 'RSA'; } else if (-1 !== hex.indexOf(OBJ_ID_EC)) { typ = 'EC'; crv = 'P-256'; } else { // TODO more than just P-256 console.warn("unsupported ec curve"); } } return { typ: typ, pub: pub, der: der, crv: crv }; } function toHex(ab) { var hex = []; var u8 = new Uint8Array(ab); var size = u8.byteLength; var i; var h; for (i = 0; i < size; i += 1) { h = u8[i].toString(16); if (2 === h.length) { hex.push(h); } else { hex.push('0' + h); } } return hex.join(''); } function fromHex(hex) { if ('undefined' !== typeof Buffer) { return Buffer.from(hex, 'hex'); } var ab = new ArrayBuffer(hex.length/2); var i; var j; ab = new Uint8Array(ab); for (i = 0, j = 0; i < (hex.length/2); i += 1) { ab[i] = parseInt(hex.slice(j, j+1), 16); j += 2; } return ab.buffer; } function readEcPrivkey(der) { return readEcPubkey(der); } function readEcPubkey(der) { // the key is the last 520 bits of both the private key and the public key // he 3 bits prior identify the key as var x, y; var compressed; var keylen = 32; var offset = 64; var headerSize = 4; var header = toHex(der.slice(der.byteLength - (offset + headerSize), der.byteLength - offset)); if ('03420004' !== header) { offset = 32; header = toHex(der.slice(der.byteLength - (offset + headerSize), der.byteLength - offset)); if ('03420002' !== header) { throw new Error("not a valid EC P-256 key (expected 0x0342004 or 0x0342002 as pub key preamble, but found " + header + ")"); } } console.log('header', header); console.log('offset', offset); // The one good thing that came from the b***kchain hysteria: good EC documentation // https://davidederosa.com/basic-blockchain-programming/elliptic-curve-keys/ compressed = ('2' === header[header.byteLength -1]); console.log(der.byteLength - offset, (der.byteLength - offset) + keylen); x = der.slice(der.byteLength - offset, (der.byteLength - offset) + keylen); if (!compressed) { y = der.slice(der.byteLength - keylen, der.byteLength); } return { x: x , y: y || null }; } function readPubkey(der) { var offset = 28 + 5; // header plus size var ksBytes = der.slice(30, 32); // not sure why it shows 257 instead of 256 var keysize = new DataView(ksBytes).getUint16(0, false) - 1; var pub = der.slice(offset, offset + keysize); return pub; } function readPrivkey(der) { var offset = 7 + 5; // header plus size var ksBytes = der.slice(9, 11); // not sure why it shows 257 instead of 256 var keysize = new DataView(ksBytes).getUint16(0, false) - 1; var pub = der.slice(offset, offset + keysize); return pub; } // I used OpenSSL to create RSA keys with sizes 2048 and 4096. // Then I used https://lapo.it/asn1js/ to see which bits changed. // And I created a template from the bits that do and don't. // No ASN.1 and X.509 parsers or generators. Yay! var rsaAsn1Head = ( '30 82 xx 22 30 0D 06 09' + '2A 86 48 86 F7 0D 01 01' + '01 05 00 03 82 xx 0F 00' + '30 82 xx 0A 02 82 xx 01' + '00').replace(/\s+/g, ''); var rsaAsn1Foot = ('02 03 01 00 01').replace(/\s+/g, ''); function toRsaPub(pub) { // 256 // 2048-bit var len = '0' + (pub.byteLength / 256); var head = rsaAsn1Head.replace(/xx/g, len); var headSize = (rsaAsn1Head.length / 2); var foot = rsaAsn1Foot; var footSize = (foot.length / 2); var size = headSize + pub.byteLength + footSize; var der = new Uint8Array(new ArrayBuffer(size)); var i, j; for (i = 0, j = 0; i < headSize; i += 1) { der[i] = parseInt(head.slice(j,j+2), 16); j += 2; } pub = new Uint8Array(pub); for (i = 0; i < pub.byteLength; i += 1) { der[headSize + i] = pub[i]; } for (i = 0, j = 0; i < footSize; i += 1) { der[headSize + pub.byteLength + i] = parseInt(foot.slice(j,j+2), 16); j += 2; } return der.buffer; } function h(d) { d = d.toString(16); if (d.length % 2) { return '0' + d; } return d; } // I used OpenSSL to create EC keys with the P-256 curve TODO P-384. // Then I used https://lapo.it/asn1js/ to see which bits changed. // And I created a template from the bits that do and don't. // No ASN.1 and X.509 parsers or generators. Yay! var ecP256Asn1Head = ( '30 {n}' // 0x59 = 89 bytes // sequence + '30 _13' // 0x13 = 19 bytes // sequence + '06 _07 2A 86 48 CE 3D 02 01' // 0x07 = 7 bytes // 1.2.840.10045.2.1 ecPublicKey (ANSI X9.62 public key type) + '06 _08 2A 86 48 CE 3D 03 01 07' // 0x08 = 8 bytes // 1.2.840.10045.3.1.7 prime256v1 (ANSI X9.62 named elliptic curve) + '03 {p} 00 {c} {x} {y}' // xlen+ylen+1+1 // bit string ).replace(/[\s_]+/g, '') ; function toEcPub(x, y) { // 256 // 2048-bit var keylen = toHex([ x.byteLength + (y && y.byteLength || 0) ]); var head = ecP256Asn1Head .replace(/{n}/, h(0x13 + 2 + 2 + keylen)) .replace(/{p}/, h(2 + keylen)) .replace(/{c}/, y && '04' || '02') .replace(/{x}/, toHex(x)) .replace(/{y}/, y && toHex(y) || '') ; return fromHex(head); } function formatAsPem(str) { var finalString = ''; while (str.length > 0) { finalString += str.substring(0, 64) + '\n'; str = str.substring(64); } return finalString; } function formatAsPrivatePem(str, privacy, pemName) { var pemstr = (pemName ? pemName + ' ' : ''); var privstr = (privacy ? privacy + ' ' : ''); var finalString = '-----BEGIN ' + pemstr + privstr + 'KEY-----\n'; finalString += formatAsPem(str); finalString += '-----END ' + pemstr + privstr + 'KEY-----'; return finalString; } function formatAsPublicPem(str) { var privacy = 'PUBLIC'; var pemName = ''; var pemstr = (pemName ? pemName + ' ' : ''); var privstr = (privacy ? privacy + ' ' : ''); var finalString = '-----BEGIN ' + pemstr + privstr + 'KEY-----\n'; finalString += formatAsPem(str); finalString += '-----END ' + pemstr + privstr + 'KEY-----'; return finalString; } function toBase64(der) { if ('undefined' === typeof btoa) { return Buffer.from(der).toString('base64'); } var chs = []; der = new Uint8Array(der); der.forEach(function (b) { chs.push(String.fromCharCode(b)); }); return btoa(chs.join('')); } function fromBase64(b64) { var buf; var ab; if ('undefined' === typeof atob) { buf = Buffer.from(b64, 'base64'); return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); } buf = atob(b64); ab = new ArrayBuffer(buf.length); ab = new Uint8Array(ab); buf.split('').forEach(function (ch, i) { ab[i] = ch.charCodeAt(0); }); return ab.buffer; } exports.parsePem = parsePem; exports.toBase64 = toBase64; exports.toRsaPub = toRsaPub; exports.toEcPub = toEcPub; exports.formatAsPublicPem = formatAsPublicPem; exports.formatAsPrivatePem = formatAsPrivatePem; exports.formatAsPem = formatAsPem; exports.readPubkey = readPubkey; exports.readEcPubkey = readEcPubkey; exports.readEcPrivkey = readEcPrivkey; exports.readPrivkey = readPrivkey; exports.toHex = toHex; exports.fromHex = fromHex; }('undefined' !== typeof module ? module.exports: window));