293 lines
8.0 KiB
JavaScript
293 lines
8.0 KiB
JavaScript
(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));
|