🔱: [acme] sync upgrade with 21 commits [trident-sync]

Bump v5.0.0
This commit is contained in:
xiaojunnuo
2023-01-29 15:27:11 +08:00
parent 62e3945d30
commit f64ea78c44
54 changed files with 8136 additions and 2 deletions
@@ -0,0 +1,448 @@
/**
* Legacy node-forge crypto interface
*
* DEPRECATION WARNING: This crypto interface is deprecated and will be removed from acme-client in a future
* major release. Please migrate to the new `acme.crypto` interface at your earliest convenience.
*
* @namespace forge
*/
const net = require('net');
const { promisify } = require('util');
const forge = require('node-forge');
const generateKeyPair = promisify(forge.pki.rsa.generateKeyPair);
/**
* Attempt to parse forge object from PEM encoded string
*
* @private
* @param {string} input PEM string
* @return {object}
*/
function forgeObjectFromPem(input) {
const msg = forge.pem.decode(input)[0];
let result;
switch (msg.type) {
case 'PRIVATE KEY':
case 'RSA PRIVATE KEY':
result = forge.pki.privateKeyFromPem(input);
break;
case 'PUBLIC KEY':
case 'RSA PUBLIC KEY':
result = forge.pki.publicKeyFromPem(input);
break;
case 'CERTIFICATE':
case 'X509 CERTIFICATE':
case 'TRUSTED CERTIFICATE':
result = forge.pki.certificateFromPem(input).publicKey;
break;
case 'CERTIFICATE REQUEST':
result = forge.pki.certificationRequestFromPem(input).publicKey;
break;
default:
throw new Error('Unable to detect forge message type');
}
return result;
}
/**
* Parse domain names from a certificate or CSR
*
* @private
* @param {object} obj Forge certificate or CSR
* @returns {object} {commonName, altNames}
*/
function parseDomains(obj) {
let commonName = null;
let altNames = [];
let altNamesDict = [];
const commonNameObject = (obj.subject.attributes || []).find((a) => a.name === 'commonName');
const rootAltNames = (obj.extensions || []).find((e) => 'altNames' in e);
const rootExtensions = (obj.attributes || []).find((a) => 'extensions' in a);
if (rootAltNames && rootAltNames.altNames && rootAltNames.altNames.length) {
altNamesDict = rootAltNames.altNames;
}
else if (rootExtensions && rootExtensions.extensions && rootExtensions.extensions.length) {
const extAltNames = rootExtensions.extensions.find((e) => 'altNames' in e);
if (extAltNames && extAltNames.altNames && extAltNames.altNames.length) {
altNamesDict = extAltNames.altNames;
}
}
if (commonNameObject) {
commonName = commonNameObject.value;
}
if (altNamesDict) {
altNames = altNamesDict.map((a) => a.value);
}
return {
commonName,
altNames
};
}
/**
* Generate a private RSA key
*
* @param {number} [size] Size of the key, default: `2048`
* @returns {Promise<buffer>} PEM encoded private RSA key
*
* @example Generate private RSA key
* ```js
* const privateKey = await acme.forge.createPrivateKey();
* ```
*
* @example Private RSA key with defined size
* ```js
* const privateKey = await acme.forge.createPrivateKey(4096);
* ```
*/
async function createPrivateKey(size = 2048) {
const keyPair = await generateKeyPair({ bits: size });
const pemKey = forge.pki.privateKeyToPem(keyPair.privateKey);
return Buffer.from(pemKey);
}
exports.createPrivateKey = createPrivateKey;
/**
* Create public key from a private RSA key
*
* @param {buffer|string} key PEM encoded private RSA key
* @returns {Promise<buffer>} PEM encoded public RSA key
*
* @example Create public key
* ```js
* const publicKey = await acme.forge.createPublicKey(privateKey);
* ```
*/
exports.createPublicKey = async function(key) {
const privateKey = forge.pki.privateKeyFromPem(key);
const publicKey = forge.pki.rsa.setPublicKey(privateKey.n, privateKey.e);
const pemKey = forge.pki.publicKeyToPem(publicKey);
return Buffer.from(pemKey);
};
/**
* Parse body of PEM encoded object from buffer or string
* If multiple objects are chained, the first body will be returned
*
* @param {buffer|string} str PEM encoded buffer or string
* @returns {string} PEM body
*/
exports.getPemBody = (str) => {
const msg = forge.pem.decode(str)[0];
return forge.util.encode64(msg.body);
};
/**
* Split chain of PEM encoded objects from buffer or string into array
*
* @param {buffer|string} str PEM encoded buffer or string
* @returns {string[]} Array of PEM bodies
*/
exports.splitPemChain = (str) => forge.pem.decode(str).map(forge.pem.encode);
/**
* Get modulus
*
* @param {buffer|string} input PEM encoded private key, certificate or CSR
* @returns {Promise<buffer>} Modulus
*
* @example Get modulus
* ```js
* const m1 = await acme.forge.getModulus(privateKey);
* const m2 = await acme.forge.getModulus(certificate);
* const m3 = await acme.forge.getModulus(certificateRequest);
* ```
*/
exports.getModulus = async function(input) {
if (!Buffer.isBuffer(input)) {
input = Buffer.from(input);
}
const obj = forgeObjectFromPem(input);
return Buffer.from(forge.util.hexToBytes(obj.n.toString(16)), 'binary');
};
/**
* Get public exponent
*
* @param {buffer|string} input PEM encoded private key, certificate or CSR
* @returns {Promise<buffer>} Exponent
*
* @example Get public exponent
* ```js
* const e1 = await acme.forge.getPublicExponent(privateKey);
* const e2 = await acme.forge.getPublicExponent(certificate);
* const e3 = await acme.forge.getPublicExponent(certificateRequest);
* ```
*/
exports.getPublicExponent = async function(input) {
if (!Buffer.isBuffer(input)) {
input = Buffer.from(input);
}
const obj = forgeObjectFromPem(input);
return Buffer.from(forge.util.hexToBytes(obj.e.toString(16)), 'binary');
};
/**
* Read domains from a Certificate Signing Request
*
* @param {buffer|string} csr PEM encoded Certificate Signing Request
* @returns {Promise<object>} {commonName, altNames}
*
* @example Read Certificate Signing Request domains
* ```js
* const { commonName, altNames } = await acme.forge.readCsrDomains(certificateRequest);
*
* console.log(`Common name: ${commonName}`);
* console.log(`Alt names: ${altNames.join(', ')}`);
* ```
*/
exports.readCsrDomains = async function(csr) {
if (!Buffer.isBuffer(csr)) {
csr = Buffer.from(csr);
}
const obj = forge.pki.certificationRequestFromPem(csr);
return parseDomains(obj);
};
/**
* Read information from a certificate
*
* @param {buffer|string} cert PEM encoded certificate
* @returns {Promise<object>} Certificate info
*
* @example Read certificate information
* ```js
* const info = await acme.forge.readCertificateInfo(certificate);
* const { commonName, altNames } = info.domains;
*
* console.log(`Not after: ${info.notAfter}`);
* console.log(`Not before: ${info.notBefore}`);
*
* console.log(`Common name: ${commonName}`);
* console.log(`Alt names: ${altNames.join(', ')}`);
* ```
*/
exports.readCertificateInfo = async function(cert) {
if (!Buffer.isBuffer(cert)) {
cert = Buffer.from(cert);
}
const obj = forge.pki.certificateFromPem(cert);
const issuerCn = (obj.issuer.attributes || []).find((a) => a.name === 'commonName');
return {
issuer: {
commonName: issuerCn ? issuerCn.value : null
},
domains: parseDomains(obj),
notAfter: obj.validity.notAfter,
notBefore: obj.validity.notBefore
};
};
/**
* Determine ASN.1 type for CSR subject short name
* Note: https://tools.ietf.org/html/rfc5280
*
* @private
* @param {string} shortName CSR subject short name
* @returns {forge.asn1.Type} ASN.1 type
*/
function getCsrValueTagClass(shortName) {
switch (shortName) {
case 'C':
return forge.asn1.Type.PRINTABLESTRING;
case 'E':
return forge.asn1.Type.IA5STRING;
default:
return forge.asn1.Type.UTF8;
}
}
/**
* Create array of short names and values for Certificate Signing Request subjects
*
* @private
* @param {object} subjectObj Key-value of short names and values
* @returns {object[]} Certificate Signing Request subject array
*/
function createCsrSubject(subjectObj) {
return Object.entries(subjectObj).reduce((result, [shortName, value]) => {
if (value) {
const valueTagClass = getCsrValueTagClass(shortName);
result.push({ shortName, value, valueTagClass });
}
return result;
}, []);
}
/**
* Create array of alt names for Certificate Signing Requests
* Note: https://github.com/digitalbazaar/forge/blob/dfdde475677a8a25c851e33e8f81dca60d90cfb9/lib/x509.js#L1444-L1454
*
* @private
* @param {string[]} altNames Alt names
* @returns {object[]} Certificate Signing Request alt names array
*/
function formatCsrAltNames(altNames) {
return altNames.map((value) => {
const type = net.isIP(value) ? 7 : 2;
return { type, value };
});
}
/**
* Create a Certificate Signing Request
*
* @param {object} data
* @param {number} [data.keySize] Size of newly created private key, default: `2048`
* @param {string} [data.commonName]
* @param {array} [data.altNames] default: `[]`
* @param {string} [data.country]
* @param {string} [data.state]
* @param {string} [data.locality]
* @param {string} [data.organization]
* @param {string} [data.organizationUnit]
* @param {string} [data.emailAddress]
* @param {buffer|string} [key] CSR private key
* @returns {Promise<buffer[]>} [privateKey, certificateSigningRequest]
*
* @example Create a Certificate Signing Request
* ```js
* const [certificateKey, certificateRequest] = await acme.forge.createCsr({
* commonName: 'test.example.com'
* });
* ```
*
* @example Certificate Signing Request with both common and alternative names
* ```js
* const [certificateKey, certificateRequest] = await acme.forge.createCsr({
* keySize: 4096,
* commonName: 'test.example.com',
* altNames: ['foo.example.com', 'bar.example.com']
* });
* ```
*
* @example Certificate Signing Request with additional information
* ```js
* const [certificateKey, certificateRequest] = await acme.forge.createCsr({
* commonName: 'test.example.com',
* country: 'US',
* state: 'California',
* locality: 'Los Angeles',
* organization: 'The Company Inc.',
* organizationUnit: 'IT Department',
* emailAddress: 'contact@example.com'
* });
* ```
*
* @example Certificate Signing Request with predefined private key
* ```js
* const certificateKey = await acme.forge.createPrivateKey();
*
* const [, certificateRequest] = await acme.forge.createCsr({
* commonName: 'test.example.com'
* }, certificateKey);
*/
exports.createCsr = async function(data, key = null) {
if (!key) {
key = await createPrivateKey(data.keySize);
}
else if (!Buffer.isBuffer(key)) {
key = Buffer.from(key);
}
if (typeof data.altNames === 'undefined') {
data.altNames = [];
}
const csr = forge.pki.createCertificationRequest();
/* Public key */
const privateKey = forge.pki.privateKeyFromPem(key);
const publicKey = forge.pki.rsa.setPublicKey(privateKey.n, privateKey.e);
csr.publicKey = publicKey;
/* Ensure subject common name is present in SAN - https://cabforum.org/wp-content/uploads/BRv1.2.3.pdf */
if (data.commonName && !data.altNames.includes(data.commonName)) {
data.altNames.unshift(data.commonName);
}
/* Subject */
const subject = createCsrSubject({
CN: data.commonName,
C: data.country,
ST: data.state,
L: data.locality,
O: data.organization,
OU: data.organizationUnit,
E: data.emailAddress
});
csr.setSubject(subject);
/* SAN extension */
if (data.altNames.length) {
csr.setAttributes([{
name: 'extensionRequest',
extensions: [{
name: 'subjectAltName',
altNames: formatCsrAltNames(data.altNames)
}]
}]);
}
/* Sign CSR using SHA-256 */
csr.sign(privateKey, forge.md.sha256.create());
/* Done */
const pemCsr = forge.pki.certificationRequestToPem(csr);
return [key, Buffer.from(pemCsr)];
};
@@ -0,0 +1,526 @@
/**
* Native Node.js crypto interface
*
* @namespace crypto
*/
const net = require('net');
const { promisify } = require('util');
const crypto = require('crypto');
const jsrsasign = require('jsrsasign');
const generateKeyPair = promisify(crypto.generateKeyPair);
/**
* Determine key type and info by attempting to derive public key
*
* @private
* @param {buffer|string} keyPem PEM encoded private or public key
* @returns {object}
*/
function getKeyInfo(keyPem) {
const result = {
isRSA: false,
isECDSA: false,
signatureAlgorithm: null,
publicKey: crypto.createPublicKey(keyPem)
};
if (result.publicKey.asymmetricKeyType === 'rsa') {
result.isRSA = true;
result.signatureAlgorithm = 'SHA256withRSA';
}
else if (result.publicKey.asymmetricKeyType === 'ec') {
result.isECDSA = true;
result.signatureAlgorithm = 'SHA256withECDSA';
}
else {
throw new Error('Unable to parse key information, unknown format');
}
return result;
}
/**
* Generate a private RSA key
*
* @param {number} [modulusLength] Size of the keys modulus in bits, default: `2048`
* @returns {Promise<buffer>} PEM encoded private RSA key
*
* @example Generate private RSA key
* ```js
* const privateKey = await acme.crypto.createPrivateRsaKey();
* ```
*
* @example Private RSA key with modulus size 4096
* ```js
* const privateKey = await acme.crypto.createPrivateRsaKey(4096);
* ```
*/
async function createPrivateRsaKey(modulusLength = 2048) {
const pair = await generateKeyPair('rsa', {
modulusLength,
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
});
return Buffer.from(pair.privateKey);
}
exports.createPrivateRsaKey = createPrivateRsaKey;
/**
* Alias of `createPrivateRsaKey()`
*
* @function
*/
exports.createPrivateKey = createPrivateRsaKey;
/**
* Generate a private ECDSA key
*
* @param {string} [namedCurve] ECDSA curve name (P-256, P-384 or P-521), default `P-256`
* @returns {Promise<buffer>} PEM encoded private ECDSA key
*
* @example Generate private ECDSA key
* ```js
* const privateKey = await acme.crypto.createPrivateEcdsaKey();
* ```
*
* @example Private ECDSA key using P-384 curve
* ```js
* const privateKey = await acme.crypto.createPrivateEcdsaKey('P-384');
* ```
*/
exports.createPrivateEcdsaKey = async (namedCurve = 'P-256') => {
const pair = await generateKeyPair('ec', {
namedCurve,
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
});
return Buffer.from(pair.privateKey);
};
/**
* Get a public key derived from a RSA or ECDSA key
*
* @param {buffer|string} keyPem PEM encoded private or public key
* @returns {buffer} PEM encoded public key
*
* @example Get public key
* ```js
* const publicKey = acme.crypto.getPublicKey(privateKey);
* ```
*/
exports.getPublicKey = (keyPem) => {
const info = getKeyInfo(keyPem);
const publicKey = info.publicKey.export({
type: info.isECDSA ? 'spki' : 'pkcs1',
format: 'pem'
});
return Buffer.from(publicKey);
};
/**
* Get a JSON Web Key derived from a RSA or ECDSA key
*
* https://datatracker.ietf.org/doc/html/rfc7517
*
* @param {buffer|string} keyPem PEM encoded private or public key
* @returns {object} JSON Web Key
*
* @example Get JWK
* ```js
* const jwk = acme.crypto.getJwk(privateKey);
* ```
*/
function getJwk(keyPem) {
const jwk = crypto.createPublicKey(keyPem).export({
format: 'jwk'
});
/* Sort keys */
return Object.keys(jwk).sort().reduce((result, k) => {
result[k] = jwk[k];
return result;
}, {});
}
exports.getJwk = getJwk;
/**
* Fix missing support for NIST curve names in jsrsasign
*
* @private
* @param {string} crv NIST curve name
* @returns {string} SECG curve name
*/
function convertNistCurveNameToSecg(nistName) {
switch (nistName) {
case 'P-256':
return 'secp256r1';
case 'P-384':
return 'secp384r1';
case 'P-521':
return 'secp521r1';
default:
return nistName;
}
}
/**
* Split chain of PEM encoded objects from string into array
*
* @param {buffer|string} chainPem PEM encoded object chain
* @returns {array} Array of PEM objects including headers
*/
function splitPemChain(chainPem) {
if (Buffer.isBuffer(chainPem)) {
chainPem = chainPem.toString();
}
return chainPem
/* Split chain into chunks, starting at every header */
.split(/\s*(?=-----BEGIN [A-Z0-9- ]+-----\r?\n?)/g)
/* Match header, PEM body and footer */
.map((pem) => pem.match(/\s*-----BEGIN ([A-Z0-9- ]+)-----\r?\n?([\S\s]+)\r?\n?-----END \1-----/))
/* Filter out non-matches or empty bodies */
.filter((pem) => pem && pem[2] && pem[2].replace(/[\r\n]+/g, '').trim())
/* Decode to hex, and back to PEM for formatting etc */
.map(([pem, header]) => jsrsasign.hextopem(jsrsasign.pemtohex(pem, header), header));
}
exports.splitPemChain = splitPemChain;
/**
* Parse body of PEM encoded object and return a Base64URL string
* If multiple objects are chained, the first body will be returned
*
* @param {buffer|string} pem PEM encoded chain or object
* @returns {string} Base64URL-encoded body
*/
exports.getPemBodyAsB64u = (pem) => {
const chain = splitPemChain(pem);
if (!chain.length) {
throw new Error('Unable to parse PEM body from string');
}
/* First object, hex and back to b64 without new lines */
return jsrsasign.hextob64u(jsrsasign.pemtohex(chain[0]));
};
/**
* Parse common name from a subject object
*
* @private
* @param {object} subj Subject returned from jsrsasign
* @returns {string} Common name value
*/
function parseCommonName(subj) {
const subjectArr = (subj && subj.array) ? subj.array : [];
const cnArr = subjectArr.find((s) => (s[0] && s[0].type && s[0].value && (s[0].type === 'CN')));
return (cnArr && cnArr.length && cnArr[0].value) ? cnArr[0].value : null;
}
/**
* Parse domains from a certificate or CSR
*
* @private
* @param {object} params Certificate or CSR params returned from jsrsasign
* @returns {object} {commonName, altNames}
*/
function parseDomains(params) {
const commonName = parseCommonName(params.subject);
const extensionArr = (params.ext || params.extreq || []);
let altNames = [];
if (extensionArr && extensionArr.length) {
const altNameExt = extensionArr.find((e) => (e.extname && (e.extname === 'subjectAltName')));
const altNameArr = (altNameExt && altNameExt.array && altNameExt.array.length) ? altNameExt.array : [];
altNames = altNameArr.map((a) => Object.values(a)[0] || null).filter((a) => a);
}
return {
commonName,
altNames
};
}
/**
* Read domains from a Certificate Signing Request
*
* @param {buffer|string} csrPem PEM encoded Certificate Signing Request
* @returns {object} {commonName, altNames}
*
* @example Read Certificate Signing Request domains
* ```js
* const { commonName, altNames } = acme.crypto.readCsrDomains(certificateRequest);
*
* console.log(`Common name: ${commonName}`);
* console.log(`Alt names: ${altNames.join(', ')}`);
* ```
*/
exports.readCsrDomains = (csrPem) => {
if (Buffer.isBuffer(csrPem)) {
csrPem = csrPem.toString();
}
/* Parse CSR */
const params = jsrsasign.KJUR.asn1.csr.CSRUtil.getParam(csrPem);
return parseDomains(params);
};
/**
* Read information from a certificate
* If multiple certificates are chained, the first will be read
*
* @param {buffer|string} certPem PEM encoded certificate or chain
* @returns {object} Certificate info
*
* @example Read certificate information
* ```js
* const info = acme.crypto.readCertificateInfo(certificate);
* const { commonName, altNames } = info.domains;
*
* console.log(`Not after: ${info.notAfter}`);
* console.log(`Not before: ${info.notBefore}`);
*
* console.log(`Common name: ${commonName}`);
* console.log(`Alt names: ${altNames.join(', ')}`);
* ```
*/
exports.readCertificateInfo = (certPem) => {
const chain = splitPemChain(certPem);
if (!chain.length) {
throw new Error('Unable to parse PEM body from string');
}
/* Parse certificate */
const obj = new jsrsasign.X509();
obj.readCertPEM(chain[0]);
const params = obj.getParam();
return {
issuer: {
commonName: parseCommonName(params.issuer)
},
domains: parseDomains(params),
notBefore: jsrsasign.zulutodate(params.notbefore),
notAfter: jsrsasign.zulutodate(params.notafter)
};
};
/**
* Determine ASN.1 character string type for CSR subject field
*
* https://tools.ietf.org/html/rfc5280
* https://github.com/kjur/jsrsasign/blob/2613c64559768b91dde9793dfa318feacb7c3b8a/src/x509-1.1.js#L2404-L2412
* https://github.com/kjur/jsrsasign/blob/2613c64559768b91dde9793dfa318feacb7c3b8a/src/asn1x509-1.0.js#L3526-L3535
*
* @private
* @param {string} field CSR subject field
* @returns {string} ASN.1 jsrsasign character string type
*/
function getCsrAsn1CharStringType(field) {
switch (field) {
case 'C':
return 'prn';
case 'E':
return 'ia5';
default:
return 'utf8';
}
}
/**
* Create array of subject fields for a Certificate Signing Request
*
* @private
* @param {object} input Key-value of subject fields
* @returns {object[]} Certificate Signing Request subject array
*/
function createCsrSubject(input) {
return Object.entries(input).reduce((result, [type, value]) => {
if (value) {
const ds = getCsrAsn1CharStringType(type);
result.push([{ type, value, ds }]);
}
return result;
}, []);
}
/**
* Create array of alt names for Certificate Signing Requests
*
* https://github.com/kjur/jsrsasign/blob/3edc0070846922daea98d9588978e91d855577ec/src/x509-1.1.js#L1355-L1410
*
* @private
* @param {string[]} altNames Array of alt names
* @returns {object[]} Certificate Signing Request alt names array
*/
function formatCsrAltNames(altNames) {
return altNames.map((value) => {
const key = net.isIP(value) ? 'ip' : 'dns';
return { [key]: value };
});
}
/**
* Create a Certificate Signing Request
*
* @param {object} data
* @param {number} [data.keySize] Size of newly created RSA private key modulus in bits, default: `2048`
* @param {string} [data.commonName] FQDN of your server
* @param {array} [data.altNames] SAN (Subject Alternative Names), default: `[]`
* @param {string} [data.country] 2 letter country code
* @param {string} [data.state] State or province
* @param {string} [data.locality] City
* @param {string} [data.organization] Organization name
* @param {string} [data.organizationUnit] Organizational unit name
* @param {string} [data.emailAddress] Email address
* @param {string} [keyPem] PEM encoded CSR private key
* @returns {Promise<buffer[]>} [privateKey, certificateSigningRequest]
*
* @example Create a Certificate Signing Request
* ```js
* const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
* commonName: 'test.example.com'
* });
* ```
*
* @example Certificate Signing Request with both common and alternative names
* ```js
* const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
* keySize: 4096,
* commonName: 'test.example.com',
* altNames: ['foo.example.com', 'bar.example.com']
* });
* ```
*
* @example Certificate Signing Request with additional information
* ```js
* const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
* commonName: 'test.example.com',
* country: 'US',
* state: 'California',
* locality: 'Los Angeles',
* organization: 'The Company Inc.',
* organizationUnit: 'IT Department',
* emailAddress: 'contact@example.com'
* });
* ```
*
* @example Certificate Signing Request with ECDSA private key
* ```js
* const certificateKey = await acme.crypto.createPrivateEcdsaKey();
*
* const [, certificateRequest] = await acme.crypto.createCsr({
* commonName: 'test.example.com'
* }, certificateKey);
*/
exports.createCsr = async (data, keyPem = null) => {
if (!keyPem) {
keyPem = await createPrivateRsaKey(data.keySize);
}
else if (!Buffer.isBuffer(keyPem)) {
keyPem = Buffer.from(keyPem);
}
if (typeof data.altNames === 'undefined') {
data.altNames = [];
}
/* Get key info and JWK */
const info = getKeyInfo(keyPem);
const jwk = getJwk(keyPem);
const extensionRequests = [];
/* Missing support for NIST curve names in jsrsasign - https://github.com/kjur/jsrsasign/blob/master/src/asn1x509-1.0.js#L4388-L4393 */
if (jwk.crv && (jwk.kty === 'EC')) {
jwk.crv = convertNistCurveNameToSecg(jwk.crv);
}
/* Ensure subject common name is present in SAN - https://cabforum.org/wp-content/uploads/BRv1.2.3.pdf */
if (data.commonName && !data.altNames.includes(data.commonName)) {
data.altNames.unshift(data.commonName);
}
/* Subject */
const subject = createCsrSubject({
CN: data.commonName,
C: data.country,
ST: data.state,
L: data.locality,
O: data.organization,
OU: data.organizationUnit,
E: data.emailAddress
});
/* SAN extension */
if (data.altNames.length) {
extensionRequests.push({
extname: 'subjectAltName',
array: formatCsrAltNames(data.altNames)
});
}
/* Create CSR */
const csr = new jsrsasign.KJUR.asn1.csr.CertificationRequest({
subject: { array: subject },
sigalg: info.signatureAlgorithm,
sbjprvkey: keyPem.toString(),
sbjpubkey: jwk,
extreq: extensionRequests
});
/* Sign CSR, get PEM */
csr.sign();
const pem = csr.getPEM();
/* Done */
return [keyPem, Buffer.from(pem)];
};