v0.0.3: ecdsa is soooooo close
This commit is contained in:
		
							parent
							
								
									30e617ada1
								
							
						
					
					
						commit
						d928c2bdc0
					
				@ -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-11 (Sunday) W00T! got a CSR generated for RSA with VanillaJS ArrayBuffer
 | 
			
		||||
* 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™ for node.js
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										13
									
								
								convert-from-hex.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								convert-from-hex.js
									
									
									
									
									
										Normal 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]);
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "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.",
 | 
			
		||||
  "main": "index.js",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										153
									
								
								pubkey.js
									
									
									
									
									
								
							
							
						
						
									
										153
									
								
								pubkey.js
									
									
									
									
									
								
							@ -1,21 +1,23 @@
 | 
			
		||||
(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
 | 
			
		||||
 | 
			
		||||
//       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) {
 | 
			
		||||
  var crv;
 | 
			
		||||
  var der = fromBase64(pem.split(/\n/).filter(function (line, i) {
 | 
			
		||||
    if (0 === i) {
 | 
			
		||||
      if (/ PUBLIC /.test(line)) {
 | 
			
		||||
        pub = true;
 | 
			
		||||
@ -31,18 +33,21 @@ function parsePem(pem) {
 | 
			
		||||
    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';
 | 
			
		||||
      }
 | 
			
		||||
  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
 | 
			
		||||
      // 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) {
 | 
			
		||||
@ -62,6 +67,60 @@ function toHex(ab) {
 | 
			
		||||
  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);
 | 
			
		||||
@ -122,23 +181,70 @@ function toRsaPub(pub) {
 | 
			
		||||
  return der.buffer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function formatAsPem(str, privacy, pemName) {
 | 
			
		||||
  var pemstr = (pemName ? pemName + ' ' : '');
 | 
			
		||||
  var privstr = (privacy ? privacy + ' ' : '');
 | 
			
		||||
  var finalString = '-----BEGIN ' + pemstr + privstr + 'KEY-----\n';
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
@ -172,10 +278,15 @@ function fromBase64(b64) {
 | 
			
		||||
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));
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										387
									
								
								re-sign.js
									
									
									
									
									
								
							
							
						
						
									
										387
									
								
								re-sign.js
									
									
									
									
									
								
							@ -4,22 +4,27 @@ 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'
 | 
			
		||||
  ( '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'
 | 
			
		||||
  + '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, '');
 | 
			
		||||
var csrDomains = '82 {dlen} {domain.tld}';  // 2+n bytes (type 82?)
 | 
			
		||||
/*
 | 
			
		||||
@ -57,19 +62,31 @@ var csrRsaContent =
 | 
			
		||||
 | 
			
		||||
function privateToPub(pem) {
 | 
			
		||||
  var pubbuf;
 | 
			
		||||
  var pubxy;
 | 
			
		||||
  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);
 | 
			
		||||
  var pubder;
 | 
			
		||||
 | 
			
		||||
  if ('RSA' === key.typ) {
 | 
			
		||||
    if (key.pub) {
 | 
			
		||||
      pubbuf = pubkey.readPubkey(key.der);
 | 
			
		||||
    } else {
 | 
			
		||||
      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));
 | 
			
		||||
  var der = pubkey.toRsaPub(pubbuf);
 | 
			
		||||
  var b64 = pubkey.toBase64(der);
 | 
			
		||||
  var b64 = pubkey.toBase64(pubder);
 | 
			
		||||
  return pubkey.formatAsPublicPem(b64);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -89,45 +106,6 @@ function pubToPem(pubbuf) {
 | 
			
		||||
  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) {
 | 
			
		||||
@ -163,13 +141,14 @@ function createCsrBodyRsa(domains, csrpub) {
 | 
			
		||||
    .replace(/{[^}]+}/, h(
 | 
			
		||||
        3
 | 
			
		||||
      + 13 + sublen
 | 
			
		||||
      + 38 + publen
 | 
			
		||||
      + 38 + publen // Length for RSA-related stuff
 | 
			
		||||
      + 30 + sanlen
 | 
			
		||||
    ))
 | 
			
		||||
 | 
			
		||||
      // #0 Total 3
 | 
			
		||||
    , '02 01 00'                                                      // 3 bytes, int 0
 | 
			
		||||
 | 
			
		||||
      // Subject
 | 
			
		||||
      // #1 Total 2+11+n
 | 
			
		||||
    , '30 {3.2.0seqlen}'                                              // 2 bytes, sequence
 | 
			
		||||
      .replace(/{[^}]+}/, h(2+2+5+2+sublen))
 | 
			
		||||
@ -182,6 +161,7 @@ function createCsrBodyRsa(domains, csrpub) {
 | 
			
		||||
          .replace(/{dlen}/, h(sublen))
 | 
			
		||||
          .replace(/{domain\.tld}/, strToHex(domains[0]))
 | 
			
		||||
 | 
			
		||||
      // Public Key
 | 
			
		||||
      // #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))
 | 
			
		||||
@ -198,6 +178,7 @@ function createCsrBodyRsa(domains, csrpub) {
 | 
			
		||||
        , '02 03 {mod}'                                               // 5 bytes, key and modules [01 00 01]
 | 
			
		||||
          .replace(/{mod}/, '01 00 01')
 | 
			
		||||
 | 
			
		||||
      // AltNames
 | 
			
		||||
      // #3 Total 2+28+n
 | 
			
		||||
    , 'A0 {16.2.3ellen}'                                              // 2 bytes, ?? [4B]
 | 
			
		||||
      .replace(/{[^}]+}/, h(2+11+2+2+2+5+2+2+sanlen))
 | 
			
		||||
@ -222,25 +203,44 @@ function createCsrBodyRsa(domains, csrpub) {
 | 
			
		||||
  return fromHex(body);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function createCsrBodyEc(domains, csrpub) {
 | 
			
		||||
function createCsrBodyEc(domains, xy) {
 | 
			
		||||
  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 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(
 | 
			
		||||
        3
 | 
			
		||||
      + 13 + sublen
 | 
			
		||||
      + 38 + publen
 | 
			
		||||
      + 27 + publen // Length for EC-related P-256 stuff
 | 
			
		||||
      + 30 + sanlen
 | 
			
		||||
    ))
 | 
			
		||||
 | 
			
		||||
      // #0 Total 3
 | 
			
		||||
    , '02 01 00'                                                      // 3 bytes, int 0
 | 
			
		||||
 | 
			
		||||
      // Subject
 | 
			
		||||
      // #1 Total 2+11+n
 | 
			
		||||
    , '30 {3.2.0seqlen}'                                              // 2 bytes, sequence
 | 
			
		||||
      .replace(/{[^}]+}/, h(2+2+5+2+sublen))
 | 
			
		||||
@ -253,38 +253,40 @@ function createCsrBodyEc(domains, csrpub) {
 | 
			
		||||
          .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')
 | 
			
		||||
      // P-256 Public Key
 | 
			
		||||
      // #2 Total 2+25+xy
 | 
			
		||||
    , '30 {+25+xy}'                                                   // 2 bytes, sequence
 | 
			
		||||
      .replace(/{[^}]+}/, h(2+9+10+3+1+publen))
 | 
			
		||||
      , '30 13'                                                       // 2 bytes, sequence
 | 
			
		||||
          // 1.2.840.10045.2.1 ecPublicKey
 | 
			
		||||
          // (ANSI X9.62 public key type)
 | 
			
		||||
        , '06 07 2A 86 48 CE 3D 02 01'                                // 9 bytes, object id
 | 
			
		||||
          // 1.2.840.10045.3.1.7 prime256v1
 | 
			
		||||
          // (ANSI X9.62 named elliptic curve)
 | 
			
		||||
        , '06 08 2A 86 48 CE 3D 03 01 07'                             // 10 bytes, object id
 | 
			
		||||
      , '03 {xylen} 00 {xy}'                                          // 3+1+n bytes
 | 
			
		||||
        .replace(/{xylen}/, h(publen+2))
 | 
			
		||||
        .replace(/{xy}/, compression + hxy)
 | 
			
		||||
 | 
			
		||||
      // Altnames
 | 
			
		||||
      // #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))
 | 
			
		||||
      , '30 {17.3.9seqlen}'                                           // 2 bytes, sequence
 | 
			
		||||
      , '30 {+26}'                                                    // 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
 | 
			
		||||
          // (extensionRequest (PKCS #9 via CRMF))
 | 
			
		||||
        , '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))
 | 
			
		||||
            , '30 {20.6.0seqlen}'                                     // 2 bytes, sequence
 | 
			
		||||
            , '30 {+11}'                                              // 2 bytes, sequence
 | 
			
		||||
              .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))
 | 
			
		||||
                , '06 03 55 1D 11'                                    // 5 bytes, object id (subjectAltName (X.509 extension))
 | 
			
		||||
                , '04 {23.8.0octlen}'                                 // 2 bytes, octet string
 | 
			
		||||
                  // (subjectAltName (X.509 extension))
 | 
			
		||||
                , '06 03 55 1D 11'                                    // 5 bytes, object id
 | 
			
		||||
                , '04 {+2}'                                           // 2 bytes, octet string
 | 
			
		||||
                  .replace(/{[^}]+}/, h(2+sanlen))
 | 
			
		||||
                  , '30 {24.9.0seqlen}'                               // 2 bytes, sequence
 | 
			
		||||
                  , '30 {+n}'                                         // 2 bytes, sequence
 | 
			
		||||
                    .replace(/{[^}]+}/, h(sanlen))
 | 
			
		||||
                    , '{altnames}'                                    // n (elements of sequence)
 | 
			
		||||
                      .replace(/{altnames}/, altnames)
 | 
			
		||||
@ -293,7 +295,37 @@ function createCsrBodyEc(domains, csrpub) {
 | 
			
		||||
  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
 | 
			
		||||
  var body = createCsrBodyRsa(domains, csrpub);
 | 
			
		||||
  var sign = crypto.createSign('SHA256');
 | 
			
		||||
@ -322,17 +354,55 @@ function createCsr(domains, keypem) {
 | 
			
		||||
    i += 1;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  var pem = pubkey.formatAsPublicPem(pubkey.toBase64(ab));
 | 
			
		||||
  var pem = pubkey.formatAsPem(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 createEcCsr(domains, keypem, pubj) {
 | 
			
		||||
  // TODO get pub from priv
 | 
			
		||||
 | 
			
		||||
  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) {
 | 
			
		||||
  var longEnough = csrbody.byteLength > 256;
 | 
			
		||||
 | 
			
		||||
@ -393,12 +463,135 @@ function signagain(csrbody) {
 | 
			
		||||
  //var head = csrHead.replace(/xxxx/);
 | 
			
		||||
  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("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);
 | 
			
		||||
 | 
			
		||||
//check([ 'whatever.net', 'api.whatever.net' ], keypem, csrFull);
 | 
			
		||||
check([ 'example.com', 'www.example.com', 'api.example.com' ], keypem, csrFull);
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user