@@ -23,14 +23,28 @@ import (
2323var (
2424 mozillaPoolOnce sync.Once
2525 mozillaPool * x509.CertPool
26- mozillaPoolErr error
26+ errMozillaPool error
2727 mozillaSubjectsOnce sync.Once
2828 mozillaSubjects map [string ]bool
2929 mozillaRootKeysOnce sync.Once
3030 mozillaRootKeys map [string ][]byte // RawSubject → marshaled PKIX public key
3131
3232 // ErrChainVerificationFailed indicates that certificate path validation failed.
33- ErrChainVerificationFailed = errors .New ("chain verification failed" )
33+ ErrChainVerificationFailed = errors .New ("chain verification failed" )
34+ errMozillaRootParse = errors .New ("parsing embedded Mozilla root certificates" )
35+ errAIAAddressBlocked = errors .New ("blocked address for AIA fetch" )
36+ errAIAPrivateAddress = errors .New ("blocked private address for AIA fetch" )
37+ errAIAUnsupportedScheme = errors .New ("unsupported scheme" )
38+ errAIAMissingHostname = errors .New ("missing hostname in URL" )
39+ errAIAResolveNoIPs = errors .New ("no IP addresses returned" )
40+ errFetchLeafHTTPSRequired = errors .New ("invalid URL scheme" )
41+ errFetchLeafMissingHostname = errors .New ("fetch leaf URL is missing hostname" )
42+ errFetchLeafNotTLS = errors .New ("TLS dial did not return TLS connection" )
43+ errFetchLeafNoCerts = errors .New ("no certificates returned by TLS server" )
44+ errAIAFetchRedirects = errors .New ("AIA redirect limit exceeded" )
45+ errAIAHTTPStatus = errors .New ("AIA server returned non-200 status" )
46+ errBundleLeafNil = errors .New ("leaf certificate is nil" )
47+ errBundleUnknownTrustStore = errors .New ("unknown trust_store" )
3448)
3549
3650// privateNetworks contains CIDR ranges for private, reserved, and shared
@@ -69,12 +83,12 @@ func MozillaRootPool() (*x509.CertPool, error) {
6983 mozillaPoolOnce .Do (func () {
7084 pool := x509 .NewCertPool ()
7185 if ! pool .AppendCertsFromPEM ([]byte (embedded .MozillaCACertificatesPEM ())) {
72- mozillaPoolErr = errors . New ( "parsing embedded Mozilla root certificates" )
86+ errMozillaPool = errMozillaRootParse
7387 return
7488 }
7589 mozillaPool = pool
7690 })
77- return mozillaPool , mozillaPoolErr
91+ return mozillaPool , errMozillaPool
7892}
7993
8094// MozillaRootSubjects returns a set of raw ASN.1 subject byte strings from all
@@ -186,11 +200,11 @@ type lookupIPAddressesFunc func(ctx context.Context, host string) ([]net.IP, err
186200
187201func ipBlockedForAIA (ip net.IP ) error {
188202 if ip .IsLoopback () || ip .IsLinkLocalUnicast () || ip .IsLinkLocalMulticast () || ip .IsUnspecified () {
189- return fmt .Errorf ("blocked address %s (loopback, link-local, or unspecified)" , ip .String ())
203+ return fmt .Errorf ("%w %s (loopback, link-local, or unspecified)" , errAIAAddressBlocked , ip .String ())
190204 }
191205 for _ , network := range privateNetworks {
192206 if network .Contains (ip ) {
193- return fmt .Errorf ("blocked private address %s" , ip .String ())
207+ return fmt .Errorf ("%w %s" , errAIAPrivateAddress , ip .String ())
194208 }
195209 }
196210 return nil
@@ -213,11 +227,11 @@ func ValidateAIAURLWithOptions(ctx context.Context, input ValidateAIAURLInput) e
213227 case "http" , "https" :
214228 // allowed
215229 default :
216- return fmt .Errorf ("unsupported scheme %q (only http and https are allowed)" , parsed .Scheme )
230+ return fmt .Errorf ("%w %q (only http and https are allowed)" , errAIAUnsupportedScheme , parsed .Scheme )
217231 }
218232 host := parsed .Hostname ()
219233 if host == "" {
220- return fmt . Errorf ( "missing hostname in URL" )
234+ return errAIAMissingHostname
221235 }
222236
223237 if input .AllowPrivateNetworks {
@@ -248,7 +262,7 @@ func ValidateAIAURLWithOptions(ctx context.Context, input ValidateAIAURLInput) e
248262 return fmt .Errorf ("resolving host %q: %w" , host , err )
249263 }
250264 if len (ips ) == 0 {
251- return fmt .Errorf ("resolving host %q: no IP addresses returned " , host )
265+ return fmt .Errorf ("resolving host %q: %w " , host , errAIAResolveNoIPs )
252266 }
253267 for _ , resolvedIP := range ips {
254268 if blockedErr := ipBlockedForAIA (resolvedIP ); blockedErr != nil {
@@ -366,12 +380,12 @@ func FetchLeafFromURL(ctx context.Context, input FetchLeafFromURLInput) (*x509.C
366380 return nil , fmt .Errorf ("parsing URL: %w" , err )
367381 }
368382 if parsed .Scheme != "https" {
369- return nil , fmt .Errorf ("invalid URL scheme %q (https required)" , parsed .Scheme )
383+ return nil , fmt .Errorf ("%w: %q" , errFetchLeafHTTPSRequired , parsed .Scheme )
370384 }
371385
372386 host := parsed .Hostname ()
373387 if host == "" {
374- return nil , fmt . Errorf ( "missing hostname in URL" )
388+ return nil , errFetchLeafMissingHostname
375389 }
376390 port := parsed .Port ()
377391 if port == "" {
@@ -395,11 +409,11 @@ func FetchLeafFromURL(ctx context.Context, input FetchLeafFromURLInput) (*x509.C
395409
396410 tlsConn , ok := conn .(* tls.Conn )
397411 if ! ok {
398- return nil , fmt .Errorf ("TLS dial to %s:%s: connection is not TLS" , host , port )
412+ return nil , fmt .Errorf ("%w for %s:%s" , errFetchLeafNotTLS , host , port )
399413 }
400414 certs := tlsConn .ConnectionState ().PeerCertificates
401415 if len (certs ) == 0 {
402- return nil , fmt .Errorf ("no certificates returned by %s:%s" , host , port )
416+ return nil , fmt .Errorf ("%w for %s:%s" , errFetchLeafNoCerts , host , port )
403417 }
404418 return certs [0 ], nil
405419}
@@ -429,7 +443,7 @@ func FetchAIACertificates(ctx context.Context, input FetchAIACertificatesInput)
429443 Timeout : input .Timeout ,
430444 CheckRedirect : func (req * http.Request , via []* http.Request ) error {
431445 if len (via ) >= maxRedirects {
432- return fmt .Errorf ("stopped after %d redirects" , maxRedirects )
446+ return fmt .Errorf ("%w: stopped after %d redirects" , errAIAFetchRedirects , maxRedirects )
433447 }
434448 if err := ValidateAIAURLWithOptions (req .Context (), ValidateAIAURLInput {URL : req .URL .String (), AllowPrivateNetworks : input .AllowPrivateNetworks }); err != nil {
435449 return fmt .Errorf ("redirect blocked: %w" , err )
@@ -488,7 +502,7 @@ func fetchCertificatesFromURL(ctx context.Context, input fetchCertificatesFromUR
488502 defer func () { _ = resp .Body .Close () }()
489503
490504 if resp .StatusCode != http .StatusOK {
491- return nil , fmt .Errorf ("HTTP %d from %s" , resp .StatusCode , input .URL )
505+ return nil , fmt .Errorf ("%w: HTTP %d from %s" , errAIAHTTPStatus , resp .StatusCode , input .URL )
492506 }
493507
494508 body , err := io .ReadAll (io .LimitReader (resp .Body , 1 << 20 )) // 1MB limit
@@ -538,8 +552,7 @@ func detectAndSwapLeaf(leaf *x509.Certificate, extras []*x509.Certificate) (*x50
538552func checkSHA1Signatures (chain []* x509.Certificate ) []string {
539553 var warnings []string
540554 for _ , cert := range chain {
541- switch cert .SignatureAlgorithm {
542- case x509 .SHA1WithRSA , x509 .ECDSAWithSHA1 :
555+ if cert .SignatureAlgorithm == x509 .SHA1WithRSA || cert .SignatureAlgorithm == x509 .ECDSAWithSHA1 {
543556 warnings = append (warnings , fmt .Sprintf ("certificate %q uses deprecated SHA-1 signature algorithm (%s)" , cert .Subject .CommonName , cert .SignatureAlgorithm ))
544557 }
545558 }
@@ -572,7 +585,7 @@ type BundleInput struct {
572585// Bundle resolves the full certificate chain for a leaf certificate.
573586func Bundle (ctx context.Context , input BundleInput ) (* BundleResult , error ) {
574587 if input .Leaf == nil {
575- return nil , fmt . Errorf ( "leaf certificate is nil" )
588+ return nil , errBundleLeafNil
576589 }
577590 leaf := input .Leaf
578591 opts := input .Options
@@ -627,7 +640,7 @@ func Bundle(ctx context.Context, input BundleInput) (*BundleResult, error) {
627640 rootPool .AddCert (cert )
628641 }
629642 default :
630- return nil , fmt .Errorf ("unknown trust_store : %q" , opts .TrustStore )
643+ return nil , fmt .Errorf ("%w : %q" , errBundleUnknownTrustStore , opts .TrustStore )
631644 }
632645
633646 // Verify
0 commit comments