// LoadConfig attempts to load the configuration from a byte slice. // On error, it returns nil. func LoadConfig(config []byte) (*Config, error) { var cfg = &Config{} err := json.Unmarshal(config, &cfg) if err != nil { return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("failed to unmarshal configuration: "+err.Error())) } if cfg.Signing == nil { return nil, errors.New("No \"signing\" field present") } if cfg.Signing.Default == nil { log.Debugf("no default given: using default config") cfg.Signing.Default = DefaultConfig() } else { if err := cfg.Signing.Default.populate(cfg); err != nil { return nil, err } } for k := range cfg.Signing.Profiles { if err := cfg.Signing.Profiles[k].populate(cfg); err != nil { return nil, err } } if !cfg.Valid() { return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("invalid configuration")) } log.Debugf("configuration ok") return cfg, nil }
// BundleFromRemote fetches the certificate served by the server at // serverName (or ip, if the ip argument is not the empty string). It // is expected that the method will be able to make a connection at // port 443. The certificate used by the server in this connection is // used to build the bundle, which will necessarily be keyless. func (b *Bundler) BundleFromRemote(serverName, ip string, flavor BundleFlavor) (*Bundle, error) { config := &tls.Config{ RootCAs: b.RootPool, ServerName: serverName, } // Dial by IP if present var dialName string if ip != "" { dialName = ip + ":443" } else { dialName = serverName + ":443" } log.Debugf("bundling from remote %s", dialName) dialer := &net.Dialer{Timeout: time.Duration(5) * time.Second} conn, err := tls.DialWithDialer(dialer, "tcp", dialName, config) var dialError string // If there's an error in tls.Dial, try again with // InsecureSkipVerify to fetch the remote bundle to (re-)bundle // with. If the bundle is indeed not usable (expired, mismatched // hostnames, etc.), report the error. Otherwise, create a // working bundle and insert the tls error in the bundle.Status. if err != nil { log.Debugf("dial failed: %v", err) // record the error msg dialError = fmt.Sprintf("Failed rigid TLS handshake with %s: %v", dialName, err) // dial again with InsecureSkipVerify log.Debugf("try again with InsecureSkipVerify.") config.InsecureSkipVerify = true conn, err = tls.DialWithDialer(dialer, "tcp", dialName, config) if err != nil { log.Debugf("dial with InsecureSkipVerify failed: %v", err) return nil, errors.Wrap(errors.DialError, errors.Unknown, err) } } connState := conn.ConnectionState() certs := connState.PeerCertificates err = conn.VerifyHostname(serverName) if err != nil { log.Debugf("failed to verify hostname: %v", err) return nil, errors.Wrap(errors.CertificateError, errors.VerifyFailed, err) } // Bundle with remote certs. Inject the initial dial error, if any, to the status reporting. bundle, err := b.Bundle(certs, nil, flavor) if err != nil { return nil, err } else if dialError != "" { bundle.Status.Messages = append(bundle.Status.Messages, dialError) } return bundle, err }
// Generate generates a key as specified in the request. Currently, // only ECDSA and RSA are supported. func (kr *KeyRequest) Generate() (interface{}, error) { log.Debugf("generate key from request: algo=%s, size=%d", kr.Algo, kr.Size) switch kr.Algo { case "rsa": if kr.Size < 2048 { return nil, errors.New("RSA key is too weak") } return rsa.GenerateKey(rand.Reader, kr.Size) case "ecdsa": var curve elliptic.Curve switch kr.Size { case curveP256: curve = elliptic.P256() case curveP384: curve = elliptic.P384() case curveP521: curve = elliptic.P521() default: return nil, errors.New("invalid curve") } return ecdsa.GenerateKey(curve, rand.Reader) default: return nil, errors.New("invalid algorithm") } }
// LoadFile attempts to load the db configuration file stored at the path // and returns the configuration. On error, it returns nil. func LoadFile(path string) (cfg *DBConfig, err error) { log.Debugf("loading db configuration file from %s", path) if path == "" { return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("invalid path")) } var body []byte body, err = ioutil.ReadFile(path) if err != nil { return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("could not read configuration file")) } cfg = &DBConfig{} err = json.Unmarshal(body, &cfg) if err != nil { return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("failed to unmarshal configuration: "+err.Error())) } if cfg.DataSourceName == "" || cfg.DriverName == "" { return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("invalid db configuration")) } return }
// Generate generates a key as specified in the request. Currently, // only ECDSA and RSA are supported. func (kr *BasicKeyRequest) Generate() (crypto.PrivateKey, error) { log.Debugf("generate key from request: algo=%s, size=%d", kr.Algo(), kr.Size()) switch kr.Algo() { case "rsa": if kr.Size() < 2048 { return nil, errors.New("RSA key is too weak") } if kr.Size() > 8192 { return nil, errors.New("RSA key size too large") } return rsa.GenerateKey(rand.Reader, kr.Size()) case "ecdsa": var curve elliptic.Curve switch kr.Size() { case curveP256: curve = elliptic.P256() case curveP384: curve = elliptic.P384() case curveP521: curve = elliptic.P521() default: return nil, errors.New("invalid curve") } return ecdsa.GenerateKey(curve, rand.Reader) default: return nil, errors.New("invalid algorithm") } }
func (b *Bundler) verifyChain(chain []*fetchedIntermediate) bool { // This process will verify if the root of the (partial) chain is in our root pool, // and will fail otherwise. log.Debugf("verifying chain") for vchain := chain[:]; len(vchain) > 0; vchain = vchain[1:] { cert := vchain[0] // If this is a certificate in one of the pools, skip it. if b.KnownIssuers[string(cert.Cert.Signature)] { log.Debugf("certificate is known") continue } _, err := cert.Cert.Verify(b.VerifyOptions()) if err != nil { log.Debugf("certificate failed verification: %v", err) return false } else if len(chain) == len(vchain) && isChainRootNode(cert.Cert) { // The first certificate in the chain is a root; it shouldn't be stored. log.Debug("looking at root certificate, will not store") continue } // leaf cert has an empty name, don't store leaf cert. if cert.Name == "" { continue } log.Debug("add certificate to intermediate pool:", cert.Name) b.IntermediatePool.AddCert(cert.Cert) b.KnownIssuers[string(cert.Cert.Signature)] = true fileName := filepath.Join(IntermediateStash, cert.Name) var block = pem.Block{Type: "CERTIFICATE", Bytes: cert.Cert.Raw} log.Debugf("write intermediate to stash directory: %s", fileName) // If the write fails, verification should not fail. err = ioutil.WriteFile(fileName, pem.EncodeToMemory(&block), 0644) if err != nil { log.Errorf("failed to write new intermediate: %v", err) } else { log.Info("stashed new intermediate ", cert.Name) } } return true }
// fetchRemoteCertificate retrieves a single URL pointing to a certificate // and attempts to first parse it as a DER-encoded certificate; if // this fails, it attempts to decode it as a PEM-encoded certificate. func fetchRemoteCertificate(certURL string) (fi *fetchedIntermediate, err error) { log.Debugf("fetching remote certificate: %s", certURL) var resp *http.Response resp, err = http.Get(certURL) if err != nil { log.Debugf("failed HTTP get: %v", err) return } defer resp.Body.Close() var certData []byte certData, err = ioutil.ReadAll(resp.Body) if err != nil { log.Debugf("failed to read response body: %v", err) return } log.Debugf("attempting to parse certificate as DER") crt, err := x509.ParseCertificate(certData) if err != nil { log.Debugf("attempting to parse certificate as PEM") crt, err = helpers.ParseCertificatePEM(certData) if err != nil { log.Debugf("failed to parse certificate: %v", err) return } } log.Debugf("certificate fetch succeeds") fi = &fetchedIntermediate{Cert: crt, Name: constructCertFileName(crt)} return }
// Valid checks the signature policies, ensuring they are valid // policies. A policy is valid if it has defined at least key usages // to be used, and a valid default profile has defined at least a // default expiration. func (p *Signing) Valid() bool { if p == nil { return false } log.Debugf("validating configuration") if !p.Default.validProfile(true) { log.Debugf("default profile is invalid") return false } for _, sp := range p.Profiles { if !sp.validProfile(false) { log.Debugf("invalid profile") return false } } return true }
// UnmarshalJSON unmarshals a JSON string into an OID. func (oid *OID) UnmarshalJSON(data []byte) (err error) { if data[0] != '"' || data[len(data)-1] != '"' { return errors.New("OID JSON string not wrapped in quotes." + string(data)) } data = data[1 : len(data)-1] parsedOid, err := parseObjectIdentifier(string(data)) if err != nil { return err } *oid = OID(parsedOid) log.Debugf("Parsed OID %v", *oid) return }
// LoadFile attempts to load the configuration file stored at the path // and returns the configuration. On error, it returns nil. func LoadFile(path string) (*Config, error) { log.Debugf("loading configuration file from %s", path) if path == "" { return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("invalid path")) } body, err := ioutil.ReadFile(path) if err != nil { return nil, cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("could not read configuration file")) } return LoadConfig(body) }
// BundleFromPEM builds a certificate bundle from the set of byte // slices containing the PEM-encoded certificate(s), private key. func (b *Bundler) BundleFromPEM(certsPEM, keyPEM []byte, flavor BundleFlavor) (*Bundle, error) { log.Debug("bundling from PEM files") var key crypto.Signer var err error if len(keyPEM) != 0 { key, err = helpers.ParsePrivateKeyPEM(keyPEM) if err != nil { log.Debugf("failed to parse private key: %v", err) return nil, err } } certs, err := helpers.ParseCertificatesPEM(certsPEM) if err != nil { log.Debugf("failed to parse certificates: %v", err) return nil, err } else if len(certs) == 0 { log.Debugf("no certificates found") return nil, errors.New(errors.CertificateError, errors.DecodeFailed) } log.Debugf("bundle ready") return b.Bundle(certs, key, flavor) }
// ParseCertificatePEM parses and returns a PEM-encoded certificate, // can handle PEM encoded PKCS #7 structures. func ParseCertificatePEM(certPEM []byte) (*x509.Certificate, error) { certPEM = bytes.TrimSpace(certPEM) cert, rest, err := ParseOneCertificateFromPEM(certPEM) if err != nil { // Log the actual parsing error but throw a default parse error message. log.Debugf("Certificate parsing error: %v", err) return nil, cferr.New(cferr.CertificateError, cferr.ParseFailed) } else if cert == nil { return nil, cferr.New(cferr.CertificateError, cferr.DecodeFailed) } else if len(rest) > 0 { return nil, cferr.Wrap(cferr.CertificateError, cferr.ParseFailed, errors.New("the PEM file should contain only one object")) } else if len(cert) > 1 { return nil, cferr.Wrap(cferr.CertificateError, cferr.ParseFailed, errors.New("the PKCS7 object in the PEM file should contain only one certificate")) } return cert[0], nil }
// A valid profile must be a valid local profile or a valid remote profile. // A valid local profile has defined at least key usages to be used, and a // valid local default profile has defined at least a default expiration. // A valid remote profile (default or not) has remote signer initialized. // In addition, a remote profile must has a valid auth provider if auth // key defined. func (p *SigningProfile) validProfile(isDefault bool) bool { if p == nil { return false } if p.RemoteName != "" { log.Debugf("validate remote profile") if p.RemoteServer == "" { log.Debugf("invalid remote profile: no remote signer specified") return false } if p.AuthKeyName != "" && p.Provider == nil { log.Debugf("invalid remote profile: auth key name is defined but no auth provider is set") return false } } else { log.Debugf("validate local profile") if !isDefault { if len(p.Usage) == 0 { log.Debugf("invalid local profile: no usages specified") return false } else if _, _, unk := p.Usages(); len(unk) == len(p.Usage) { log.Debugf("invalid local profile: no valid usages") return false } } else { if p.Expiry == 0 { log.Debugf("invalid local profile: no expiry set") return false } } } log.Debugf("profile is valid") return true }
// New returns a new PKCS #11 signer. func NewPKCS11Signer(cfg ocspConfig.Config) (ocsp.Signer, error) { log.Debugf("Loading PKCS #11 module %s", cfg.PKCS11.Module) certData, err := ioutil.ReadFile(cfg.CACertFile) if err != nil { return nil, errors.New(errors.CertificateError, errors.ReadFailed) } cert, err := helpers.ParseCertificatePEM(certData) if err != nil { return nil, err } PKCS11 := cfg.PKCS11 priv, err := pkcs11key.New(PKCS11.Module, PKCS11.Token, PKCS11.PIN, PKCS11.Label) if err != nil { return nil, errors.New(errors.PrivateKeyError, errors.ReadFailed) } return ocsp.NewSigner(cert, cert, priv, cfg.Interval) }
// BundleFromFile takes a set of files containing the PEM-encoded leaf certificate // (optionally along with some intermediate certs), the PEM-encoded private key // and returns the bundle built from that key and the certificate(s). func (b *Bundler) BundleFromFile(bundleFile, keyFile string, flavor BundleFlavor) (*Bundle, error) { log.Debug("Loading Certificate: ", bundleFile) certsPEM, err := ioutil.ReadFile(bundleFile) if err != nil { return nil, errors.Wrap(errors.CertificateError, errors.ReadFailed, err) } var keyPEM []byte // Load private key PEM only if a file is given if keyFile != "" { log.Debug("Loading private key: ", keyFile) keyPEM, err = ioutil.ReadFile(keyFile) if err != nil { log.Debugf("failed to read private key: ", err) return nil, errors.Wrap(errors.PrivateKeyError, errors.ReadFailed, err) } if len(keyPEM) == 0 { log.Debug("key is empty") return nil, errors.Wrap(errors.PrivateKeyError, errors.DecodeFailed, err) } } return b.BundleFromPEM(certsPEM, keyPEM, flavor) }
// New returns a new PKCS #11 signer. func New(caCertFile string, policy *config.Signing, cfg *pkcs11key.Config) (signer.Signer, error) { if cfg == nil { return nil, errors.New(errors.PrivateKeyError, errors.ReadFailed) } log.Debugf("Loading PKCS #11 module %s", cfg.Module) certData, err := ioutil.ReadFile(caCertFile) if err != nil { return nil, errors.New(errors.PrivateKeyError, errors.ReadFailed) } cert, err := helpers.ParseCertificatePEM(certData) if err != nil { return nil, err } priv, err := pkcs11key.New(cfg.Module, cfg.TokenLabel, cfg.PIN, cfg.PrivateKeyLabel) if err != nil { return nil, errors.New(errors.PrivateKeyError, errors.ReadFailed) } sigAlgo := signer.DefaultSigAlgo(priv) return local.NewSigner(priv, cert, sigAlgo, policy) }
// populate is used to fill in the fields that are not in JSON // // First, the ExpiryString parameter is needed to parse // expiration timestamps from JSON. The JSON decoder is not able to // decode a string time duration to a time.Duration, so this is called // when loading the configuration to properly parse and fill out the // Expiry parameter. // This function is also used to create references to the auth key // and default remote for the profile. // It returns true if ExpiryString is a valid representation of a // time.Duration, and the AuthKeyString and RemoteName point to // valid objects. It returns false otherwise. func (p *SigningProfile) populate(cfg *Config) error { if p == nil { return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("can't parse nil profile")) } var err error if p.RemoteName == "" { log.Debugf("parse expiry in profile") if p.ExpiryString == "" { return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("empty expiry string")) } dur, err := time.ParseDuration(p.ExpiryString) if err != nil { return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, err) } log.Debugf("expiry is valid") p.Expiry = dur if p.BackdateString != "" { dur, err = time.ParseDuration(p.BackdateString) if err != nil { return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, err) } p.Backdate = dur } if !p.NotBefore.IsZero() && !p.NotAfter.IsZero() && p.NotAfter.Before(p.NotBefore) { return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, err) } if len(p.PolicyStrings) > 0 { p.Policies = make([]asn1.ObjectIdentifier, len(p.PolicyStrings)) for i, oidString := range p.PolicyStrings { p.Policies[i], err = parseObjectIdentifier(oidString) if err != nil { return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, err) } } } } else { log.Debug("match remote in profile to remotes section") if remote := cfg.Remotes[p.RemoteName]; remote != "" { if err := p.updateRemote(remote); err != nil { return err } } else { return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("failed to find remote in remotes section")) } } if p.AuthKeyName != "" { log.Debug("match auth key in profile to auth_keys section") if key, ok := cfg.AuthKeys[p.AuthKeyName]; ok == true { if key.Type == "standard" { p.Provider, err = auth.New(key.Key, nil) if err != nil { log.Debugf("failed to create new standard auth provider: %v", err) return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("failed to create new standard auth provider")) } } else { log.Debugf("unknown authentication type %v", key.Type) return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("unknown authentication type")) } } else { return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("failed to find auth_key in auth_keys section")) } } return nil }
// populate is used to fill in the fields that are not in JSON // // First, the ExpiryString parameter is needed to parse // expiration timestamps from JSON. The JSON decoder is not able to // decode a string time duration to a time.Duration, so this is called // when loading the configuration to properly parse and fill out the // Expiry parameter. // This function is also used to create references to the auth key // and default remote for the profile. // It returns true if ExpiryString is a valid representation of a // time.Duration, and the AuthKeyString and RemoteName point to // valid objects. It returns false otherwise. func (p *SigningProfile) populate(cfg *Config) error { if p == nil { return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("can't parse nil profile")) } var err error if p.RemoteName == "" && p.AuthRemote.RemoteName == "" { log.Debugf("parse expiry in profile") if p.ExpiryString == "" { return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("empty expiry string")) } dur, err := time.ParseDuration(p.ExpiryString) if err != nil { return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, err) } log.Debugf("expiry is valid") p.Expiry = dur if p.BackdateString != "" { dur, err = time.ParseDuration(p.BackdateString) if err != nil { return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, err) } p.Backdate = dur } if !p.NotBefore.IsZero() && !p.NotAfter.IsZero() && p.NotAfter.Before(p.NotBefore) { return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, err) } if len(p.Policies) > 0 { for _, policy := range p.Policies { for _, qualifier := range policy.Qualifiers { if qualifier.Type != "" && qualifier.Type != "id-qt-unotice" && qualifier.Type != "id-qt-cps" { return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("invalid policy qualifier type")) } } } } } else if p.RemoteName != "" { log.Debug("match remote in profile to remotes section") if p.AuthRemote.RemoteName != "" { log.Error("profile has both a remote and an auth remote specified") return cferr.New(cferr.PolicyError, cferr.InvalidPolicy) } if remote := cfg.Remotes[p.RemoteName]; remote != "" { if err := p.updateRemote(remote); err != nil { return err } } else { return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("failed to find remote in remotes section")) } } else { log.Debug("match auth remote in profile to remotes section") if remote := cfg.Remotes[p.AuthRemote.RemoteName]; remote != "" { if err := p.updateRemote(remote); err != nil { return err } } else { return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("failed to find remote in remotes section")) } } if p.AuthKeyName != "" { log.Debug("match auth key in profile to auth_keys section") if key, ok := cfg.AuthKeys[p.AuthKeyName]; ok == true { if key.Type == "standard" { p.Provider, err = auth.New(key.Key, nil) if err != nil { log.Debugf("failed to create new standard auth provider: %v", err) return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("failed to create new standard auth provider")) } } else { log.Debugf("unknown authentication type %v", key.Type) return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("unknown authentication type")) } } else { return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("failed to find auth_key in auth_keys section")) } } if p.AuthRemote.AuthKeyName != "" { log.Debug("match auth remote key in profile to auth_keys section") if key, ok := cfg.AuthKeys[p.AuthRemote.AuthKeyName]; ok == true { if key.Type == "standard" { p.RemoteProvider, err = auth.New(key.Key, nil) if err != nil { log.Debugf("failed to create new standard auth provider: %v", err) return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("failed to create new standard auth provider")) } } else { log.Debugf("unknown authentication type %v", key.Type) return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("unknown authentication type")) } } else { return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("failed to find auth_remote's auth_key in auth_keys section")) } } if p.NameWhitelistString != "" { log.Debug("compiling whitelist regular expression") rule, err := regexp.Compile(p.NameWhitelistString) if err != nil { return cferr.Wrap(cferr.PolicyError, cferr.InvalidPolicy, errors.New("failed to compile name whitelist section")) } p.NameWhitelist = rule } return nil }
// Bundle takes an X509 certificate (already in the // Certificate structure), a private key as crypto.Signer in one of the appropriate // formats (i.e. *rsa.PrivateKey or *ecdsa.PrivateKey, or even a opaque key), using them to // build a certificate bundle. func (b *Bundler) Bundle(certs []*x509.Certificate, key crypto.Signer, flavor BundleFlavor) (*Bundle, error) { log.Infof("bundling certificate for %+v", certs[0].Subject) if len(certs) == 0 { return nil, nil } // Detect reverse ordering of the cert chain. if len(certs) > 1 && !partialVerify(certs) { rcerts := reverse(certs) if partialVerify(rcerts) { certs = rcerts } } var ok bool cert := certs[0] if key != nil { switch { case cert.PublicKeyAlgorithm == x509.RSA: var rsaPublicKey *rsa.PublicKey if rsaPublicKey, ok = key.Public().(*rsa.PublicKey); !ok { return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch) } if cert.PublicKey.(*rsa.PublicKey).N.Cmp(rsaPublicKey.N) != 0 { return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch) } case cert.PublicKeyAlgorithm == x509.ECDSA: var ecdsaPublicKey *ecdsa.PublicKey if ecdsaPublicKey, ok = key.Public().(*ecdsa.PublicKey); !ok { return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch) } if cert.PublicKey.(*ecdsa.PublicKey).X.Cmp(ecdsaPublicKey.X) != 0 { return nil, errors.New(errors.PrivateKeyError, errors.KeyMismatch) } default: return nil, errors.New(errors.PrivateKeyError, errors.NotRSAOrECC) } } else { switch { case cert.PublicKeyAlgorithm == x509.RSA: case cert.PublicKeyAlgorithm == x509.ECDSA: default: return nil, errors.New(errors.PrivateKeyError, errors.NotRSAOrECC) } } if cert.CheckSignatureFrom(cert) == nil { return nil, errors.New(errors.CertificateError, errors.SelfSigned) } bundle := new(Bundle) bundle.Cert = cert bundle.Key = key bundle.Issuer = &cert.Issuer bundle.Subject = &cert.Subject bundle.buildHostnames() // verify and store input intermediates to the intermediate pool. // Ignore the returned error here, will treat it in the second call. b.fetchIntermediates(certs) chains, err := cert.Verify(b.VerifyOptions()) if err != nil { log.Debugf("verification failed: %v", err) // If the error was an unknown authority, try to fetch // the intermediate specified in the AIA and add it to // the intermediates bundle. switch err := err.(type) { case x509.UnknownAuthorityError: // Do nothing -- have the default case return out. default: return nil, errors.Wrap(errors.CertificateError, errors.VerifyFailed, err) } log.Debugf("searching for intermediates via AIA issuer") err = b.fetchIntermediates(certs) if err != nil { log.Debugf("search failed: %v", err) return nil, errors.Wrap(errors.CertificateError, errors.VerifyFailed, err) } log.Debugf("verifying new chain") chains, err = cert.Verify(b.VerifyOptions()) if err != nil { log.Debugf("failed to verify chain: %v", err) return nil, errors.Wrap(errors.CertificateError, errors.VerifyFailed, err) } log.Debugf("verify ok") } var matchingChains [][]*x509.Certificate switch flavor { case Optimal: matchingChains = optimalChains(chains) case Ubiquitous: if len(ubiquity.Platforms) == 0 { log.Warning("No metadata, Ubiquitous falls back to Optimal.") } matchingChains = ubiquitousChains(chains) case Force: matchingChains = forceChains(certs, chains) default: matchingChains = ubiquitousChains(chains) } bundle.Chain = matchingChains[0] // Include at least one intermediate if the leaf has enabled OCSP and is not CA. if bundle.Cert.OCSPServer != nil && !bundle.Cert.IsCA && len(bundle.Chain) <= 2 { // No op. Return one intermediate if there is one. } else { // do not include the root. bundle.Chain = bundle.Chain[:len(bundle.Chain)-1] } statusCode := int(errors.Success) var messages []string // Check if bundle is expiring. expiringCerts := checkExpiringCerts(bundle.Chain) bundle.Expires = helpers.ExpiryTime(bundle.Chain) if len(expiringCerts) > 0 { statusCode |= errors.BundleExpiringBit messages = append(messages, expirationWarning(expiringCerts)) } // Check if bundle contains SHA2 certs. if ubiquity.ChainHashUbiquity(matchingChains[0]) <= ubiquity.SHA2Ubiquity { statusCode |= errors.BundleNotUbiquitousBit messages = append(messages, sha2Warning) } // Check if bundle contains ECDSA signatures. if ubiquity.ChainKeyAlgoUbiquity(matchingChains[0]) <= ubiquity.ECDSA256Ubiquity { statusCode |= errors.BundleNotUbiquitousBit messages = append(messages, ecdsaWarning) } // Add root store presence info root := matchingChains[0][len(matchingChains[0])-1] bundle.Root = root log.Infof("the anchoring root is %v", root.Subject) // Check if there is any platform that doesn't trust the chain. // Also, an warning will be generated if ubiquity.Platforms is nil, untrusted := ubiquity.UntrustedPlatforms(root) untrustedMsg := untrustedPlatformsWarning(untrusted) if len(untrustedMsg) > 0 { log.Debug("Populate untrusted platform warning.") statusCode |= errors.BundleNotUbiquitousBit messages = append(messages, untrustedMsg) } // Check if there is any platform that rejects the chain because of SHA1 deprecation. deprecated := ubiquity.DeprecatedSHA1Platforms(matchingChains[0]) if len(deprecated) > 0 { log.Debug("Populate SHA1 deprecation warning.") statusCode |= errors.BundleNotUbiquitousBit messages = append(messages, deprecateSHA1Warning(deprecated)) } bundle.Status = &BundleStatus{ExpiringSKIs: getSKIs(bundle.Chain, expiringCerts), Code: statusCode, Messages: messages, Untrusted: untrusted} bundle.Status.IsRebundled = diff(bundle.Chain, certs) log.Debugf("bundle complete") return bundle, nil }
// fetchIntermediates goes through each of the URLs in the AIA "Issuing // CA" extensions and fetches those certificates. If those // certificates are not present in either the root pool or // intermediate pool, the certificate is saved to file and added to // the list of intermediates to be used for verification. This will // not add any new certificates to the root pool; if the ultimate // issuer is not trusted, fetching the certicate here will not change // that. func (b *Bundler) fetchIntermediates(certs []*x509.Certificate) (err error) { log.Debugf("searching intermediates") if _, err := os.Stat(IntermediateStash); err != nil && os.IsNotExist(err) { log.Infof("intermediate stash directory %s doesn't exist, creating", IntermediateStash) err = os.MkdirAll(IntermediateStash, 0755) if err != nil { log.Errorf("failed to create intermediate stash directory %s: %v", IntermediateStash, err) return err } log.Infof("intermediate stash directory %s created", IntermediateStash) } // stores URLs and certificate signatures that have been seen seen := map[string]bool{} var foundChains int // Construct a verify chain as a reversed partial bundle, // such that the certs are ordered by promxity to the root CAs. var chain []*fetchedIntermediate for i, cert := range certs { var name string // Only construct filenames for non-leaf intermediate certs // so they will be saved to disk if necessary. // Leaf cert gets a empty name and will be skipped. if i > 0 { name = constructCertFileName(cert) } chain = append([]*fetchedIntermediate{&fetchedIntermediate{cert, name}}, chain...) seen[string(cert.Signature)] = true } // Verify the chain and store valid intermediates in the chain. // If it doesn't verify, fetch the intermediates and extend the chain // in a DFS manner and verify each time we hit a root. for { if len(chain) == 0 { log.Debugf("search complete") if foundChains == 0 { return x509.UnknownAuthorityError{} } return nil } current := chain[0] var advanced bool if b.verifyChain(chain) { foundChains++ } log.Debugf("walk AIA issuers") for _, url := range current.Cert.IssuingCertificateURL { if seen[url] { log.Debugf("url %s has been seen", url) continue } crt, err := fetchRemoteCertificate(url) if err != nil { continue } else if seen[string(crt.Cert.Signature)] { log.Debugf("fetched certificate is known") continue } seen[url] = true seen[string(crt.Cert.Signature)] = true chain = append([]*fetchedIntermediate{crt}, chain...) advanced = true break } if !advanced { log.Debugf("didn't advance, stepping back") chain = chain[1:] } } }