mirror of
https://git.coolaj86.com/coolaj86/bluecrypt-keypairs.js
synced 2025-04-21 14:20:37 +00:00
158 lines
4.9 KiB
JavaScript
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 };
|
|
};
|