initial commit [141.14.140.180,mike]
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The Mozilla CA list as extracted by the curl project, refreshed with
|
||||
//
|
||||
// curl -o ca-bundle.pem https://curl.se/ca/cacert.pem
|
||||
//
|
||||
// It is not used unless the system store fails: on a machine old enough that
|
||||
// its ca-certificates package predates Let's Encrypt's ISRG roots, every
|
||||
// https:// forge is unreachable otherwise, and updating that package is often
|
||||
// no longer possible there.
|
||||
//
|
||||
//go:embed ca-bundle.pem
|
||||
var caBundle []byte
|
||||
|
||||
// How certificates are verified. Starts at the system store and falls back one
|
||||
// step when that store turns out not to know the issuer.
|
||||
type caMode int
|
||||
|
||||
const (
|
||||
caSystem caMode = iota
|
||||
caBundled
|
||||
caFile
|
||||
caNone
|
||||
)
|
||||
|
||||
func (m caMode) String() string {
|
||||
switch m {
|
||||
case caBundled:
|
||||
return "bundled CA list"
|
||||
case caFile:
|
||||
return "--cacert " + caCertPath()
|
||||
case caNone:
|
||||
return "no verification"
|
||||
}
|
||||
return "system CA store"
|
||||
}
|
||||
|
||||
// caCurrent is the mode every request uses; the fallback in httpGet moves it
|
||||
// forward at most once per run.
|
||||
var caCurrent = caSystem
|
||||
|
||||
func bundledPool() *x509.CertPool {
|
||||
pool := x509.NewCertPool()
|
||||
if !pool.AppendCertsFromPEM(caBundle) {
|
||||
return nil
|
||||
}
|
||||
return pool
|
||||
}
|
||||
|
||||
func filePool(path string) (*x509.CertPool, error) {
|
||||
pem, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read --cacert %s: %w", path, err)
|
||||
}
|
||||
pool := x509.NewCertPool()
|
||||
if !pool.AppendCertsFromPEM(pem) {
|
||||
return nil, fmt.Errorf("no certificates found in %s", path)
|
||||
}
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
// tlsConfig builds the config for the mode currently in force. The system
|
||||
// store stays the default: it is the one the administrator controls.
|
||||
func tlsConfig(mode caMode) (*tls.Config, error) {
|
||||
switch mode {
|
||||
case caBundled:
|
||||
pool := bundledPool()
|
||||
if pool == nil {
|
||||
return nil, errors.New("the bundled CA list could not be parsed")
|
||||
}
|
||||
return &tls.Config{RootCAs: pool}, nil
|
||||
case caFile:
|
||||
pool, err := filePool(caCertPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tls.Config{RootCAs: pool}, nil
|
||||
case caNone:
|
||||
return &tls.Config{InsecureSkipVerify: true}, nil
|
||||
}
|
||||
return &tls.Config{}, nil
|
||||
}
|
||||
|
||||
func caCertPath() string {
|
||||
if opt.cacert != "" {
|
||||
return expandTilde(opt.cacert)
|
||||
}
|
||||
return expandTilde(os.Getenv("UPD_CACERT"))
|
||||
}
|
||||
|
||||
// A certificate the local store cannot chain up to a root it knows. This is
|
||||
// the failure the bundled list exists for; no other x509 problem (expired,
|
||||
// wrong host name) would be fixed by more roots.
|
||||
func isUnknownAuthority(err error) bool {
|
||||
var unknown x509.UnknownAuthorityError
|
||||
if errors.As(err, &unknown) {
|
||||
return true
|
||||
}
|
||||
// Some paths only carry the verifier's verdict as text.
|
||||
return strings.Contains(err.Error(), "certificate signed by unknown authority") ||
|
||||
strings.Contains(err.Error(), "x509: failed to load system roots")
|
||||
}
|
||||
|
||||
// Errors that will still be errors on the next attempt: retrying a rejected
|
||||
// certificate, an unresolvable host or a handshake the two sides cannot agree
|
||||
// on only makes the output longer.
|
||||
func isPermanent(err error) bool {
|
||||
var (
|
||||
hostname x509.HostnameError
|
||||
invalid x509.CertificateInvalidError
|
||||
verify *tls.CertificateVerificationError
|
||||
record tls.RecordHeaderError
|
||||
dns *net.DNSError
|
||||
)
|
||||
switch {
|
||||
case isUnknownAuthority(err),
|
||||
errors.As(err, &hostname),
|
||||
errors.As(err, &invalid),
|
||||
errors.As(err, &verify),
|
||||
errors.As(err, &record):
|
||||
return true
|
||||
case errors.As(err, &dns):
|
||||
return !dns.IsTemporary
|
||||
}
|
||||
msg := err.Error()
|
||||
return strings.Contains(msg, "tls: ") || strings.Contains(msg, "x509: ")
|
||||
}
|
||||
Reference in New Issue
Block a user