v0.0.3: ecdsa is soooooo close

This commit is contained in:
AJ ONeal 2018-11-16 01:40:07 -07:00
parent 30e617ada1
commit d928c2bdc0
5 changed files with 437 additions and 119 deletions

View File

@ -4,6 +4,7 @@ This is my project for the weekend. I expect to be finished today (Monday Nov 12
* 2018-10-10 (Saturday) work has begun * 2018-10-10 (Saturday) work has begun
* 2018-10-11 (Sunday) W00T! got a CSR generated for RSA with VanillaJS ArrayBuffer * 2018-10-11 (Sunday) W00T! got a CSR generated for RSA with VanillaJS ArrayBuffer
* 2018-10-12 (Monday) Figuring out ECDSA CSRs right now * 2018-10-12 (Monday) Figuring out ECDSA CSRs right now
* 2018-10-15 (Thursday) ECDSA is a trixy hobbit... but I think I've got it...
<!-- <!--
Keypairs&trade; for node.js Keypairs&trade; for node.js

13
convert-from-hex.js Normal file
View File

@ -0,0 +1,13 @@
'use strict';
var fs = require('fs');
var path = require('path');
function convert(name) {
var ext = path.extname(name);
var csr = fs.readFileSync(name, 'ascii').replace(/\s\+/g, '');
var bin = Buffer.from(csr, 'hex');
fs.writeFileSync(name.replace(new RegExp('\\' + ext + '$'), '') + '.bin', bin);
}
convert(process.argv[2]);

View File

@ -1,6 +1,6 @@
{ {
"name": "keypairs", "name": "keypairs",
"version": "0.0.2", "version": "0.0.3",
"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": {

153
pubkey.js
View File

@ -1,21 +1,23 @@
(function (exports) { (function (exports) {
'use strict'; '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 // 30 sequence
// 03 bit string // 03 bit string
// 05 null // 05 null
// 06 object id // 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) { function parsePem(pem) {
var typ; var typ;
var pub; var pub;
var der = fromBase64(pem.toString('ascii').split(/\n/).filter(function (line, i) { var crv;
var der = fromBase64(pem.split(/\n/).filter(function (line, i) {
if (0 === i) { if (0 === i) {
if (/ PUBLIC /.test(line)) { if (/ PUBLIC /.test(line)) {
pub = true; pub = true;
@ -31,18 +33,21 @@ function parsePem(pem) {
return !/---/.test(line); return !/---/.test(line);
}).join('')); }).join(''));
if (!typ) { if (!typ || 'EC' === typ) {
if (pub) { var hex = toHex(der).toLowerCase();
// This is the RSA object ID // This is the RSA object ID
if ('06092A864886F70D010101'.toLowerCase() === der.slice(6, 6 + 11).toString('hex')) { if (-1 !== hex.indexOf(OBJ_ID_RSA)) {
typ = 'RSA'; typ = 'RSA';
} } else if (-1 !== hex.indexOf(OBJ_ID_EC)) {
typ = 'EC';
crv = 'P-256';
} else { } else {
// TODO // TODO more than just P-256
console.warn("unsupported ec curve");
} }
} }
return { typ: typ, pub: pub, der: der }; return { typ: typ, pub: pub, der: der, crv: crv };
} }
function toHex(ab) { function toHex(ab) {
@ -62,6 +67,60 @@ function toHex(ab) {
return hex.join(''); 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) { function readPubkey(der) {
var offset = 28 + 5; // header plus size var offset = 28 + 5; // header plus size
var ksBytes = der.slice(30, 32); var ksBytes = der.slice(30, 32);
@ -122,23 +181,70 @@ function toRsaPub(pub) {
return der.buffer; return der.buffer;
} }
function formatAsPem(str, privacy, pemName) { function h(d) {
var pemstr = (pemName ? pemName + ' ' : ''); d = d.toString(16);
var privstr = (privacy ? privacy + ' ' : ''); if (d.length % 2) {
var finalString = '-----BEGIN ' + pemstr + privstr + 'KEY-----\n'; 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) { while (str.length > 0) {
finalString += str.substring(0, 64) + '\n'; finalString += str.substring(0, 64) + '\n';
str = str.substring(64); str = str.substring(64);
} }
return finalString;
}
finalString = finalString + '-----END ' + pemstr + privstr + 'KEY-----'; 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; return finalString;
} }
function formatAsPublicPem(str) { function formatAsPublicPem(str) {
return formatAsPem(str, 'PUBLIC', ''); 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) { function toBase64(der) {
@ -172,10 +278,15 @@ function fromBase64(b64) {
exports.parsePem = parsePem; exports.parsePem = parsePem;
exports.toBase64 = toBase64; exports.toBase64 = toBase64;
exports.toRsaPub = toRsaPub; exports.toRsaPub = toRsaPub;
exports.toEcPub = toEcPub;
exports.formatAsPublicPem = formatAsPublicPem; exports.formatAsPublicPem = formatAsPublicPem;
exports.formatAsPrivatePem = formatAsPrivatePem;
exports.formatAsPem = formatAsPem; exports.formatAsPem = formatAsPem;
exports.readPubkey = readPubkey; exports.readPubkey = readPubkey;
exports.readEcPubkey = readEcPubkey;
exports.readEcPrivkey = readEcPrivkey;
exports.readPrivkey = readPrivkey; exports.readPrivkey = readPrivkey;
exports.toHex = toHex; exports.toHex = toHex;
exports.fromHex = fromHex;
}('undefined' !== typeof module ? module.exports: window)); }('undefined' !== typeof module ? module.exports: window));

View File

@ -4,22 +4,27 @@ var crypto = require('crypto');
var fs = require('fs'); var fs = require('fs');
var pubkey = require('./pubkey.js'); 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 // these are static ASN.1 segments
// The head specifies that there will be 3 segments and a content length // The head specifies that there will be 3 segments and a content length
// (those segments will be content, signature header, and signature) // (those segments will be content, signature header, and signature)
var csrHead = '30 82 {0seq0len}'.replace(/\s+/g, ''); var csrHead = '30 82 {0seq0len}'.replace(/\s+/g, '');
// The tail specifies the RSA256 signature header (and is followed by the signature // The tail specifies the RSA256 signature header (and is followed by the signature
var csrRsaFoot = var csrRsaFoot =
( '30 0D 06 09 2A 86 48 86 F7 0D 01 01 0B' ( '30 0D'
// 1.2.840.113549.1.1.11 sha256WithRSAEncryption (PKCS #1)
+ '06 09 2A 86 48 86 F7 0D 01 01 0B'
+ '05 00' + '05 00'
+ '03 82 01 01 00' + '03 82 01 01 00' // preamble to sig
).replace(/\s+/g, '');
var csrEcFoot =
( '30 0A'
// 1.2.840.10045.4.3.2 ecdsaWithSHA256
// (ANSI X9.62 ECDSA algorithm with SHA256)
+ '06 08 2A 86 48 CE 3D 04 03 02'
+ '03 49 00'
+ '30 46'
+ '02 21 00 {s}' // 32 bytes of sig
+ '02 21 00 {r}' // 32 bytes of sig
).replace(/\s+/g, ''); ).replace(/\s+/g, '');
var csrDomains = '82 {dlen} {domain.tld}'; // 2+n bytes (type 82?) var csrDomains = '82 {dlen} {domain.tld}'; // 2+n bytes (type 82?)
/* /*
@ -57,19 +62,31 @@ var csrRsaContent =
function privateToPub(pem) { function privateToPub(pem) {
var pubbuf; var pubbuf;
var pubxy;
var key = pubkey.parsePem(pem); var key = pubkey.parsePem(pem);
if ('RSA' !== key.typ) { var pubder;
throw new Error(key.typ + " not supported");
} if ('RSA' === key.typ) {
if (key.pub) { if (key.pub) {
pubbuf = pubkey.readPubkey(key.der); pubbuf = pubkey.readPubkey(key.der);
} else { } else {
pubbuf = pubkey.readPrivkey(key.der); pubbuf = pubkey.readPrivkey(key.der);
}
pubder = pubkey.toRsaPub(pubbuf);
} else if ('EC' === key.typ) {
if (key.crv && 'P-256' !== key.crv) {
throw new Error("unsupported curve type");
}
if (key.pub) {
pubxy = pubkey.readEcPubkey(key.der);
} else {
pubxy = pubkey.readEcPrivkey(key.der);
}
pubder = pubkey.toEcPub(pubxy.x, pubxy.y);
} }
//console.log(pubbuf.byteLength, pubkey.toHex(pubbuf)); //console.log(pubbuf.byteLength, pubkey.toHex(pubbuf));
var der = pubkey.toRsaPub(pubbuf); var b64 = pubkey.toBase64(pubder);
var b64 = pubkey.toBase64(der);
return pubkey.formatAsPublicPem(b64); return pubkey.formatAsPublicPem(b64);
} }
@ -89,45 +106,6 @@ function pubToPem(pubbuf) {
return pubkey.formatAsPublicPem(b64); 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) { function h(d) {
d = d.toString(16); d = d.toString(16);
if (d.length % 2) { if (d.length % 2) {
@ -163,13 +141,14 @@ function createCsrBodyRsa(domains, csrpub) {
.replace(/{[^}]+}/, h( .replace(/{[^}]+}/, h(
3 3
+ 13 + sublen + 13 + sublen
+ 38 + publen + 38 + publen // Length for RSA-related stuff
+ 30 + sanlen + 30 + sanlen
)) ))
// #0 Total 3 // #0 Total 3
, '02 01 00' // 3 bytes, int 0 , '02 01 00' // 3 bytes, int 0
// Subject
// #1 Total 2+11+n // #1 Total 2+11+n
, '30 {3.2.0seqlen}' // 2 bytes, sequence , '30 {3.2.0seqlen}' // 2 bytes, sequence
.replace(/{[^}]+}/, h(2+2+5+2+sublen)) .replace(/{[^}]+}/, h(2+2+5+2+sublen))
@ -182,6 +161,7 @@ function createCsrBodyRsa(domains, csrpub) {
.replace(/{dlen}/, h(sublen)) .replace(/{dlen}/, h(sublen))
.replace(/{domain\.tld}/, strToHex(domains[0])) .replace(/{domain\.tld}/, strToHex(domains[0]))
// Public Key
// #2 Total 4+28+n+1+5 // #2 Total 4+28+n+1+5
, '30 82 {8.2.2seqlen}' // 4 bytes, sequence , '30 82 {8.2.2seqlen}' // 4 bytes, sequence
.replace(/{[^}]+}/, h(2+11+2+4+1+4+4+publen+1+5)) .replace(/{[^}]+}/, h(2+11+2+4+1+4+4+publen+1+5))
@ -198,6 +178,7 @@ function createCsrBodyRsa(domains, csrpub) {
, '02 03 {mod}' // 5 bytes, key and modules [01 00 01] , '02 03 {mod}' // 5 bytes, key and modules [01 00 01]
.replace(/{mod}/, '01 00 01') .replace(/{mod}/, '01 00 01')
// AltNames
// #3 Total 2+28+n // #3 Total 2+28+n
, 'A0 {16.2.3ellen}' // 2 bytes, ?? [4B] , 'A0 {16.2.3ellen}' // 2 bytes, ?? [4B]
.replace(/{[^}]+}/, h(2+11+2+2+2+5+2+2+sanlen)) .replace(/{[^}]+}/, h(2+11+2+2+2+5+2+2+sanlen))
@ -222,25 +203,44 @@ function createCsrBodyRsa(domains, csrpub) {
return fromHex(body); return fromHex(body);
} }
function createCsrBodyEc(domains, csrpub) { function createCsrBodyEc(domains, xy) {
var altnames = domains.map(function (d) { var altnames = domains.map(function (d) {
return csrDomains.replace(/{dlen}/, h(d.length)).replace(/{domain\.tld}/, strToHex(d)); return csrDomains.replace(/{dlen}/, h(d.length)).replace(/{domain\.tld}/, strToHex(d));
}).join('').replace(/\s+/g, ''); }).join('').replace(/\s+/g, '');
var publen = csrpub.byteLength;
var sublen = domains[0].length; var sublen = domains[0].length;
var sanlen = (altnames.length/2); var sanlen = (altnames.length/2);
var publen = xy.x.byteLength;
var compression = '04';
var hxy = '';
// 04 == x+y, 02 == x-only
if (xy.y) {
publen += xy.y.byteLength;
} else {
// Note: I don't intend to support compression - it isn't used by most
// libraries and it requir more dependencies for bigint ops to deflate.
// This is more just a placeholder. It won't work right now anyway
// because compression requires an exta bit stored (odd vs even), which
// I haven't learned yet, and I'm not sure if it's allowed at all
compression = '02';
}
hxy += pubkey.toHex(xy.x);
if (xy.y) {
hxy += pubkey.toHex(xy.y);
}
console.log('hxy:', hxy);
var body = [ '30 82 {1.1.0seqlen}' // 4 bytes, sequence var body = [ '30 81 {+85+n}' // 4 bytes, sequence
.replace(/{[^}]+}/, h( .replace(/{[^}]+}/, h(
3 3
+ 13 + sublen + 13 + sublen
+ 38 + publen + 27 + publen // Length for EC-related P-256 stuff
+ 30 + sanlen + 30 + sanlen
)) ))
// #0 Total 3 // #0 Total 3
, '02 01 00' // 3 bytes, int 0 , '02 01 00' // 3 bytes, int 0
// Subject
// #1 Total 2+11+n // #1 Total 2+11+n
, '30 {3.2.0seqlen}' // 2 bytes, sequence , '30 {3.2.0seqlen}' // 2 bytes, sequence
.replace(/{[^}]+}/, h(2+2+5+2+sublen)) .replace(/{[^}]+}/, h(2+2+5+2+sublen))
@ -253,38 +253,40 @@ function createCsrBodyEc(domains, csrpub) {
.replace(/{dlen}/, h(sublen)) .replace(/{dlen}/, h(sublen))
.replace(/{domain\.tld}/, strToHex(domains[0])) .replace(/{domain\.tld}/, strToHex(domains[0]))
// #2 Total 4+28+n+1+5 // P-256 Public Key
, '30 82 {8.2.2seqlen}' // 4 bytes, sequence // #2 Total 2+25+xy
.replace(/{[^}]+}/, h(2+11+2+4+1+4+4+publen+1+5)) , '30 {+25+xy}' // 2 bytes, sequence
, '30 0D' // 2 bytes, sequence .replace(/{[^}]+}/, h(2+9+10+3+1+publen))
, '06 09 2A 86 48 86 F7 0D 01 01 01' // 11 bytes, rsaEncryption (PKCS #1) , '30 13' // 2 bytes, sequence
, '05 00' // 2 bytes, null (why?) // 1.2.840.10045.2.1 ecPublicKey
, '03 82 {12.3.0bslen} 00' // 4+1 bytes, bit string [01 0F] // (ANSI X9.62 public key type)
.replace(/{[^}]+}/, h(1+4+4+publen+1+5)) , '06 07 2A 86 48 CE 3D 02 01' // 9 bytes, object id
, '30 82 {13.4.0seqlen}' // 4 bytes, sequence // 1.2.840.10045.3.1.7 prime256v1
.replace(/{[^}]+}/, h(4+publen+1+5)) // (ANSI X9.62 named elliptic curve)
, '02 82 {klen} 00 {key}' // 4+n bytes, int (RSA Pub Key) , '06 08 2A 86 48 CE 3D 03 01 07' // 10 bytes, object id
.replace(/{klen}/, h(publen+1)) , '03 {xylen} 00 {xy}' // 3+1+n bytes
.replace(/{key}/, pubkey.toHex(csrpub)) .replace(/{xylen}/, h(publen+2))
, '02 03 {mod}' // 5 bytes, key and modules [01 00 01] .replace(/{xy}/, compression + hxy)
.replace(/{mod}/, '01 00 01')
// Altnames
// #3 Total 2+28+n // #3 Total 2+28+n
, 'A0 {16.2.3ellen}' // 2 bytes, ?? [4B] , 'A0 {+28}' // 2 bytes, ?? [4B]
.replace(/{[^}]+}/, h(2+11+2+2+2+5+2+2+sanlen)) .replace(/{[^}]+}/, h(2+11+2+2+2+5+2+2+sanlen))
, '30 {17.3.9seqlen}' // 2 bytes, sequence , '30 {+26}' // 2 bytes, sequence
.replace(/{[^}]+}/, h(11+2+2+2+5+2+2+sanlen)) .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)) // (extensionRequest (PKCS #9 via CRMF))
, '31 {19.5.0setlen}' // 2 bytes, set , '06 09 2A 86 48 86 F7 0D 01 09 0E' // 11 bytes, object id
, '31 {+13}' // 2 bytes, set
.replace(/{[^}]+}/, h(2+2+5+2+2+sanlen)) .replace(/{[^}]+}/, h(2+2+5+2+2+sanlen))
, '30 {20.6.0seqlen}' // 2 bytes, sequence , '30 {+11}' // 2 bytes, sequence
.replace(/{[^}]+}/, h(2+5+2+2+sanlen)) .replace(/{[^}]+}/, h(2+5+2+2+sanlen))
, '30 {21.7.0seqlen}' // 2 bytes, sequence , '30 {+9}' // 2 bytes, sequence
.replace(/{[^}]+}/, h(5+2+2+sanlen)) .replace(/{[^}]+}/, h(5+2+2+sanlen))
, '06 03 55 1D 11' // 5 bytes, object id (subjectAltName (X.509 extension)) // (subjectAltName (X.509 extension))
, '04 {23.8.0octlen}' // 2 bytes, octet string , '06 03 55 1D 11' // 5 bytes, object id
, '04 {+2}' // 2 bytes, octet string
.replace(/{[^}]+}/, h(2+sanlen)) .replace(/{[^}]+}/, h(2+sanlen))
, '30 {24.9.0seqlen}' // 2 bytes, sequence , '30 {+n}' // 2 bytes, sequence
.replace(/{[^}]+}/, h(sanlen)) .replace(/{[^}]+}/, h(sanlen))
, '{altnames}' // n (elements of sequence) , '{altnames}' // n (elements of sequence)
.replace(/{altnames}/, altnames) .replace(/{altnames}/, altnames)
@ -293,7 +295,37 @@ function createCsrBodyEc(domains, csrpub) {
return fromHex(body); return fromHex(body);
} }
function createCsr(domains, keypem) { // https://gist.github.com/codermapuche/da4f96cdb6d5ff53b7ebc156ec46a10a
function signEc(keypem, ab) {
var sign = crypto.createSign('SHA256');
sign.write(new Uint8Array(ab));
sign.end();
var sig = sign.sign(keypem);
console.log("");
console.log("SIGNING!!");
console.log('csr body:', pubkey.toHex(new Uint8Array(ab)));
console.log('ec sig:', pubkey.toHex(sig));
console.log("");
sig = new Uint8Array(sig.buffer.slice(sig.byteOffset, sig.byteOffset + sig.byteLength));
var rlen = new Uint8Array(sig)[3];
var r = sig.slice(4, 4 + rlen);
var slen = sig.slice[5 + rlen];
var s = sig.slice(6 + rlen, 6 + rlen + slen);
// not sure what this is all about...
while(r[0] === 0) {
r = r.slice(1);
}
while(s[0] === 0) {
s = s.slice(1);
}
return { r: r, s: s };
}
function createRsaCsr(domains, keypem, csrpub) {
// TODO get pub from priv // TODO get pub from priv
var body = createCsrBodyRsa(domains, csrpub); var body = createCsrBodyRsa(domains, csrpub);
var sign = crypto.createSign('SHA256'); var sign = crypto.createSign('SHA256');
@ -322,17 +354,55 @@ function createCsr(domains, keypem) {
i += 1; i += 1;
}); });
var pem = pubkey.formatAsPublicPem(pubkey.toBase64(ab)); var pem = pubkey.formatAsPem(pubkey.toBase64(ab));
return pem; return pem;
} }
//var pem = createCsr([ 'example.com', 'www.example.com', 'api.example.com' ], keypem); function createEcCsr(domains, keypem, pubj) {
var pem = createCsr([ 'whatever.net', 'api.whatever.net' ], keypem); // TODO get pub from priv
console.log('pem:');
console.log(pem);
return;
console.log('[TRACE] createEcCsr');
var body = createCsrBodyEc(domains, pubj);
console.log('csr body:', pubkey.toHex(body))
var sig = signEc(keypem, body);
//var siglen = sig.s.byteLength + sig.r.byteLength;
console.log('r:', pubkey.toHex(sig.r));
console.log('s:', pubkey.toHex(sig.s));
// XXX TODO this is junk, not real
var foot = csrEcFoot
.replace(/{s}/, pubkey.toHex(sig.s))
.replace(/{r}/, pubkey.toHex(sig.r))
;
var len = body.byteLength + (foot.length/2) + sig.r.byteLength + sig.s.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(foot).forEach(function (b) {
ab[i] = b;
i += 1;
});
new Uint8Array(sig).forEach(function (b) {
ab[i] = b;
i += 1;
});
var pem = pubkey.formatAsPem(pubkey.toBase64(ab));
return pem;
}
/*
function signagain(csrbody) { function signagain(csrbody) {
var longEnough = csrbody.byteLength > 256; var longEnough = csrbody.byteLength > 256;
@ -393,12 +463,135 @@ function signagain(csrbody) {
//var head = csrHead.replace(/xxxx/); //var head = csrHead.replace(/xxxx/);
return false; return false;
} }
*/
function check(domains, keypem, csrFull) {
var pemblock = pubkey.parsePem(keypem);
if ('EC' === pemblock.typ) {
console.log("EC TIME:");
console.log(pemblock);
checkEc(domains, keypem, pemblock, csrFull);
} else if ('RSA' === pemblock.typ) {
console.log("RSA TIME!");
checkRsa(domains, keypem, pemblock, csrFull);
} else {
// TODO try
throw new Error("unknown key type");
}
}
// TODO
function checkEc(domains, keypem, pemblock, csrFull) {
var bodyend = (csrFull.byteLength - (3+2+2+32+2+32));
var sig = csrFull.slice(bodyend);
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
var csrbody = csrFull.slice(4, bodyend);
console.log();
console.log('csr body (' + csrbody.byteLength + ')');
console.log(pubkey.toHex(csrbody));
console.log();
var cnend = 21 + 2 + domains[0].length + 2 + 2 + 7 + 8;
var csrpub = csrFull.slice(cnend, cnend+2+89);
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");
}
*/
var pubj = pubkey.readEcPubkey(pemblock.der);
console.log('pubj:', pubj);
var pem = createEcCsr(domains, keypem, pubj, csrpub);
console.log('EC CSR PEM:');
console.log(pem);
return;
}
function checkRsa(domains, keypem, pubj, csrFull) {
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(pubkey.toHex(csrbody.slice(0, csrbody.byteLength - sig.byteLength)));
console.log();
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");
}
var pem = createRsaCsr(domains, keypem, csrpub);
console.log('RSA CSR PEM:');
console.log(pem);
return;
}
var keyname = process.argv[2];
var dername = process.argv[3];
var keypem = fs.readFileSync(keyname, 'ascii');
var csrFull = fs.readFileSync(dername);
var csrFull = csrFull.buffer.slice(csrFull.byteOffset, csrFull.byteOffset + csrFull.byteLength);
console.log(); console.log();
console.log("CSR"); console.log("CSR");
console.log(pubkey.toHex(csrFull)); console.log(pubkey.toHex(csrFull));
console.log(); console.log();
console.log(pubkey.toHex(csrbody.slice(0, csrbody.byteLength - sig.byteLength)));
console.log(); //check([ 'whatever.net', 'api.whatever.net' ], keypem, csrFull);
console.log(); check([ 'example.com', 'www.example.com', 'api.example.com' ], keypem, csrFull);
//signagain(csrbody);