bluecrypt-keypairs.js/lib/csr-ec.js

158 lines
4.9 KiB
JavaScript

// 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();
// 1.3.132.0.34
// secp384r1 (SECG (Certicom) named elliptic curve)
var OBJ_ID_EC_384 = '06 05 2B81040022'.replace(/\s+/g, '').toLowerCase();
var ECDSACSR = {};
var ECDSA = {};
var DER = {};
var PEM = {};
var ASN1;
var Hex = {};
var AB = {};
//
// CSR - the main event
//
ECDSACSR.create = function createEcCsr(keypem, domains) {
var pemblock = PEM.parseBlock(keypem);
var ecpub = PEM.parseEcPubkey(pemblock.der);
var request = ECDSACSR.request(ecpub, domains);
return AB.fromHex(ECDSACSR.sign(keypem, request));
};
ECDSACSR.request = function createCsrBodyEc(xy, domains) {
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 += Hex.fromAB(xy.x);
if (xy.y) { hxy += Hex.fromAB(xy.y); }
// Sorry for the mess, but it is what it is
return ASN1('30'
// Version (0)
, ASN1.UInt('00')
// CN / Subject
, ASN1('30'
, ASN1('31'
, ASN1('30'
// object id (commonName)
, ASN1('06', '55 04 03')
, ASN1('0C', Hex.fromString(domains[0])))))
// EC P-256 Public Key
, ASN1('30'
, ASN1('30'
// 1.2.840.10045.2.1 ecPublicKey
// (ANSI X9.62 public key type)
, ASN1('06', '2A 86 48 CE 3D 02 01')
// 1.2.840.10045.3.1.7 prime256v1
// (ANSI X9.62 named elliptic curve)
, ASN1('06', '2A 86 48 CE 3D 03 01 07')
)
, ASN1.BitStr(compression + hxy))
// CSR Extension Subject Alternative Names
, ASN1('A0'
, ASN1('30'
// (extensionRequest (PKCS #9 via CRMF))
, ASN1('06', '2A 86 48 86 F7 0D 01 09 0E')
, ASN1('31'
, ASN1('30'
, ASN1('30'
// (subjectAltName (X.509 extension))
, ASN1('06', '55 1D 11')
, ASN1('04'
, ASN1('30', domains.map(function (d) {
return ASN1('82', Hex.fromString(d));
}).join(''))))))))
);
};
ECDSACSR.sign = function csrEcSig(keypem, request) {
var sig = ECDSA.sign(keypem, AB.fromHex(request));
var rLen = sig.r.byteLength;
var rc = '';
var sLen = sig.s.byteLength;
var sc = '';
if (0x80 & new Uint8Array(sig.r)[0]) { rc = '00'; rLen += 1; }
if (0x80 & new Uint8Array(sig.s)[0]) { sc = '00'; sLen += 1; }
return ASN1('30'
// The Full CSR Request Body
, request
// The Signature Type
, ASN1('30'
// 1.2.840.10045.4.3.2 ecdsaWithSHA256
// (ANSI X9.62 ECDSA algorithm with SHA256)
, ASN1('06', '2A 86 48 CE 3D 04 03 02')
)
// The Signature, embedded in a Bit Stream
, ASN1.BitStr(
// As far as I can tell this is a completely separate ASN.1 structure
// that just so happens to be embedded in a Bit String of another ASN.1
ASN1('30'
, ASN1.UInt(Hex.fromAB(sig.r))
, ASN1.UInt(Hex.fromAB(sig.s))))
);
};
//
// ECDSA
//
// Took some tips from https://gist.github.com/codermapuche/da4f96cdb6d5ff53b7ebc156ec46a10a
ECDSA.sign = function signEc(keypem, ab) {
// Signer is a stream
var sign = crypto.createSign('SHA256');
sign.write(new Uint8Array(ab));
sign.end();
// The signature is ASN1 encoded
var sig = sign.sign(keypem);
// Convert to a JavaScript ArrayBuffer just because
sig = new Uint8Array(sig.buffer.slice(sig.byteOffset, sig.byteOffset + sig.byteLength));
// The first two bytes '30 xx' signify SEQUENCE and LENGTH
// The sequence length byte will be a single byte because the signature is less that 128 bytes (0x80, 1024-bit)
// (this would not be true for P-521, but I'm not supporting that yet)
// The 3rd byte will be '02', signifying INTEGER
// The 4th byte will tell us the length of 'r' (which, on occassion, will be less than the full 255 bytes)
var rIndex = 3;
var rLen = sig[rIndex];
var rEnd = rIndex + 1 + rLen;
var sIndex = rEnd + 1;
var sLen = sig[sIndex];
var sEnd = sIndex + 1 + sLen;
var r = sig.slice(rIndex + 1, rEnd);
var s = sig.slice(sIndex + 1, sEnd); // this should be end-of-file
// ASN1 INTEGER types use the high-order bit to signify a negative number,
// hence a leading '00' is used for numbers that begin with '80' or greater
// which is why r length is sometimes a byte longer than its bit length
if (0 === s[0]) { s = s.slice(1); }
if (0 === r[0]) { r = r.slice(1); }
return { raw: sig.buffer, r: r.buffer, s: s.buffer };
};