v0.0.2: WIP, useful as an example
This commit is contained in:
parent
ab5e1a0ee8
commit
30e617ada1
|
@ -1,6 +1,9 @@
|
||||||
This is being ported from code from rsa-compat.js, greenlock.html (bacme.js), and others.
|
This is being ported from code from rsa-compat.js, greenlock.html (bacme.js), and others.
|
||||||
|
|
||||||
Today is 2018-10-10 come back in a week.
|
This is my project for the weekend. I expect to be finished today (Monday Nov 12th, 2018)
|
||||||
|
* 2018-10-10 (Saturday) work has begun
|
||||||
|
* 2018-10-11 (Sunday) W00T! got a CSR generated for RSA with VanillaJS ArrayBuffer
|
||||||
|
* 2018-10-12 (Monday) Figuring out ECDSA CSRs right now
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Keypairs™ for node.js
|
Keypairs™ for node.js
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
function convert(name) {
|
||||||
|
var ext = path.extname(name);
|
||||||
|
var csr = fs.readFileSync(name, 'ascii').split(/\n/).filter(function (line) {
|
||||||
|
return !/---/.test(line);
|
||||||
|
}).join('');
|
||||||
|
console.log(csr);
|
||||||
|
var der = Buffer.from(csr, 'base64');
|
||||||
|
fs.writeFileSync(name.replace(new RegExp('\\' + ext + '$'), '') + '.der', der);
|
||||||
|
}
|
||||||
|
|
||||||
|
convert(process.argv[2]);
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "keypairs",
|
"name": "keypairs",
|
||||||
"version": "0.0.1",
|
"version": "0.0.2",
|
||||||
"description": "Interchangeably use RSA & ECDSA with PEM and JWK for Signing, Verifying, CSR generation and JOSE. Ugh... that was a mouthful.",
|
"description": "Interchangeably use RSA & ECDSA with PEM and JWK for Signing, Verifying, CSR generation and JOSE. Ugh... that was a mouthful.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var pubkey = require('./pubkey.js');
|
||||||
|
var pubname = process.argv[2];
|
||||||
|
var fs = require('fs');
|
||||||
|
var pem = fs.readFileSync(pubname);
|
||||||
|
|
||||||
|
var key = pubkey.parsePem(pem);
|
||||||
|
if ('RSA' !== key.typ) {
|
||||||
|
throw new Error(key.typ + " not supported");
|
||||||
|
}
|
||||||
|
if (key.pub) {
|
||||||
|
var pubbuf = pubkey.readPubkey(key.der);
|
||||||
|
} else {
|
||||||
|
var pubbuf = pubkey.readPrivkey(key.der);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(pubbuf.byteLength, pubkey.toHex(pubbuf));
|
||||||
|
var der = pubkey.toRsaPub(pubbuf);
|
||||||
|
var b64 = pubkey.toBase64(der);
|
||||||
|
var pem = pubkey.formatAsPublicPem(b64);
|
||||||
|
console.log('Pub:\n');
|
||||||
|
console.log(pem);
|
|
@ -0,0 +1,181 @@
|
||||||
|
(function (exports) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// 30 sequence
|
||||||
|
// 03 bit string
|
||||||
|
// 05 null
|
||||||
|
// 06 object id
|
||||||
|
|
||||||
|
// 00 00 00 00
|
||||||
|
// 30 82 01 22 30 0D 06 09 2A 86 48 86 F7 0D 01 01 01 05 00 03 82 01 0F 00 30 82 01 0A 02 82 01 01
|
||||||
|
// 00 ... 02 03 01 00 01
|
||||||
|
// 30 82 02 22 30 0D 06 09 2A 86 48 86 F7 0D 01 01 01 05 00 03 82 02 0F 00 30 82 02 0A 02 82 02 01
|
||||||
|
// 00 ... 02 03 01 00 01
|
||||||
|
|
||||||
|
function parsePem(pem) {
|
||||||
|
var typ;
|
||||||
|
var pub;
|
||||||
|
var der = fromBase64(pem.toString('ascii').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) {
|
||||||
|
if (pub) {
|
||||||
|
// This is the RSA object ID
|
||||||
|
if ('06092A864886F70D010101'.toLowerCase() === der.slice(6, 6 + 11).toString('hex')) {
|
||||||
|
typ = 'RSA';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { typ: typ, pub: pub, der: der };
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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 formatAsPem(str, privacy, pemName) {
|
||||||
|
var pemstr = (pemName ? pemName + ' ' : '');
|
||||||
|
var privstr = (privacy ? privacy + ' ' : '');
|
||||||
|
var finalString = '-----BEGIN ' + pemstr + privstr + 'KEY-----\n';
|
||||||
|
|
||||||
|
while (str.length > 0) {
|
||||||
|
finalString += str.substring(0, 64) + '\n';
|
||||||
|
str = str.substring(64);
|
||||||
|
}
|
||||||
|
|
||||||
|
finalString = finalString + '-----END ' + pemstr + privstr + 'KEY-----';
|
||||||
|
|
||||||
|
return finalString;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatAsPublicPem(str) {
|
||||||
|
return formatAsPem(str, 'PUBLIC', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
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.formatAsPublicPem = formatAsPublicPem;
|
||||||
|
exports.formatAsPem = formatAsPem;
|
||||||
|
exports.readPubkey = readPubkey;
|
||||||
|
exports.readPrivkey = readPrivkey;
|
||||||
|
exports.toHex = toHex;
|
||||||
|
|
||||||
|
}('undefined' !== typeof module ? module.exports: window));
|
|
@ -0,0 +1,404 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var crypto = require('crypto');
|
||||||
|
var fs = require('fs');
|
||||||
|
var pubkey = require('./pubkey.js');
|
||||||
|
|
||||||
|
var keyname = process.argv[2];
|
||||||
|
var dername = process.argv[3];
|
||||||
|
|
||||||
|
var keypem = fs.readFileSync(keyname);
|
||||||
|
var csrFull = fs.readFileSync(dername);
|
||||||
|
var csrFull = csrFull.buffer.slice(csrFull.byteOffset, csrFull.byteOffset + csrFull.byteLength);
|
||||||
|
|
||||||
|
// these are static ASN.1 segments
|
||||||
|
// The head specifies that there will be 3 segments and a content length
|
||||||
|
// (those segments will be content, signature header, and signature)
|
||||||
|
var csrHead = '30 82 {0seq0len}'.replace(/\s+/g, '');
|
||||||
|
// The tail specifies the RSA256 signature header (and is followed by the signature
|
||||||
|
var csrRsaFoot =
|
||||||
|
( '30 0D 06 09 2A 86 48 86 F7 0D 01 01 0B'
|
||||||
|
+ '05 00'
|
||||||
|
+ '03 82 01 01 00'
|
||||||
|
).replace(/\s+/g, '');
|
||||||
|
var csrDomains = '82 {dlen} {domain.tld}'; // 2+n bytes (type 82?)
|
||||||
|
/*
|
||||||
|
var csrRsaContent =
|
||||||
|
'30 82 {1.1.0seqlen}' // 7+n 4 bytes, sequence
|
||||||
|
+ '02 01 00' // 3 3 bytes, int 0
|
||||||
|
|
||||||
|
+ '30 {3.2.0seqlen}' // 13+n 2 bytes, sequence
|
||||||
|
+ '31 {4.3.0setlen}' // 11+n 2 bytes, set
|
||||||
|
+ '30 {5.4.0seqlen}' // 9+n 2 bytes, sequence
|
||||||
|
+ '06 03 55 04 03' // 7+n 5 bytes, object id (commonName)
|
||||||
|
+ '0C {dlen} {domain.tld}' // 2+n 2+n bytes, utf8string
|
||||||
|
|
||||||
|
+ '30 82 {8.2.2seqlen}' // 19 4 bytes, sequence
|
||||||
|
+ '30 0D' // 15 2 bytes, sequence
|
||||||
|
+ '06 09 2A 86 48 86 F7 0D 01 01 01' // 13 11 bytes, rsaEncryption (PKCS #1)
|
||||||
|
+ '05 00' // 2 2 bytes, null (why?)
|
||||||
|
+ '03 82 {12.3.0bslen} 00' // +18+m 5 bytes, bit string [01 0F]
|
||||||
|
+ '30 82 {13.4.0seqlen}' // +13+m 4 bytes, sequence
|
||||||
|
+ '02 82 {klen} 00 {key}' // +9+m 4+1+n bytes, int (RSA Pub Key)
|
||||||
|
+ '02 03 {mod}' // +5 5 bytes, key and modules [01 00 01]
|
||||||
|
|
||||||
|
+ 'A0 {16.2.3ellen}' // 30+n 2 bytes, ?? [4B]
|
||||||
|
+ '30 {17.3.9seqlen}' // 28+n 2 bytes, sequence
|
||||||
|
+ '06 09 2A 86 48 86 F7 0D 01 09 0E' // 26+n 11 bytes, object id (extensionRequest (PKCS #9 via CRMF))
|
||||||
|
+ '31 {19.5.0setlen}' // 15+n 2 bytes, set
|
||||||
|
+ '30 {20.6.0seqlen}' // 13+n 2 bytes, sequence
|
||||||
|
+ '30 {21.7.0seqlen}' // 11+n 2 bytes, sequence
|
||||||
|
+ '06 03 55 1D 11' // 9+n 5 bytes, object id (subjectAltName (X.509 extension))
|
||||||
|
+ '04 {23.8.0octlen}' // 4+n 2 bytes, octet string
|
||||||
|
+ '30 {24.9.0seqlen}' // 2+n 2 bytes, sequence
|
||||||
|
+ '{altnames}' // n n bytes
|
||||||
|
;
|
||||||
|
*/
|
||||||
|
|
||||||
|
function privateToPub(pem) {
|
||||||
|
var pubbuf;
|
||||||
|
var key = pubkey.parsePem(pem);
|
||||||
|
if ('RSA' !== key.typ) {
|
||||||
|
throw new Error(key.typ + " not supported");
|
||||||
|
}
|
||||||
|
if (key.pub) {
|
||||||
|
pubbuf = pubkey.readPubkey(key.der);
|
||||||
|
} else {
|
||||||
|
pubbuf = pubkey.readPrivkey(key.der);
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(pubbuf.byteLength, pubkey.toHex(pubbuf));
|
||||||
|
var der = pubkey.toRsaPub(pubbuf);
|
||||||
|
var b64 = pubkey.toBase64(der);
|
||||||
|
return pubkey.formatAsPublicPem(b64);
|
||||||
|
}
|
||||||
|
|
||||||
|
function strToHex(str) {
|
||||||
|
return str.split('').map(function (ch) {
|
||||||
|
var h = ch.charCodeAt(0).toString(16);
|
||||||
|
if (2 === h.length) {
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
return '0' + h;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function pubToPem(pubbuf) {
|
||||||
|
var der = pubkey.toRsaPub(pubbuf);
|
||||||
|
var b64 = pubkey.toBase64(der);
|
||||||
|
return pubkey.formatAsPublicPem(b64);
|
||||||
|
}
|
||||||
|
|
||||||
|
var sigend = (csrFull.byteLength - (2048 / 8));
|
||||||
|
var sig = csrFull.slice(sigend);
|
||||||
|
|
||||||
|
console.log();
|
||||||
|
console.log();
|
||||||
|
console.log('csr (' + csrFull.byteLength + ')');
|
||||||
|
console.log(pubkey.toHex(csrFull));
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
// First 4 bytes define Segment, segment length, and content length
|
||||||
|
console.log(sigend, csrRsaFoot, csrRsaFoot.length/2);
|
||||||
|
var csrbody = csrFull.slice(4, sigend - (csrRsaFoot.length/2));
|
||||||
|
console.log('csr body (' + csrbody.byteLength + ')');
|
||||||
|
console.log(pubkey.toHex(csrbody));
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
var csrpub = csrFull.slice(63 + 5, 63 + 5 + 256);
|
||||||
|
console.log('csr pub (' + csrpub.byteLength + ')');
|
||||||
|
console.log(pubkey.toHex(csrpub));
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
console.log('sig (' + sig.byteLength + ')');
|
||||||
|
console.log(pubkey.toHex(sig));
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
var csrpem = pubToPem(csrpub);
|
||||||
|
console.log(csrpem);
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
var prvpem = privateToPub(keypem);
|
||||||
|
console.log(prvpem);
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
if (csrpem === prvpem) {
|
||||||
|
console.log("Public Keys Match");
|
||||||
|
} else {
|
||||||
|
throw new Error("public key read from keyfile doesn't match public key read from CSR");
|
||||||
|
}
|
||||||
|
|
||||||
|
function h(d) {
|
||||||
|
d = d.toString(16);
|
||||||
|
if (d.length % 2) {
|
||||||
|
return '0' + d;
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 createCsrBodyRsa(domains, csrpub) {
|
||||||
|
var altnames = domains.map(function (d) {
|
||||||
|
return csrDomains.replace(/{dlen}/, h(d.length)).replace(/{domain\.tld}/, strToHex(d));
|
||||||
|
}).join('').replace(/\s+/g, '');
|
||||||
|
var publen = csrpub.byteLength;
|
||||||
|
var sublen = domains[0].length;
|
||||||
|
var sanlen = (altnames.length/2);
|
||||||
|
|
||||||
|
var body = [ '30 82 {1.1.0seqlen}' // 4 bytes, sequence
|
||||||
|
.replace(/{[^}]+}/, h(
|
||||||
|
3
|
||||||
|
+ 13 + sublen
|
||||||
|
+ 38 + publen
|
||||||
|
+ 30 + sanlen
|
||||||
|
))
|
||||||
|
|
||||||
|
// #0 Total 3
|
||||||
|
, '02 01 00' // 3 bytes, int 0
|
||||||
|
|
||||||
|
// #1 Total 2+11+n
|
||||||
|
, '30 {3.2.0seqlen}' // 2 bytes, sequence
|
||||||
|
.replace(/{[^}]+}/, h(2+2+5+2+sublen))
|
||||||
|
, '31 {4.3.0setlen}' // 2 bytes, set
|
||||||
|
.replace(/{[^}]+}/, h(2+5+2+sublen))
|
||||||
|
, '30 {5.4.0seqlen}' // 2 bytes, sequence
|
||||||
|
.replace(/{[^}]+}/, h(5+2+sublen))
|
||||||
|
, '06 03 55 04 03' // 5 bytes, object id (commonName)
|
||||||
|
, '0C {dlen} {domain.tld}' // 2+n bytes, utf8string
|
||||||
|
.replace(/{dlen}/, h(sublen))
|
||||||
|
.replace(/{domain\.tld}/, strToHex(domains[0]))
|
||||||
|
|
||||||
|
// #2 Total 4+28+n+1+5
|
||||||
|
, '30 82 {8.2.2seqlen}' // 4 bytes, sequence
|
||||||
|
.replace(/{[^}]+}/, h(2+11+2+4+1+4+4+publen+1+5))
|
||||||
|
, '30 0D' // 2 bytes, sequence
|
||||||
|
, '06 09 2A 86 48 86 F7 0D 01 01 01' // 11 bytes, rsaEncryption (PKCS #1)
|
||||||
|
, '05 00' // 2 bytes, null (why?)
|
||||||
|
, '03 82 {12.3.0bslen} 00' // 4+1 bytes, bit string [01 0F]
|
||||||
|
.replace(/{[^}]+}/, h(1+4+4+publen+1+5))
|
||||||
|
, '30 82 {13.4.0seqlen}' // 4 bytes, sequence
|
||||||
|
.replace(/{[^}]+}/, h(4+publen+1+5))
|
||||||
|
, '02 82 {klen} 00 {key}' // 4+n bytes, int (RSA Pub Key)
|
||||||
|
.replace(/{klen}/, h(publen+1))
|
||||||
|
.replace(/{key}/, pubkey.toHex(csrpub))
|
||||||
|
, '02 03 {mod}' // 5 bytes, key and modules [01 00 01]
|
||||||
|
.replace(/{mod}/, '01 00 01')
|
||||||
|
|
||||||
|
// #3 Total 2+28+n
|
||||||
|
, 'A0 {16.2.3ellen}' // 2 bytes, ?? [4B]
|
||||||
|
.replace(/{[^}]+}/, h(2+11+2+2+2+5+2+2+sanlen))
|
||||||
|
, '30 {17.3.9seqlen}' // 2 bytes, sequence
|
||||||
|
.replace(/{[^}]+}/, h(11+2+2+2+5+2+2+sanlen))
|
||||||
|
, '06 09 2A 86 48 86 F7 0D 01 09 0E' // 11 bytes, object id (extensionRequest (PKCS #9 via CRMF))
|
||||||
|
, '31 {19.5.0setlen}' // 2 bytes, set
|
||||||
|
.replace(/{[^}]+}/, h(2+2+5+2+2+sanlen))
|
||||||
|
, '30 {20.6.0seqlen}' // 2 bytes, sequence
|
||||||
|
.replace(/{[^}]+}/, h(2+5+2+2+sanlen))
|
||||||
|
, '30 {21.7.0seqlen}' // 2 bytes, sequence
|
||||||
|
.replace(/{[^}]+}/, h(5+2+2+sanlen))
|
||||||
|
, '06 03 55 1D 11' // 5 bytes, object id (subjectAltName (X.509 extension))
|
||||||
|
, '04 {23.8.0octlen}' // 2 bytes, octet string
|
||||||
|
.replace(/{[^}]+}/, h(2+sanlen))
|
||||||
|
, '30 {24.9.0seqlen}' // 2 bytes, sequence
|
||||||
|
.replace(/{[^}]+}/, h(sanlen))
|
||||||
|
, '{altnames}' // n (elements of sequence)
|
||||||
|
.replace(/{altnames}/, altnames)
|
||||||
|
];
|
||||||
|
body = body.join('').replace(/\s+/g, '');
|
||||||
|
return fromHex(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCsrBodyEc(domains, csrpub) {
|
||||||
|
var altnames = domains.map(function (d) {
|
||||||
|
return csrDomains.replace(/{dlen}/, h(d.length)).replace(/{domain\.tld}/, strToHex(d));
|
||||||
|
}).join('').replace(/\s+/g, '');
|
||||||
|
var publen = csrpub.byteLength;
|
||||||
|
var sublen = domains[0].length;
|
||||||
|
var sanlen = (altnames.length/2);
|
||||||
|
|
||||||
|
var body = [ '30 82 {1.1.0seqlen}' // 4 bytes, sequence
|
||||||
|
.replace(/{[^}]+}/, h(
|
||||||
|
3
|
||||||
|
+ 13 + sublen
|
||||||
|
+ 38 + publen
|
||||||
|
+ 30 + sanlen
|
||||||
|
))
|
||||||
|
|
||||||
|
// #0 Total 3
|
||||||
|
, '02 01 00' // 3 bytes, int 0
|
||||||
|
|
||||||
|
// #1 Total 2+11+n
|
||||||
|
, '30 {3.2.0seqlen}' // 2 bytes, sequence
|
||||||
|
.replace(/{[^}]+}/, h(2+2+5+2+sublen))
|
||||||
|
, '31 {4.3.0setlen}' // 2 bytes, set
|
||||||
|
.replace(/{[^}]+}/, h(2+5+2+sublen))
|
||||||
|
, '30 {5.4.0seqlen}' // 2 bytes, sequence
|
||||||
|
.replace(/{[^}]+}/, h(5+2+sublen))
|
||||||
|
, '06 03 55 04 03' // 5 bytes, object id (commonName)
|
||||||
|
, '0C {dlen} {domain.tld}' // 2+n bytes, utf8string
|
||||||
|
.replace(/{dlen}/, h(sublen))
|
||||||
|
.replace(/{domain\.tld}/, strToHex(domains[0]))
|
||||||
|
|
||||||
|
// #2 Total 4+28+n+1+5
|
||||||
|
, '30 82 {8.2.2seqlen}' // 4 bytes, sequence
|
||||||
|
.replace(/{[^}]+}/, h(2+11+2+4+1+4+4+publen+1+5))
|
||||||
|
, '30 0D' // 2 bytes, sequence
|
||||||
|
, '06 09 2A 86 48 86 F7 0D 01 01 01' // 11 bytes, rsaEncryption (PKCS #1)
|
||||||
|
, '05 00' // 2 bytes, null (why?)
|
||||||
|
, '03 82 {12.3.0bslen} 00' // 4+1 bytes, bit string [01 0F]
|
||||||
|
.replace(/{[^}]+}/, h(1+4+4+publen+1+5))
|
||||||
|
, '30 82 {13.4.0seqlen}' // 4 bytes, sequence
|
||||||
|
.replace(/{[^}]+}/, h(4+publen+1+5))
|
||||||
|
, '02 82 {klen} 00 {key}' // 4+n bytes, int (RSA Pub Key)
|
||||||
|
.replace(/{klen}/, h(publen+1))
|
||||||
|
.replace(/{key}/, pubkey.toHex(csrpub))
|
||||||
|
, '02 03 {mod}' // 5 bytes, key and modules [01 00 01]
|
||||||
|
.replace(/{mod}/, '01 00 01')
|
||||||
|
|
||||||
|
// #3 Total 2+28+n
|
||||||
|
, 'A0 {16.2.3ellen}' // 2 bytes, ?? [4B]
|
||||||
|
.replace(/{[^}]+}/, h(2+11+2+2+2+5+2+2+sanlen))
|
||||||
|
, '30 {17.3.9seqlen}' // 2 bytes, sequence
|
||||||
|
.replace(/{[^}]+}/, h(11+2+2+2+5+2+2+sanlen))
|
||||||
|
, '06 09 2A 86 48 86 F7 0D 01 09 0E' // 11 bytes, object id (extensionRequest (PKCS #9 via CRMF))
|
||||||
|
, '31 {19.5.0setlen}' // 2 bytes, set
|
||||||
|
.replace(/{[^}]+}/, h(2+2+5+2+2+sanlen))
|
||||||
|
, '30 {20.6.0seqlen}' // 2 bytes, sequence
|
||||||
|
.replace(/{[^}]+}/, h(2+5+2+2+sanlen))
|
||||||
|
, '30 {21.7.0seqlen}' // 2 bytes, sequence
|
||||||
|
.replace(/{[^}]+}/, h(5+2+2+sanlen))
|
||||||
|
, '06 03 55 1D 11' // 5 bytes, object id (subjectAltName (X.509 extension))
|
||||||
|
, '04 {23.8.0octlen}' // 2 bytes, octet string
|
||||||
|
.replace(/{[^}]+}/, h(2+sanlen))
|
||||||
|
, '30 {24.9.0seqlen}' // 2 bytes, sequence
|
||||||
|
.replace(/{[^}]+}/, h(sanlen))
|
||||||
|
, '{altnames}' // n (elements of sequence)
|
||||||
|
.replace(/{altnames}/, altnames)
|
||||||
|
];
|
||||||
|
body = body.join('').replace(/\s+/g, '');
|
||||||
|
return fromHex(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCsr(domains, keypem) {
|
||||||
|
// TODO get pub from priv
|
||||||
|
var body = createCsrBodyRsa(domains, csrpub);
|
||||||
|
var sign = crypto.createSign('SHA256');
|
||||||
|
sign.write(new Uint8Array(body));
|
||||||
|
sign.end();
|
||||||
|
var sig = sign.sign(keypem);
|
||||||
|
var len = body.byteLength + (csrRsaFoot.length/2) + sig.byteLength;
|
||||||
|
console.log('headlen', h(len));
|
||||||
|
var head = csrHead.replace(/{[^}]+}/, h(len));
|
||||||
|
var ab = new Uint8Array(new ArrayBuffer(4 + len));
|
||||||
|
var i = 0;
|
||||||
|
fromHex(head).forEach(function (b) {
|
||||||
|
ab[i] = b;
|
||||||
|
i += 1;
|
||||||
|
});
|
||||||
|
body.forEach(function (b) {
|
||||||
|
ab[i] = b;
|
||||||
|
i += 1;
|
||||||
|
});
|
||||||
|
fromHex(csrRsaFoot).forEach(function (b) {
|
||||||
|
ab[i] = b;
|
||||||
|
i += 1;
|
||||||
|
});
|
||||||
|
new Uint8Array(sig).forEach(function (b) {
|
||||||
|
ab[i] = b;
|
||||||
|
i += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
var pem = pubkey.formatAsPublicPem(pubkey.toBase64(ab));
|
||||||
|
|
||||||
|
return pem;
|
||||||
|
}
|
||||||
|
|
||||||
|
//var pem = createCsr([ 'example.com', 'www.example.com', 'api.example.com' ], keypem);
|
||||||
|
var pem = createCsr([ 'whatever.net', 'api.whatever.net' ], keypem);
|
||||||
|
console.log('pem:');
|
||||||
|
console.log(pem);
|
||||||
|
return;
|
||||||
|
|
||||||
|
function signagain(csrbody) {
|
||||||
|
var longEnough = csrbody.byteLength > 256;
|
||||||
|
|
||||||
|
if (!longEnough) { return false; }
|
||||||
|
|
||||||
|
var domains = [ 'example.com', 'www.example.com', 'api.example.com' ];
|
||||||
|
var body = createCsrBodyRsa(domains);
|
||||||
|
|
||||||
|
var sign = crypto.createSign('SHA256');
|
||||||
|
sign.write(new Uint8Array(body));
|
||||||
|
sign.end();
|
||||||
|
var sig2 = sign.sign(keypem);
|
||||||
|
var hexsig = pubkey.toHex(sig);
|
||||||
|
var hexsig2 = pubkey.toHex(sig2);
|
||||||
|
|
||||||
|
console.log('hexsig:');
|
||||||
|
console.log(hexsig);
|
||||||
|
if (hexsig2 !== hexsig) {
|
||||||
|
throw new Error("sigs didn't match");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Winner winner!', csrbody.byteLength);
|
||||||
|
console.log(pubkey.toHex(csrbody));
|
||||||
|
console.log();
|
||||||
|
console.log('Test:', body.byteLength);
|
||||||
|
console.log(pubkey.toHex(body));
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
var len = body.byteLength + (csrRsaFoot.length/2) + sig.byteLength;
|
||||||
|
console.log('headlen', h(len));
|
||||||
|
var head = csrHead.replace(/{[^}]+}/, h(len));
|
||||||
|
var ab = new Uint8Array(new ArrayBuffer(4 + len));
|
||||||
|
var i = 0;
|
||||||
|
fromHex(head).forEach(function (b) {
|
||||||
|
ab[i] = b;
|
||||||
|
i += 1;
|
||||||
|
});
|
||||||
|
body.forEach(function (b) {
|
||||||
|
ab[i] = b;
|
||||||
|
i += 1;
|
||||||
|
});
|
||||||
|
fromHex(csrRsaFoot).forEach(function (b) {
|
||||||
|
ab[i] = b;
|
||||||
|
i += 1;
|
||||||
|
});
|
||||||
|
new Uint8Array(sig2).forEach(function (b) {
|
||||||
|
ab[i] = b;
|
||||||
|
i += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Whole enchilada:", pubkey.toHex(ab.buffer) === pubkey.toHex(csrFull));
|
||||||
|
console.log(pubkey.toHex(ab.buffer));
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
// subject + 2-byte altname headers + altnames themselves + public key
|
||||||
|
//var extralen = domains[0].length + (domains.length * 2) + domains.join('').length + csrpub.byteLength;
|
||||||
|
//var foot = csrRsaFoot.replace(/xxxx/g, (csrpub.byteLength + 1));
|
||||||
|
//var head = csrHead.replace(/xxxx/);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log();
|
||||||
|
console.log("CSR");
|
||||||
|
console.log(pubkey.toHex(csrFull));
|
||||||
|
console.log();
|
||||||
|
console.log(pubkey.toHex(csrbody.slice(0, csrbody.byteLength - sig.byteLength)));
|
||||||
|
console.log();
|
||||||
|
console.log();
|
||||||
|
//signagain(csrbody);
|
Loading…
Reference in New Issue