2026-05-05 19:17:44 +08:00
// @ts-nocheck
2023-01-29 15:27:11 +08:00
/**
* Native Node.js crypto interface
*
* @namespace crypto
*/
2024-11-12 12:15:06 +08:00
import net from 'net' ;
import { promisify } from 'util' ;
import crypto from 'crypto' ;
import asn1js from 'asn1js' ;
import x509 from '@peculiar/x509' ;
2023-01-29 15:27:11 +08:00
2024-01-30 19:24:20 +00:00
const randomInt = promisify ( crypto . randomInt ) ;
2023-01-29 15:27:11 +08:00
const generateKeyPair = promisify ( crypto . generateKeyPair ) ;
2024-02-03 19:24:11 +00:00
/* Use Node.js Web Crypto API */
x509 . cryptoProvider . set ( crypto . webcrypto ) ;
/* id-ce-subjectAltName - https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 */
const subjectAltNameOID = '2.5.29.17' ;
/* id-pe-acmeIdentifier - https://datatracker.ietf.org/doc/html/rfc8737#section-6.1 */
2024-01-30 19:24:20 +00:00
const alpnAcmeIdentifierOID = '1.3.6.1.5.5.7.1.31' ;
2023-01-29 15:27:11 +08:00
/**
* 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 ,
2024-05-22 19:24:07 +00:00
publicKey : crypto.createPublicKey ( keyPem ) ,
2023-01-29 15:27:11 +08:00
} ;
if ( result . publicKey . asymmetricKeyType === 'rsa' ) {
result . isRSA = true ;
}
else if ( result . publicKey . asymmetricKeyType === 'ec' ) {
result . isECDSA = true ;
}
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);
* ```
*/
2026-05-05 19:17:44 +08:00
export async function createPrivateRsaKey ( modulusLength = 2048 , encodingType = 'pkcs8' ) : Promise < Buffer > {
2023-01-29 15:27:11 +08:00
const pair = await generateKeyPair ( 'rsa' , {
modulusLength ,
privateKeyEncoding : {
2024-09-23 14:32:57 +08:00
type : encodingType ,
2024-05-22 19:24:07 +00:00
format : 'pem' ,
} ,
2023-01-29 15:27:11 +08:00
} ) ;
return Buffer . from ( pair . privateKey ) ;
}
/**
* Alias of `createPrivateRsaKey()`
*
* @function
*/
2024-11-12 12:15:06 +08:00
export const createPrivateKey = createPrivateRsaKey ;
2023-01-29 15:27:11 +08:00
/**
* 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');
* ```
*/
2026-05-05 19:17:44 +08:00
export const createPrivateEcdsaKey = async ( namedCurve = 'P-256' , encodingType = 'pkcs8' ) : Promise < Buffer > = > {
2023-01-29 15:27:11 +08:00
const pair = await generateKeyPair ( 'ec' , {
namedCurve ,
privateKeyEncoding : {
2024-09-23 14:32:57 +08:00
type : encodingType ,
2024-05-22 19:24:07 +00:00
format : 'pem' ,
} ,
2023-01-29 15:27:11 +08:00
} ) ;
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);
* ```
*/
2026-05-05 19:17:44 +08:00
export const getPublicKey = ( keyPem ) : Buffer = > {
2023-01-29 15:27:11 +08:00
const info = getKeyInfo ( keyPem ) ;
const publicKey = info . publicKey . export ( {
type : info . isECDSA ? 'spki' : 'pkcs1' ,
2024-05-22 19:24:07 +00:00
format : 'pem' ,
2023-01-29 15:27:11 +08:00
} ) ;
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);
* ```
*/
2024-11-12 12:15:06 +08:00
export function getJwk ( keyPem ) {
2023-01-29 15:27:11 +08:00
const jwk = crypto . createPublicKey ( keyPem ) . export ( {
2024-05-22 19:24:07 +00:00
format : 'jwk' ,
2023-01-29 15:27:11 +08:00
} ) ;
/* Sort keys */
return Object . keys ( jwk ) . sort ( ) . reduce ( ( result , k ) = > {
result [ k ] = jwk [ k ] ;
return result ;
} , { } ) ;
}
/**
2024-02-03 19:24:11 +00:00
* Produce CryptoKeyPair and signing algorithm from a PEM encoded private key
2023-01-29 15:27:11 +08:00
*
* @private
2024-02-03 19:24:11 +00:00
* @param {buffer|string} keyPem PEM encoded private key
* @returns {Promise<array>} [keyPair, signingAlgorithm]
2023-01-29 15:27:11 +08:00
*/
2024-02-03 19:24:11 +00:00
async function getWebCryptoKeyPair ( keyPem ) {
const info = getKeyInfo ( keyPem ) ;
const jwk = getJwk ( keyPem ) ;
/* Signing algorithm */
const sigalg = {
name : 'RSASSA-PKCS1-v1_5' ,
2024-05-22 19:24:07 +00:00
hash : { name : 'SHA-256' } ,
2024-02-03 19:24:11 +00:00
} ;
if ( info . isECDSA ) {
sigalg . name = 'ECDSA' ;
sigalg . namedCurve = jwk . crv ;
if ( jwk . crv === 'P-384' ) {
sigalg . hash . name = 'SHA-384' ;
}
if ( jwk . crv === 'P-521' ) {
sigalg . hash . name = 'SHA-512' ;
}
2023-01-29 15:27:11 +08:00
}
2024-02-03 19:24:11 +00:00
/* Decode PEM and import into CryptoKeyPair */
const privateKeyDec = x509 . PemConverter . decodeFirst ( keyPem . toString ( ) ) ;
const privateKey = await crypto . webcrypto . subtle . importKey ( 'pkcs8' , privateKeyDec , sigalg , true , [ 'sign' ] ) ;
const publicKey = await crypto . webcrypto . subtle . importKey ( 'jwk' , jwk , sigalg , true , [ 'verify' ] ) ;
return [ { privateKey , publicKey } , sigalg ] ;
2023-01-29 15:27:11 +08:00
}
/**
* Split chain of PEM encoded objects from string into array
*
* @param {buffer|string} chainPem PEM encoded object chain
2024-02-03 19:24:11 +00:00
* @returns {string[]} Array of PEM objects including headers
2023-01-29 15:27:11 +08:00
*/
2024-11-12 12:15:06 +08:00
export function splitPemChain ( chainPem ) {
2023-01-29 15:27:11 +08:00
if ( Buffer . isBuffer ( chainPem ) ) {
chainPem = chainPem . toString ( ) ;
}
2024-02-03 19:24:11 +00:00
/* Decode into array and re-encode */
return x509 . PemConverter . decodeWithHeaders ( chainPem )
. map ( ( params ) = > x509 . PemConverter . encode ( [ params ] ) ) ;
2023-01-29 15:27:11 +08:00
}
/**
* 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
*/
2024-11-12 12:15:06 +08:00
export const getPemBodyAsB64u = ( pem ) = > {
2023-01-29 15:27:11 +08:00
const chain = splitPemChain ( pem ) ;
if ( ! chain . length ) {
throw new Error ( 'Unable to parse PEM body from string' ) ;
}
2024-02-03 19:24:11 +00:00
/* Select first object, extract body and convert to b64u */
const dec = x509 . PemConverter . decodeFirst ( chain [ 0 ] ) ;
return Buffer . from ( dec ) . toString ( 'base64url' ) ;
2023-01-29 15:27:11 +08:00
} ;
/**
* Parse domains from a certificate or CSR
*
* @private
2024-02-03 19:24:11 +00:00
* @param {object} input x509.Certificate or x509.Pkcs10CertificateRequest
2023-01-29 15:27:11 +08:00
* @returns {object} {commonName, altNames}
*/
2024-02-03 19:24:11 +00:00
function parseDomains ( input ) {
const commonName = input . subjectName . getField ( 'CN' ) . pop ( ) || null ;
const altNamesRaw = input . getExtension ( subjectAltNameOID ) ;
2023-01-29 15:27:11 +08:00
let altNames = [ ] ;
2024-02-03 19:24:11 +00:00
if ( altNamesRaw ) {
const altNamesExt = new x509 . SubjectAlternativeNameExtension ( altNamesRaw . rawData ) ;
altNames = altNames . concat ( altNamesExt . names . items . map ( ( i ) = > i . value ) ) ;
2023-01-29 15:27:11 +08:00
}
return {
commonName ,
2024-05-22 19:24:07 +00:00
altNames ,
2023-01-29 15:27:11 +08:00
} ;
}
/**
* 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(', ')}`);
* ```
*/
2024-11-12 12:15:06 +08:00
export const readCsrDomains = ( csrPem ) = > {
2023-01-29 15:27:11 +08:00
if ( Buffer . isBuffer ( csrPem ) ) {
csrPem = csrPem . toString ( ) ;
}
2024-02-03 19:24:11 +00:00
const dec = x509 . PemConverter . decodeFirst ( csrPem ) ;
const csr = new x509 . Pkcs10CertificateRequest ( dec ) ;
return parseDomains ( csr ) ;
2023-01-29 15:27:11 +08:00
} ;
/**
* 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(', ')}`);
* ```
*/
2024-11-12 12:15:06 +08:00
export const readCertificateInfo = ( certPem ) = > {
2024-02-03 19:24:11 +00:00
if ( Buffer . isBuffer ( certPem ) ) {
certPem = certPem . toString ( ) ;
}
const dec = x509 . PemConverter . decodeFirst ( certPem ) ;
const cert = new x509 . X509Certificate ( dec ) ;
2023-01-29 15:27:11 +08:00
return {
issuer : {
2024-05-22 19:24:07 +00:00
commonName : cert.issuerName.getField ( 'CN' ) . pop ( ) || null ,
2023-01-29 15:27:11 +08:00
} ,
2024-02-03 19:24:11 +00:00
domains : parseDomains ( cert ) ,
notBefore : cert.notBefore ,
2024-05-22 19:24:07 +00:00
notAfter : cert.notAfter ,
2023-01-29 15:27:11 +08:00
} ;
} ;
/**
2024-02-03 19:24:11 +00:00
* Determine ASN.1 character string type for CSR subject field name
2023-01-29 15:27:11 +08:00
*
2024-02-03 19:24:11 +00:00
* https://datatracker.ietf.org/doc/html/rfc5280
* https://github.com/PeculiarVentures/x509/blob/ecf78224fd594abbc2fa83c41565d79874f88e00/src/name.ts#L65-L71
2023-01-29 15:27:11 +08:00
*
* @private
2024-02-03 19:24:11 +00:00
* @param {string} field CSR subject field name
* @returns {string} ASN.1 character string type
2023-01-29 15:27:11 +08:00
*/
function getCsrAsn1CharStringType ( field ) {
switch ( field ) {
case 'C' :
2024-02-03 19:24:11 +00:00
return 'printableString' ;
2023-01-29 15:27:11 +08:00
case 'E' :
2024-02-03 19:24:11 +00:00
return 'ia5String' ;
2023-01-29 15:27:11 +08:00
default :
2024-02-03 19:24:11 +00:00
return 'utf8String' ;
2023-01-29 15:27:11 +08:00
}
}
/**
* Create array of subject fields for a Certificate Signing Request
*
2024-02-03 19:24:11 +00:00
* https://github.com/PeculiarVentures/x509/blob/ecf78224fd594abbc2fa83c41565d79874f88e00/src/name.ts#L65-L71
*
2023-01-29 15:27:11 +08:00
* @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 ) ;
2024-02-03 19:24:11 +00:00
result . push ( { [ type ] : [ { [ ds ] : value } ] } ) ;
2023-01-29 15:27:11 +08:00
}
return result ;
} , [ ] ) ;
}
/**
2024-02-03 19:24:11 +00:00
* Create x509 subject alternate name extension
2023-01-29 15:27:11 +08:00
*
2024-02-03 19:24:11 +00:00
* https://github.com/PeculiarVentures/x509/blob/ecf78224fd594abbc2fa83c41565d79874f88e00/src/extensions/subject_alt_name.ts
2023-01-29 15:27:11 +08:00
*
* @private
* @param {string[]} altNames Array of alt names
2024-02-03 19:24:11 +00:00
* @returns {x509.SubjectAlternativeNameExtension} Subject alternate name extension
2023-01-29 15:27:11 +08:00
*/
2024-02-03 19:24:11 +00:00
function createSubjectAltNameExtension ( altNames ) {
return new x509 . SubjectAlternativeNameExtension ( altNames . map ( ( value ) = > {
const type = net . isIP ( value ) ? 'ip' : 'dns' ;
return { type , value } ;
} ) ) ;
2023-01-29 15:27:11 +08:00
}
/**
* 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
2024-02-03 19:24:11 +00:00
* @param {string[]} [data.altNames] SAN (Subject Alternative Names), default: `[]`
2023-01-29 15:27:11 +08:00
* @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
2024-02-03 19:24:11 +00:00
* @param {buffer|string} [keyPem] PEM encoded CSR private key
2023-01-29 15:27:11 +08:00
* @returns {Promise<buffer[]>} [privateKey, certificateSigningRequest]
*
* @example Create a Certificate Signing Request
* ```js
* const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
2024-05-23 19:24:12 +00:00
* altNames: ['test.example.com'],
2023-01-29 15:27:11 +08:00
* });
* ```
*
* @example Certificate Signing Request with both common and alternative names
2024-05-23 19:24:12 +00:00
* > *Warning*: Certificate subject common name has been [deprecated](https://letsencrypt.org/docs/glossary/#def-CN) and its use is [discouraged](https://cabforum.org/uploads/BRv1.2.3.pdf).
2023-01-29 15:27:11 +08:00
* ```js
* const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
* keySize: 4096,
* commonName: 'test.example.com',
2024-05-23 19:24:12 +00:00
* altNames: ['foo.example.com', 'bar.example.com'],
2023-01-29 15:27:11 +08:00
* });
* ```
*
* @example Certificate Signing Request with additional information
* ```js
* const [certificateKey, certificateRequest] = await acme.crypto.createCsr({
2024-05-23 19:24:12 +00:00
* altNames: ['test.example.com'],
2023-01-29 15:27:11 +08:00
* country: 'US',
* state: 'California',
* locality: 'Los Angeles',
* organization: 'The Company Inc.',
* organizationUnit: 'IT Department',
2024-05-23 19:24:12 +00:00
* emailAddress: 'contact@example.com',
2023-01-29 15:27:11 +08:00
* });
* ```
*
* @example Certificate Signing Request with ECDSA private key
* ```js
* const certificateKey = await acme.crypto.createPrivateEcdsaKey();
*
* const [, certificateRequest] = await acme.crypto.createCsr({
2024-05-23 19:24:12 +00:00
* altNames: ['test.example.com'],
2023-01-29 15:27:11 +08:00
* }, certificateKey);
2024-05-23 19:24:12 +00:00
* ```
2023-01-29 15:27:11 +08:00
*/
2024-11-12 12:15:06 +08:00
export const createCsr = async ( data , keyPem = null ) = > {
2023-01-29 15:27:11 +08:00
if ( ! keyPem ) {
keyPem = await createPrivateRsaKey ( data . keySize ) ;
}
else if ( ! Buffer . isBuffer ( keyPem ) ) {
keyPem = Buffer . from ( keyPem ) ;
}
if ( typeof data . altNames === 'undefined' ) {
data . altNames = [ ] ;
}
/* 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 ) ;
}
2024-02-03 19:24:11 +00:00
/* CryptoKeyPair and signing algorithm from private key */
const [ keys , signingAlgorithm ] = await getWebCryptoKeyPair ( keyPem ) ;
2023-01-29 15:27:11 +08:00
2024-02-03 19:24:11 +00:00
const extensions = [
/* https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3 */
new x509 . KeyUsagesExtension ( x509 . KeyUsageFlags . digitalSignature | x509 . KeyUsageFlags . keyEncipherment ) , // eslint-disable-line no-bitwise
/* https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 */
2024-05-22 19:24:07 +00:00
createSubjectAltNameExtension ( data . altNames ) ,
2024-02-03 19:24:11 +00:00
] ;
2023-01-29 15:27:11 +08:00
/* Create CSR */
2024-02-03 19:24:11 +00:00
const csr = await x509 . Pkcs10CertificateRequestGenerator . create ( {
keys ,
extensions ,
signingAlgorithm ,
name : createCsrSubject ( {
CN : data.commonName ,
C : data.country ,
ST : data.state ,
L : data.locality ,
O : data.organization ,
OU : data.organizationUnit ,
2024-05-22 19:24:07 +00:00
E : data.emailAddress ,
} ) ,
2023-01-29 15:27:11 +08:00
} ) ;
2024-01-30 19:24:20 +00:00
/* Done */
2024-02-03 19:24:11 +00:00
const pem = csr . toString ( 'pem' ) ;
2024-01-30 19:24:20 +00:00
return [ keyPem , Buffer . from ( pem ) ] ;
2024-02-03 19:24:11 +00:00
} ;
2024-01-30 19:24:20 +00:00
/**
* Create a self-signed ALPN certificate for TLS-ALPN-01 challenges
*
2024-02-03 19:24:11 +00:00
* https://datatracker.ietf.org/doc/html/rfc8737
2024-01-30 19:24:20 +00:00
*
* @param {object} authz Identifier authorization
* @param {string} keyAuthorization Challenge key authorization
2024-02-03 19:24:11 +00:00
* @param {buffer|string} [keyPem] PEM encoded CSR private key
2024-01-30 19:24:20 +00:00
* @returns {Promise<buffer[]>} [privateKey, certificate]
*
* @example Create a ALPN certificate
* ```js
* const [alpnKey, alpnCertificate] = await acme.crypto.createAlpnCertificate(authz, keyAuthorization);
* ```
*
* @example Create a ALPN certificate with ECDSA private key
* ```js
* const alpnKey = await acme.crypto.createPrivateEcdsaKey();
* const [, alpnCertificate] = await acme.crypto.createAlpnCertificate(authz, keyAuthorization, alpnKey);
2024-05-23 19:24:12 +00:00
* ```
2024-01-30 19:24:20 +00:00
*/
2024-11-12 12:15:06 +08:00
export const createAlpnCertificate = async ( authz , keyAuthorization , keyPem = null ) = > {
2024-02-03 19:24:11 +00:00
if ( ! keyPem ) {
keyPem = await createPrivateRsaKey ( ) ;
}
else if ( ! Buffer . isBuffer ( keyPem ) ) {
keyPem = Buffer . from ( keyPem ) ;
}
2024-01-30 19:24:20 +00:00
const now = new Date ( ) ;
const commonName = authz . identifier . value ;
/* Pseudo-random serial - max 20 bytes, 11 for epoch (year 5138), 9 random */
const random = await randomInt ( 1 , 999999999 ) ;
2024-02-03 19:24:11 +00:00
const serialNumber = ` ${ Math . floor ( now . getTime ( ) / 1000 ) } ${ random } ` ;
/* CryptoKeyPair and signing algorithm from private key */
const [ keys , signingAlgorithm ] = await getWebCryptoKeyPair ( keyPem ) ;
const extensions = [
/* https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3 */
new x509 . KeyUsagesExtension ( x509 . KeyUsageFlags . keyCertSign | x509 . KeyUsageFlags . cRLSign , true ) , // eslint-disable-line no-bitwise
/* https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9 */
new x509 . BasicConstraintsExtension ( true , 2 , true ) ,
/* https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.2 */
await x509 . SubjectKeyIdentifierExtension . create ( keys . publicKey ) ,
/* https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 */
2024-05-22 19:24:07 +00:00
createSubjectAltNameExtension ( [ commonName ] ) ,
2024-02-03 19:24:11 +00:00
] ;
/* ALPN extension */
const payload = crypto . createHash ( 'sha256' ) . update ( keyAuthorization ) . digest ( 'hex' ) ;
const octstr = new asn1js . OctetString ( { valueHex : Buffer.from ( payload , 'hex' ) } ) ;
extensions . push ( new x509 . Extension ( alpnAcmeIdentifierOID , true , octstr . toBER ( ) ) ) ;
2024-01-30 19:24:20 +00:00
/* Self-signed ALPN certificate */
2024-02-03 19:24:11 +00:00
const cert = await x509 . X509CertificateGenerator . createSelfSigned ( {
keys ,
signingAlgorithm ,
extensions ,
serialNumber ,
notBefore : now ,
notAfter : now ,
name : createCsrSubject ( {
2024-05-22 19:24:07 +00:00
CN : commonName ,
} ) ,
2024-01-30 19:24:20 +00:00
} ) ;
2023-01-29 15:27:11 +08:00
/* Done */
2024-02-03 19:24:11 +00:00
const pem = cert . toString ( 'pem' ) ;
return [ keyPem , Buffer . from ( pem ) ] ;
2024-01-30 19:24:20 +00:00
} ;
/**
* Validate that a ALPN certificate contains the expected key authorization
*
* @param {buffer|string} certPem PEM encoded certificate
* @param {string} keyAuthorization Expected challenge key authorization
* @returns {boolean} True when valid
*/
2024-11-12 12:15:06 +08:00
export const isAlpnCertificateAuthorizationValid = ( certPem , keyAuthorization ) = > {
2024-02-03 19:24:11 +00:00
const expected = crypto . createHash ( 'sha256' ) . update ( keyAuthorization ) . digest ( 'hex' ) ;
2024-01-30 19:24:20 +00:00
2024-02-03 19:24:11 +00:00
/* Attempt to locate ALPN extension */
const cert = new x509 . X509Certificate ( certPem ) ;
const ext = cert . getExtension ( alpnAcmeIdentifierOID ) ;
if ( ! ext ) {
2024-01-30 19:24:20 +00:00
throw new Error ( 'Unable to locate ALPN extension within parsed certificate' ) ;
}
2024-02-03 19:24:11 +00:00
/* Decode extension value */
const parsed = asn1js . fromBER ( ext . value ) ;
const result = Buffer . from ( parsed . result . valueBlock . valueHexView ) . toString ( 'hex' ) ;
2024-01-30 19:24:20 +00:00
/* Return true if match */
2024-02-03 19:24:11 +00:00
return ( result === expected ) ;
2023-01-29 15:27:11 +08:00
} ;