old-keypairs.js/re-sign.js

405 lines
16 KiB
JavaScript

'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);