@@ -128,6 +128,11 @@ let KEY_PAIR: {
128128 length : number
129129} | undefined ;
130130
131+ interface CertGenerationOptions {
132+ selfSigned ?: boolean ;
133+ expired ?: boolean ;
134+ }
135+
131136export class LocalCA {
132137 private caCert : forge . pki . Certificate ;
133138 private caKey : forge . pki . PrivateKey ;
@@ -177,128 +182,78 @@ export class LocalCA {
177182 domain = `*.${ otherParts . join ( '.' ) } ` ;
178183 }
179184
180- let cert = pki . createCertificate ( ) ;
181-
182- cert . publicKey = KEY_PAIR ! . publicKey ;
183- cert . serialNumber = generateSerialNumber ( ) ;
184-
185- cert . validity . notBefore = new Date ( ) ;
186- // Make it valid for the last 24h - helps in cases where clocks slightly disagree.
187- cert . validity . notBefore . setDate ( cert . validity . notBefore . getDate ( ) - 1 ) ;
188-
189- cert . validity . notAfter = new Date ( ) ;
190- // Valid for the next year by default.
191- cert . validity . notAfter . setFullYear ( cert . validity . notAfter . getFullYear ( ) + 1 ) ;
192-
193- cert . setSubject ( [
194- ...( domain [ 0 ] === '*'
195- ? [ ] // We skip the CN (deprecated, rarely used) for wildcards, since they can't be used here.
196- : [ { name : 'commonName' , value : domain } ]
197- ) ,
198- { name : 'countryName' , value : this . options ?. countryName ?? 'XX' } , // ISO-3166-1 alpha-2 'unknown country' code
199- { name : 'localityName' , value : this . options ?. localityName ?? 'Unknown' } ,
200- { name : 'organizationName' , value : this . options ?. organizationName ?? 'Testserver Test Cert' }
201- ] ) ;
202- cert . setIssuer ( this . caCert . subject . attributes ) ;
203-
204- const policyList = forge . asn1 . create ( forge . asn1 . Class . UNIVERSAL , forge . asn1 . Type . SEQUENCE , true , [
205- forge . asn1 . create ( forge . asn1 . Class . UNIVERSAL , forge . asn1 . Type . SEQUENCE , true , [
206- forge . asn1 . create (
207- forge . asn1 . Class . UNIVERSAL ,
208- forge . asn1 . Type . OID ,
209- false ,
210- forge . asn1 . oidToDer ( '2.5.29.32.0' ) . getBytes ( ) // Mark all as Domain Verified
211- )
212- ] )
213- ] ) ;
214-
215- cert . setExtensions ( [
216- { name : 'basicConstraints' , cA : false , critical : true } ,
217- { name : 'keyUsage' , digitalSignature : true , keyEncipherment : true , critical : true } ,
218- { name : 'extKeyUsage' , serverAuth : true , clientAuth : true } ,
219- {
220- name : 'subjectAltName' ,
221- altNames : [ {
222- type : 2 ,
223- value : domain
224- } ]
225- } ,
226- { name : 'certificatePolicies' , value : policyList } ,
227- { name : 'subjectKeyIdentifier' } ,
228- {
229- name : 'authorityKeyIdentifier' ,
230- // We have to calculate this ourselves due to
231- // https://github.com/digitalbazaar/forge/issues/462
232- keyIdentifier : (
233- this . caCert as any // generateSubjectKeyIdentifier is missing from node-forge types
234- ) . generateSubjectKeyIdentifier ( ) . getBytes ( )
235- }
236- ] ) ;
237-
238- cert . sign ( this . caKey , md . sha256 . create ( ) ) ;
239-
240- const generatedCertificate = {
241- key : pki . privateKeyToPem ( KEY_PAIR ! . privateKey ) ,
242- cert : pki . certificateToPem ( cert ) ,
243- ca : pki . certificateToPem ( this . caCert )
244- } ;
245-
246- // We cache in memory only - no need to persist these (unlike ACME etc)
247- this . certInMemoryCache [ domain ] = generatedCertificate ;
248-
249- // Make sure this gets regenerated in 24 hours, in case this server is running persistently
250- setTimeout ( ( ) => {
251- delete this . certInMemoryCache [ domain ] ;
252- } , 1000 * 60 * 60 * 24 ) . unref ( ) ;
253-
254- return generatedCertificate ;
185+ return this . generateCert ( domain , domain ) ;
255186 }
256187
257188 generateSelfSignedCertificate ( domain : string ) {
258- const cacheKey = `${ domain } :self-signed` ;
189+ return this . generateCert ( domain , `${ domain } :self-signed` , { selfSigned : true } ) ;
190+ }
191+
192+ generateExpiredCertificate ( domain : string ) {
193+ return this . generateCert ( domain , `${ domain } :expired` , { expired : true } ) ;
194+ }
259195
196+ private generateCert (
197+ domain : string ,
198+ cacheKey : string ,
199+ options : CertGenerationOptions = { }
200+ ) : LocallyGeneratedCertificate {
260201 const cachedCert = this . certInMemoryCache [ cacheKey ] ;
261202 if ( cachedCert ) return cachedCert ;
262203
263204 const cert = pki . createCertificate ( ) ;
264-
265205 cert . publicKey = KEY_PAIR ! . publicKey ;
266206 cert . serialNumber = generateSerialNumber ( ) ;
267207
268208 cert . validity . notBefore = new Date ( ) ;
269- cert . validity . notBefore . setDate ( cert . validity . notBefore . getDate ( ) - 1 ) ;
270-
271209 cert . validity . notAfter = new Date ( ) ;
272- cert . validity . notAfter . setFullYear ( cert . validity . notAfter . getFullYear ( ) + 1 ) ;
210+
211+ if ( options . expired ) {
212+ cert . validity . notBefore . setDate ( cert . validity . notBefore . getDate ( ) - 2 ) ;
213+ cert . validity . notAfter . setDate ( cert . validity . notAfter . getDate ( ) - 1 ) ;
214+ } else {
215+ cert . validity . notBefore . setDate ( cert . validity . notBefore . getDate ( ) - 1 ) ;
216+ cert . validity . notAfter . setFullYear ( cert . validity . notAfter . getFullYear ( ) + 1 ) ;
217+ }
273218
274219 const subject = [
275- { name : 'commonName' , value : domain } ,
220+ ...( domain [ 0 ] === '*'
221+ ? [ ] // We skip the CN (deprecated, rarely used) for wildcards, since they can't be used here.
222+ : [ { name : 'commonName' , value : domain } ]
223+ ) ,
276224 { name : 'countryName' , value : this . options ?. countryName ?? 'XX' } ,
277225 { name : 'localityName' , value : this . options ?. localityName ?? 'Unknown' } ,
278226 { name : 'organizationName' , value : this . options ?. organizationName ?? 'Testserver Test Cert' }
279227 ] ;
280228
281229 cert . setSubject ( subject ) ;
282- cert . setIssuer ( subject ) ; // Self-signed: issuer = subject
230+ cert . setIssuer ( options . selfSigned ? subject : this . caCert . subject . attributes ) ;
283231
284- cert . setExtensions ( [
232+ const extensions : any [ ] = [
285233 { name : 'basicConstraints' , cA : false , critical : true } ,
286234 { name : 'keyUsage' , digitalSignature : true , keyEncipherment : true , critical : true } ,
287235 { name : 'extKeyUsage' , serverAuth : true , clientAuth : true } ,
288- {
289- name : 'subjectAltName' ,
290- altNames : [ { type : 2 , value : domain } ]
291- } ,
236+ { name : 'subjectAltName' , altNames : [ { type : 2 , value : domain } ] } ,
292237 { name : 'subjectKeyIdentifier' }
293- ] ) ;
238+ ] ;
239+
240+ if ( ! options . selfSigned ) {
241+ extensions . push ( {
242+ name : 'authorityKeyIdentifier' ,
243+ // We have to calculate this ourselves due to
244+ // https://github.com/digitalbazaar/forge/issues/462
245+ keyIdentifier : ( this . caCert as any ) . generateSubjectKeyIdentifier ( ) . getBytes ( )
246+ } ) ;
247+ }
294248
295- cert . sign ( KEY_PAIR ! . privateKey , md . sha256 . create ( ) ) ; // Self-signed: sign with own key
249+ cert . setExtensions ( extensions ) ;
250+ cert . sign ( options . selfSigned ? KEY_PAIR ! . privateKey : this . caKey , md . sha256 . create ( ) ) ;
296251
297252 const certPem = pki . certificateToPem ( cert ) ;
298253 const generatedCertificate = {
299254 key : pki . privateKeyToPem ( KEY_PAIR ! . privateKey ) ,
300255 cert : certPem ,
301- ca : certPem // Self-signed: cert is its own CA
256+ ca : options . selfSigned ? certPem : pki . certificateToPem ( this . caCert )
302257 } ;
303258
304259 this . certInMemoryCache [ cacheKey ] = generatedCertificate ;
0 commit comments