@@ -13,7 +13,6 @@ const { encrypt, decrypt } = require('./encrypt');
1313const  {  SettingsHandler }  =  require ( './settings-handler' ) ; 
1414const  {  Resolver }  =  require ( 'dns' ) . promises ; 
1515const  resolver  =  new  Resolver ( ) ; 
16- const  punycode  =  require ( 'punycode.js' ) ; 
1716const  {  getCertificate }  =  require ( './acme/certs' ) ; 
1817
1918const  {  promisify }  =  require ( 'util' ) ; 
@@ -22,8 +21,6 @@ const generateKeyPair = promisify(crypto.generateKeyPair);
2221const  CERT_RENEW_TTL  =  30  *  24  *  3600  *  1000 ; 
2322const  CERT_RENEW_DELAY  =  24  *  3600  *  100 ; 
2423
25- const  CAA_DOMAIN  =  'letsencrypt.org' ; 
26- 
2724class  CertHandler  { 
2825    constructor ( options )  { 
2926        options  =  options  ||  { } ; 
@@ -35,6 +32,8 @@ class CertHandler {
3532        this . database  =  options . database ; 
3633        this . redis  =  options . redis ; 
3734
35+         this . users  =  options . users ; 
36+ 
3837        this . acmeConfig  =  options . acmeConfig ; 
3938
4039        this . ctxCache  =  new  Map ( ) ; 
@@ -450,6 +449,7 @@ class CertHandler {
450449            description : certData . description , 
451450            fingerprint : certData . fingerprint  ||  certData . fp , 
452451            expires : certData . expires , 
452+             autogenerated : certData . autogenerated , 
453453            altNames : certData . altNames , 
454454            acme : ! ! certData . acme , 
455455            hasCert : ( ! ! certData . privateKey  &&  certData . cert )  ||  false , 
@@ -632,35 +632,50 @@ class CertHandler {
632632        return  context ; 
633633    } 
634634
635-     normalizeDomain ( domain )  { 
636-         domain  =  ( domain  ||  '' ) . toString ( ) . toLowerCase ( ) . trim ( ) ; 
637-         try  { 
638-             if  ( / [ \x80 - \uFFFF ] / . test ( domain ) )  { 
639-                 domain  =  punycode . toASCII ( domain ) ; 
640-             } 
641-         }  catch  ( E )  { 
642-             // ignore 
643-         } 
644- 
645-         return  domain ; 
646-     } 
647- 
648635    async  precheckAcmeCertificate ( domain )  { 
649-         let  typePrefix  =  domain . split ( '.' ) . shift ( ) . toLowerCase ( ) . trim ( ) ; 
650- 
651-         let  subdomainTargets  =  ( ( await  this . settingsHandler . get ( 'const:acme:subdomains' ) )  ||  '' ) 
652-             . toString ( ) 
653-             . split ( ',' ) 
654-             . map ( entry  =>  entry . trim ( ) ) 
655-             . filter ( entry  =>  entry ) ; 
636+         const  dotPos  =  domain . indexOf ( '.' ) ; 
637+         if  ( dotPos  <  0 )  { 
638+             // not a FQDN 
639+             return  false ; 
640+         } 
641+         const  subdomain  =  domain . substring ( 0 ,  dotPos ) . toLowerCase ( ) . trim ( ) ; 
642+         const  maindomain  =  domain 
643+             . substring ( dotPos  +  1 ) 
644+             . toLowerCase ( ) 
645+             . trim ( ) ; 
656646
657-         if  ( ! subdomainTargets . includes ( typePrefix ) )  { 
647+         let  subdomainTargets  =  [ ] . concat ( this . acmeConfig . autogenerate ?. cnameMapping ?. [ subdomain ]  ||  [ ] ) ; 
648+         if  ( ! subdomainTargets . length )  { 
658649            // unsupported subdomain 
659650            log . verbose ( 'Certs' ,  'Skip ACME. reason="unsupported subdomain" action=precheck domain=%s' ,  domain ) ; 
660651            return  false ; 
661652        } 
662653
654+         // CNAME check 
655+         let  resolved ; 
656+         try  { 
657+             resolved  =  await  resolver . resolveCname ( domain ) ; 
658+         }  catch  ( err )  { 
659+             log . error ( 'Certs' ,  'DNS CNAME query failed. action=precheck domain=%s error=%s' ,  domain ,  err . message ) ; 
660+             return  false ; 
661+         } 
662+ 
663+         if  ( ! resolved  ||  ! resolved . length )  { 
664+             log . verbose ( 'Certs' ,  'Skip ACME. reason="empty CNAME result" action=precheck domain=%s' ,  domain ) ; 
665+             return  false ; 
666+         } 
667+ 
668+         for  ( let  row  of  resolved )  { 
669+             if  ( ! subdomainTargets . includes ( row ) )  { 
670+                 log . verbose ( 'Certs' ,  'Skip ACME. reason="unknown CNAME target" action=precheck domain=%s target=%s' ,  domain ,  row ) ; 
671+                 return  false ; 
672+             } 
673+         } 
674+ 
663675        // CAA check 
676+ 
677+         const  caaDomains  =  this . acmeConfig . caaDomains ?. map ( domain  =>  tools . normalizeDomain ( domain ) ) . filter ( d  =>  d ) ; 
678+ 
664679        let  parts  =  domain . split ( '.' ) ; 
665680        for  ( let  i  =  0 ;  i  <  parts . length  -  1 ;  i ++ )  { 
666681            let  subdomain  =  parts . slice ( i ) . join ( '.' ) ; 
@@ -671,59 +686,53 @@ class CertHandler {
671686                // assume not found 
672687            } 
673688
674-             if  ( caaRes ?. length  &&  ! caaRes . some ( r  =>  ( r ?. issue   ||   '' ) . trim ( ) . toLowerCase ( )   ===   CAA_DOMAIN ) )  { 
689+             if  ( caaRes ?. length  &&  ! caaRes . some ( r  =>  caaDomains . includes ( tools . normalizeDomain ( r ?. issue ) ) ) )  { 
675690                log . verbose ( 'Certs' ,  'Skip ACME. reason="LE not listed in the CAA record". action=precheck domain=%s subdomain=%s' ,  domain ,  subdomain ) ; 
676691                return  false ; 
677692            }  else  if  ( caaRes ?. length )  { 
678-                 log . verbose ( 'Certs' ,  'CAA record found. action=precheck domain=%s subdomain=%s' ,  domain ,  subdomain ) ; 
693+                 log . verbose ( 'Certs' ,  'CAA record found. action=precheck domain=%s subdomain=%s caa=%s ' ,  domain ,  subdomain ,   caaRes . join ( ',' ) ) ; 
679694                break ; 
680695            } 
681696        } 
682697
683-         // check if the domain points to correct cname 
684-         let  cnameTargets  =  ( ( await  this . settingsHandler . get ( 'const:acme:cname' ) )  ||  '' ) 
685-             . toString ( ) 
686-             . split ( ',' ) 
687-             . map ( entry  =>  entry . trim ( ) ) 
688-             . filter ( entry  =>  entry ) ; 
689- 
690-         if  ( ! cnameTargets )  { 
691-             log . verbose ( 'Certs' ,  'Skip ACME. reason="no cname targets" action=precheck domain=%s' ,  domain ) ; 
692-             return  false ; 
693-         } 
694- 
695-         let  resolved ; 
696-         try  { 
697-             resolved  =  await  resolver . resolveCname ( domain ) ; 
698-         }  catch  ( err )  { 
699-             log . error ( 'Certs' ,  'DNS CNAME query failed. action=precheck domain=%s error=%s' ,  domain ,  err . message ) ; 
700-             return  false ; 
701-         } 
698+         // Address check 
699+         const  addressMatchRegex  =  tools . escapeRegexStr ( `@${ maindomain }  ` ) ; 
700+         const  addressData  =  await  this . users . collection ( 'addresses' ) . findOne ( { 
701+             addrview : { 
702+                 $regex : `${ addressMatchRegex }  $` 
703+             } 
704+         } ) ; 
702705
703-         if  ( ! resolved   ||   ! resolved . length )  { 
704-             log . verbose ( 'Certs' ,  'Skip ACME. reason="empty CNAME result"  action=precheck domain=%s' ,  domain ) ; 
706+         if  ( ! addressData )  { 
707+             log . verbose ( 'Certs' ,  'Skip ACME. reason="No addresses found for the domain".  action=precheck domain=%s subdomain=%s ' ,  domain ,   subdomain ) ; 
705708            return  false ; 
706709        } 
707710
708-         for  ( let  row  of  resolved )  { 
709-             if  ( ! cnameTargets . includes ( row ) )  { 
710-                 log . verbose ( 'Certs' ,  'Skip ACME. reason="unknown CNAME target" action=precheck domain=%s target=%s' ,  domain ,  row ) ; 
711-                 return  false ; 
712-             } 
713-         } 
714- 
715711        return  true ; 
716712    } 
717713
718714    async  autogenerateAcmeCertificate ( servername )  { 
719-         let  domain  =  this . normalizeDomain ( servername ) ; 
715+         let  domain  =  tools . normalizeDomain ( servername ) ; 
716+ 
717+         if  ( ! this . acmeConfig . autogenerate ?. enabled )  { 
718+             // can not create autogenerated TLS certificates 
719+             log . verbose ( 'Certs' ,  'Skip ACME. reason="Certificate autogeneration not enabled" action=precheck domain=%s' ,  domain ) ; 
720+             return  false ; 
721+         } 
722+ 
720723        let  valid  =  await  this . precheckAcmeCertificate ( domain ) ; 
721724        if  ( ! valid )  { 
722725            return  false ; 
723726        } 
724727
725728        log . verbose ( 'Certs' ,  'ACME precheck passed. action=precheck domain=%s' ,  domain ) ; 
726729
730+         this . loggelf ( { 
731+             short_message : ` Autogenerating TLS certificate for ${ domain }  ` , 
732+             _sni_servername : domain , 
733+             _cert_action : 'sni_autogenerate' 
734+         } ) ; 
735+ 
727736        // add row to db 
728737        let  certInsertResult  =  await  this . set ( { 
729738            servername, 
0 commit comments