bluecrypt-keypairs.js/lib/csr.js

190 lines
5.2 KiB
JavaScript
Raw Normal View History

2019-05-04 06:59:47 +00:00
// Copyright 2018-present AJ ONeal. All rights reserved
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
(function (exports) {
2019-05-04 05:58:35 +00:00
'use strict';
2019-05-04 06:59:47 +00:00
var ASN1 = exports.ASN1;
var Enc = exports.Enc;
var PEM = exports.PEM;
var X509 = exports.x509;
var Keypairs = exports.Keypairs;
2019-05-04 05:58:35 +00:00
2019-05-04 06:59:47 +00:00
// TODO find a way that the prior node-ish way of `module.exports = function () {}` isn't broken
var CSR = exports.CSR = function (opts) {
2019-05-04 05:58:35 +00:00
// We're using a Promise here to be compatible with the browser version
// which will probably use the webcrypto API for some of the conversions
opts = CSR._prepare(opts);
return CSR.create(opts).then(function (bytes) {
return CSR._encode(opts, bytes);
});
};
CSR._prepare = function (opts) {
var Rasha;
opts = JSON.parse(JSON.stringify(opts));
var pem, jwk;
// We do a bit of extra error checking for user convenience
if (!opts) { throw new Error("You must pass options with key and domains to rsacsr"); }
if (!Array.isArray(opts.domains) || 0 === opts.domains.length) {
new Error("You must pass options.domains as a non-empty array");
}
// I need to check that 例.中国 is a valid domain name
if (!opts.domains.every(function (d) {
// allow punycode? xn--
if ('string' === typeof d /*&& /\./.test(d) && !/--/.test(d)*/) {
return true;
}
})) {
throw new Error("You must pass options.domains as strings");
}
if (opts.pem) {
pem = opts.pem;
} else if (opts.jwk) {
jwk = opts.jwk;
} else {
if (!opts.key) {
throw new Error("You must pass options.key as a JSON web key");
} else if (opts.key.kty) {
jwk = opts.key;
} else {
pem = opts.key;
}
}
if (pem) {
try {
Rasha = require('rasha');
} catch(e) {
throw new Error("Rasha.js is an optional dependency for PEM-to-JWK.\n"
+ "Install it if you'd like to use it:\n"
+ "\tnpm install --save rasha\n"
+ "Otherwise supply a jwk as the private key."
);
}
jwk = Rasha.importSync({ pem: pem });
}
opts.jwk = jwk;
return opts;
};
2019-05-04 06:59:47 +00:00
2019-05-04 05:58:35 +00:00
CSR._encode = function (opts, bytes) {
if ('der' === (opts.encoding||'').toLowerCase()) {
return bytes;
}
return PEM.packBlock({
type: "CERTIFICATE REQUEST"
, bytes: bytes /* { jwk: jwk, domains: opts.domains } */
});
};
CSR.create = function createCsr(opts) {
var hex = CSR.request(opts.jwk, opts.domains);
return CSR.sign(opts.jwk, hex).then(function (csr) {
return Enc.hexToBuf(csr);
});
};
2019-05-04 06:59:47 +00:00
//
// RSA
//
//
2019-05-04 05:58:35 +00:00
CSR.request = function createCsrBodyEc(jwk, domains) {
2019-05-04 06:59:47 +00:00
var asn1pub = X509.packCsrRsaPublicKey(jwk);
return X509.packCsrRsa(asn1pub, domains);
2019-05-04 05:58:35 +00:00
};
CSR.sign = function csrEcSig(jwk, request) {
2019-05-04 06:59:47 +00:00
// Took some tips from https://gist.github.com/codermapuche/da4f96cdb6d5ff53b7ebc156ec46a10a
// TODO will have to convert web ECDSA signatures to PEM ECDSA signatures (but RSA should be the same)
// TODO have a consistent non-private way to sign
return Keypairs._sign({ jwk: jwk }, Enc.hexToBuf(request)).then(function (sig) {
2019-05-04 05:58:35 +00:00
return CSR.toDer({ request: request, signature: sig });
});
};
2019-05-04 06:59:47 +00:00
2019-05-04 05:58:35 +00:00
CSR.toDer = function encode(opts) {
var sty = ASN1('30'
// 1.2.840.113549.1.1.11 sha256WithRSAEncryption (PKCS #1)
, ASN1('06', '2a864886f70d01010b')
, ASN1('05')
);
return ASN1('30'
// The Full CSR Request Body
, opts.request
// The Signature Type
, sty
// The Signature
, ASN1.BitStr(Enc.bufToHex(opts.signature))
);
};
X509.packCsrRsa = function (asn1pubkey, domains) {
return ASN1('30'
// Version (0)
, ASN1.UInt('00')
// 2.5.4.3 commonName (X.520 DN component)
, ASN1('30', ASN1('31', ASN1('30', ASN1('06', '550403'), ASN1('0c', Enc.utf8ToHex(domains[0])))))
// Public Key (RSA or EC)
, asn1pubkey
// Request Body
, ASN1('a0'
, ASN1('30'
// 1.2.840.113549.1.9.14 extensionRequest (PKCS #9 via CRMF)
, ASN1('06', '2a864886f70d01090e')
, ASN1('31'
, ASN1('30'
, ASN1('30'
// 2.5.29.17 subjectAltName (X.509 extension)
, ASN1('06', '551d11')
, ASN1('04'
, ASN1('30', domains.map(function (d) {
return ASN1('82', Enc.utf8ToHex(d));
}).join(''))))))))
);
};
X509.packPkcs1 = function (jwk) {
var n = ASN1.UInt(Enc.base64ToHex(jwk.n));
var e = ASN1.UInt(Enc.base64ToHex(jwk.e));
if (!jwk.d) {
return Enc.hexToBuf(ASN1('30', n, e));
}
return Enc.hexToBuf(ASN1('30'
, ASN1.UInt('00')
, n
, e
, ASN1.UInt(Enc.base64ToHex(jwk.d))
, ASN1.UInt(Enc.base64ToHex(jwk.p))
, ASN1.UInt(Enc.base64ToHex(jwk.q))
, ASN1.UInt(Enc.base64ToHex(jwk.dp))
, ASN1.UInt(Enc.base64ToHex(jwk.dq))
, ASN1.UInt(Enc.base64ToHex(jwk.qi))
));
};
X509.packCsrRsaPublicKey = function (jwk) {
// Sequence the key
var n = ASN1.UInt(Enc.base64ToHex(jwk.n));
var e = ASN1.UInt(Enc.base64ToHex(jwk.e));
var asn1pub = ASN1('30', n, e);
//var asn1pub = X509.packPkcs1({ kty: jwk.kty, n: jwk.n, e: jwk.e });
// Add the CSR pub key header
return ASN1('30', ASN1('30', ASN1('06', '2a864886f70d010101'), ASN1('05')), ASN1.BitStr(asn1pub));
};
2019-05-04 06:59:47 +00:00
}('undefined' === typeof window ? module.exports : window));