@@ -24,8 +24,8 @@ import {
2424 exploreCertificateRevocationList ,
2525 generatePrivateKeyFile ,
2626 makeSHA1Thumbprint ,
27- readCertificate ,
28- readCertificateAsync ,
27+ readCertificateChain ,
28+ readCertificateChainAsync ,
2929 readCertificateRevocationList ,
3030 split_der ,
3131 toPem ,
@@ -326,16 +326,17 @@ function isSelfSigned3(certificate: Buffer): boolean {
326326 * @param chain - candidate issuer certificates to search
327327 * @returns the matching issuer certificate, or `null` if not found
328328 */
329- export function findIssuerCertificateInChain ( certificate : Certificate , chain : Certificate [ ] ) : Certificate | null {
330- if ( ! certificate ) {
329+ export function findIssuerCertificateInChain ( certificate : Certificate | Certificate [ ] , chain : Certificate [ ] ) : Certificate | null {
330+ const firstCertificate = Array . isArray ( certificate ) ? certificate [ 0 ] : certificate ;
331+ if ( ! firstCertificate ) {
331332 return null ;
332333 }
333- const certInfo = exploreCertificateCached ( certificate ) ;
334+ const certInfo = exploreCertificateCached ( firstCertificate ) ;
334335
335336 // istanbul ignore next
336337 if ( isSelfSigned2 ( certInfo ) ) {
337338 // the certificate is self signed so is it's own issuer.
338- return certificate ;
339+ return firstCertificate ;
339340 }
340341 const wantedIssuerKey = certInfo . tbsCertificate . extensions ?. authorityKeyIdentifier ?. keyIdentifier ;
341342
@@ -650,7 +651,9 @@ export class CertificateManager extends EventEmitter {
650651 * @returns `"Good"` if trusted, `"BadCertificateUntrusted"` if rejected/unknown,
651652 * or `"BadCertificateInvalid"` if the certificate cannot be parsed.
652653 */
653- public async isCertificateTrusted ( certificate : Certificate ) : Promise < string > {
654+ public async isCertificateTrusted (
655+ certificate : Certificate
656+ ) : Promise < "Good" | "BadCertificateUntrusted" | "BadCertificateInvalid" > {
654657 const fingerprint = makeFingerprint ( certificate ) as Thumbprint ;
655658
656659 if ( this . #thumbs. trusted . has ( fingerprint ) ) {
@@ -675,7 +678,7 @@ export class CertificateManager extends EventEmitter {
675678 return "BadCertificateUntrusted" ;
676679 }
677680 async #innerVerifyCertificateAsync(
678- certificate : Certificate ,
681+ certificateOrChain : Certificate | Certificate [ ] ,
679682 _isIssuer : boolean ,
680683 level : number ,
681684 options : VerifyCertificateOptions
@@ -684,7 +687,7 @@ export class CertificateManager extends EventEmitter {
684687 // maximum level of certificate in chain reached !
685688 return VerificationStatus . BadSecurityChecksFailed ;
686689 }
687- const chain = split_der ( certificate ) ;
690+ const chain = Array . isArray ( certificateOrChain ) ? certificateOrChain : [ certificateOrChain ] ;
688691 debugLog ( "NB CERTIFICATE IN CHAIN = " , chain . length ) ;
689692 const info = exploreCertificateCached ( chain [ 0 ] ) ;
690693
@@ -731,7 +734,7 @@ export class CertificateManager extends EventEmitter {
731734 return VerificationStatus . BadCertificateIssuerRevocationUnknown ;
732735 }
733736 if ( issuerStatus === VerificationStatus . BadCertificateTimeInvalid ) {
734- if ( ! options || ! options . acceptOutDatedIssuerCertificate ) {
737+ if ( ! options ? .acceptOutDatedIssuerCertificate ) {
735738 // the issuer must have valid dates ....
736739 return VerificationStatus . BadCertificateIssuerTimeInvalid ;
737740 }
@@ -746,15 +749,15 @@ export class CertificateManager extends EventEmitter {
746749 return VerificationStatus . BadSecurityChecksFailed ;
747750 }
748751 // verify that certificate was signed by issuer
749- const isCertificateSignatureOK = verifyCertificateSignature ( certificate , issuerCertificate ) ;
752+ const isCertificateSignatureOK = verifyCertificateSignature ( chain [ 0 ] , issuerCertificate ) ;
750753 if ( ! isCertificateSignatureOK ) {
751754 debugLog ( " the certificate was not signed by the issuer as it claim to be ! Danger" ) ;
752755 return VerificationStatus . BadSecurityChecksFailed ;
753756 }
754757 hasValidIssuer = true ;
755758
756759 // let detected if our certificate is in the revocation list
757- let revokedStatus = await this . isCertificateRevoked ( certificate ) ;
760+ let revokedStatus = await this . isCertificateRevoked ( certificateOrChain ) ;
758761 if ( revokedStatus === VerificationStatus . BadCertificateRevocationUnknown ) {
759762 if ( options ?. ignoreMissingRevocationList ) {
760763 // continue as if the certificate was not revoked
@@ -781,17 +784,17 @@ export class CertificateManager extends EventEmitter {
781784 }
782785 } else {
783786 // verify that certificate was signed by issuer (self in this case)
784- const isCertificateSignatureOK = verifyCertificateSignature ( certificate , certificate ) ;
787+ const isCertificateSignatureOK = verifyCertificateSignature ( chain [ 0 ] , chain [ 0 ] ) ;
785788 if ( ! isCertificateSignatureOK ) {
786789 debugLog ( "Self-signed Certificate signature is not valid" ) ;
787790 return VerificationStatus . BadSecurityChecksFailed ;
788791 }
789- const revokedStatus = await this . isCertificateRevoked ( certificate ) ;
792+ const revokedStatus = await this . isCertificateRevoked ( certificateOrChain ) ;
790793 debugLog ( "revokedStatus of self signed certificate:" , revokedStatus ) ;
791794 }
792795 }
793796
794- const status = await this . #checkRejectedOrTrusted( certificate ) ;
797+ const status = await this . #checkRejectedOrTrusted( certificateOrChain ) ;
795798 if ( status === "rejected" ) {
796799 if ( ! ( options . acceptCertificateWithValidIssuerChain && hasValidIssuer && hasTrustedIssuer ) ) {
797800 return VerificationStatus . BadCertificateUntrusted ;
@@ -802,7 +805,7 @@ export class CertificateManager extends EventEmitter {
802805
803806 // Has SoftwareCertificate passed its issue date and has it not expired ?
804807 // check dates
805- const certificateInfo = exploreCertificateInfo ( certificate ) ;
808+ const certificateInfo = exploreCertificateInfo ( chain [ 0 ] ) ;
806809 const now = new Date ( ) ;
807810
808811 let isTimeInvalid = false ;
@@ -861,9 +864,26 @@ export class CertificateManager extends EventEmitter {
861864 * @returns the verification status code
862865 */
863866 protected async verifyCertificateAsync (
864- certificate : Certificate ,
867+ certificate : Certificate | Certificate [ ] ,
865868 options : VerifyCertificateOptions
866869 ) : Promise < VerificationStatus > {
870+ // When the input is a single buffer, validate that every
871+ // DER element it contains is a valid certificate. A buffer
872+ // with trailing non-certificate data (e.g. a CRL appended
873+ // after a certificate) must be rejected early.
874+ if ( ! Array . isArray ( certificate ) ) {
875+ try {
876+ const derElements = split_der ( certificate ) ;
877+ for ( const element of derElements ) {
878+ // exploreCertificateInfo will throw if the DER
879+ // element is not a valid X.509 certificate
880+ // (e.g. it is a CRL or other ASN.1 structure).
881+ exploreCertificateInfo ( element ) ;
882+ }
883+ } catch ( _err ) {
884+ return VerificationStatus . BadCertificateInvalid ;
885+ }
886+ }
867887 const status1 = await this . #innerVerifyCertificateAsync( certificate , false , 0 , options ) ;
868888 return status1 ;
869889 }
@@ -878,7 +898,10 @@ export class CertificateManager extends EventEmitter {
878898 * @param options - optional flags to relax validation rules
879899 * @returns the verification status code
880900 */
881- public async verifyCertificate ( certificate : Certificate , options ?: VerifyCertificateOptions ) : Promise < VerificationStatus > {
901+ public async verifyCertificate (
902+ certificate : Certificate | Certificate [ ] ,
903+ options ?: VerifyCertificateOptions
904+ ) : Promise < VerificationStatus > {
882905 // Is the signature on the SoftwareCertificate valid .?
883906 if ( ! certificate ) {
884907 // missing certificate
@@ -1141,6 +1164,20 @@ export class CertificateManager extends EventEmitter {
11411164 return VerificationStatus . Good ;
11421165 }
11431166
1167+ /**
1168+ * Add multiple CA (issuer) certificates to the issuers store.
1169+ * @param certificates - the DER-encoded CA certificates
1170+ * @param validate - if `true`, verify each certificate before adding
1171+ * @param addInTrustList - if `true`, also add each certificate to the trusted store
1172+ * @returns `VerificationStatus.Good` on success
1173+ */
1174+ public async addIssuers ( certificates : Certificate [ ] , validate = false , addInTrustList = false ) : Promise < VerificationStatus > {
1175+ for ( const certificate of certificates ) {
1176+ await this . addIssuer ( certificate , validate , addInTrustList ) ;
1177+ }
1178+ return VerificationStatus . Good ;
1179+ }
1180+
11441181 /**
11451182 * Add a CRL to the certificate manager.
11461183 * @param crl - the CRL to add
@@ -1328,8 +1365,8 @@ export class CertificateManager extends EventEmitter {
13281365 * @returns `VerificationStatus.Good` on success, or an error
13291366 * status indicating why the certificate was rejected.
13301367 */
1331- public async addTrustedCertificateFromChain ( certificateChain : Certificate ) : Promise < VerificationStatus > {
1332- const certificates = split_der ( certificateChain ) ;
1368+ public async addTrustedCertificateFromChain ( certificateChain : Certificate | Certificate [ ] ) : Promise < VerificationStatus > {
1369+ const certificates = Array . isArray ( certificateChain ) ? certificateChain : split_der ( certificateChain ) ;
13331370 const leafCertificate = certificates [ 0 ] ;
13341371
13351372 // Structural validation — can we parse it?
@@ -1412,12 +1449,13 @@ export class CertificateManager extends EventEmitter {
14121449 * if found. If multiple matching certificates are found, a warning is logged to the console.
14131450 *
14141451 */
1415- public async findIssuerCertificate ( certificate : Certificate ) : Promise < Certificate | null > {
1416- const certInfo = exploreCertificateCached ( certificate ) ;
1452+ public async findIssuerCertificate ( certificate : Certificate | Certificate [ ] ) : Promise < Certificate | null > {
1453+ const firstCertificate = Array . isArray ( certificate ) ? certificate [ 0 ] : certificate ;
1454+ const certInfo = exploreCertificateCached ( firstCertificate ) ;
14171455
14181456 if ( isSelfSigned2 ( certInfo ) ) {
14191457 // the certificate is self signed so is it's own issuer.
1420- return certificate ;
1458+ return firstCertificate ;
14211459 }
14221460
14231461 const wantedIssuerKey = certInfo . tbsCertificate . extensions ?. authorityKeyIdentifier ?. keyIdentifier ;
@@ -1459,8 +1497,9 @@ export class CertificateManager extends EventEmitter {
14591497 * @internal
14601498 * @private
14611499 */
1462- async #checkRejectedOrTrusted( certificate : Buffer ) : Promise < CertificateStatus > {
1463- const fingerprint = makeFingerprint ( certificate ) ;
1500+ async #checkRejectedOrTrusted( certificate : Certificate | Certificate [ ] ) : Promise < CertificateStatus > {
1501+ const firstCertificate = Array . isArray ( certificate ) ? certificate [ 0 ] : certificate ;
1502+ const fingerprint = makeFingerprint ( firstCertificate ) ;
14641503
14651504 debugLog ( "#checkRejectedOrTrusted fingerprint " , short ( fingerprint ) ) ;
14661505
@@ -1538,15 +1577,16 @@ export class CertificateManager extends EventEmitter {
15381577 * found.
15391578 */
15401579 public async isCertificateRevoked (
1541- certificate : Certificate ,
1580+ certificate : Certificate | Certificate [ ] ,
15421581 issuerCertificate ?: Certificate | null
15431582 ) : Promise < VerificationStatus > {
1544- if ( isSelfSigned3 ( certificate ) ) {
1583+ const firstCertificate = Array . isArray ( certificate ) ? certificate [ 0 ] : certificate ;
1584+ if ( isSelfSigned3 ( firstCertificate ) ) {
15451585 return VerificationStatus . Good ;
15461586 }
15471587
15481588 if ( ! issuerCertificate ) {
1549- issuerCertificate = await this . findIssuerCertificate ( certificate ) ;
1589+ issuerCertificate = await this . findIssuerCertificate ( firstCertificate ) ;
15501590 }
15511591 if ( ! issuerCertificate ) {
15521592 return VerificationStatus . BadCertificateChainIncomplete ;
@@ -1556,7 +1596,7 @@ export class CertificateManager extends EventEmitter {
15561596 if ( ! crls ) {
15571597 return VerificationStatus . BadCertificateRevocationUnknown ;
15581598 }
1559- const certInfo = exploreCertificateCached ( certificate ) ;
1599+ const certInfo = exploreCertificateCached ( firstCertificate ) ;
15601600 const serialNumber =
15611601 certInfo . tbsCertificate . serialNumber || certInfo . tbsCertificate . extensions ?. authorityKeyIdentifier ?. serial || "" ;
15621602
@@ -1750,7 +1790,7 @@ export class CertificateManager extends EventEmitter {
17501790 try {
17511791 const stat = await fs . promises . stat ( filename ) ;
17521792 if ( ! stat . isFile ( ) ) continue ;
1753- const certificate = await readCertificateAsync ( filename ) ;
1793+ const certificate = ( await readCertificateChainAsync ( filename ) ) [ 0 ] ;
17541794 const info = exploreCertificateCached ( certificate ) ;
17551795 const fingerprint = makeFingerprint ( certificate ) ;
17561796 index . set ( fingerprint , { certificate, filename, info } ) ;
@@ -1851,7 +1891,7 @@ export class CertificateManager extends EventEmitter {
18511891 w . on ( "add" , ( filename : string ) => {
18521892 debugLog ( chalk . cyan ( `add in folder ${ folder } ` ) , filename ) ;
18531893 try {
1854- const certificate = readCertificate ( filename ) ;
1894+ const certificate = readCertificateChain ( filename ) [ 0 ] ;
18551895 const info = exploreCertificateCached ( certificate ) ;
18561896 const fingerprint = makeFingerprint ( certificate ) ;
18571897
@@ -1876,7 +1916,7 @@ export class CertificateManager extends EventEmitter {
18761916 w . on ( "change" , ( changedPath : string ) => {
18771917 debugLog ( chalk . cyan ( `change in folder ${ folder } ` ) , changedPath ) ;
18781918 try {
1879- const certificate = readCertificate ( changedPath ) ;
1919+ const certificate = readCertificateChain ( changedPath ) [ 0 ] ;
18801920 const newFingerprint = makeFingerprint ( certificate ) ;
18811921 const oldHash = this . #filenameToHash. get ( changedPath ) ;
18821922 if ( oldHash && oldHash !== newFingerprint ) {
0 commit comments