| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package cert |
| |
| import ( |
| "bytes" |
| "context" |
| "crypto/tls" |
| "crypto/x509" |
| "encoding/asn1" |
| "encoding/base64" |
| "encoding/pem" |
| "errors" |
| "fmt" |
| "strings" |
| |
| "github.com/hashicorp/vault/sdk/framework" |
| "github.com/hashicorp/vault/sdk/helper/certutil" |
| "github.com/hashicorp/vault/sdk/helper/cidrutil" |
| "github.com/hashicorp/vault/sdk/helper/ocsp" |
| "github.com/hashicorp/vault/sdk/helper/policyutil" |
| "github.com/hashicorp/vault/sdk/logical" |
| |
| "github.com/hashicorp/errwrap" |
| "github.com/hashicorp/go-multierror" |
| glob "github.com/ryanuber/go-glob" |
| ) |
| |
| // ParsedCert is a certificate that has been configured as trusted |
| type ParsedCert struct { |
| Entry *CertEntry |
| Certificates []*x509.Certificate |
| } |
| |
| func pathLogin(b *backend) *framework.Path { |
| return &framework.Path{ |
| Pattern: "login", |
| DisplayAttrs: &framework.DisplayAttributes{ |
| OperationPrefix: operationPrefixCert, |
| OperationVerb: "login", |
| }, |
| Fields: map[string]*framework.FieldSchema{ |
| "name": { |
| Type: framework.TypeString, |
| Description: "The name of the certificate role to authenticate against.", |
| }, |
| }, |
| Callbacks: map[logical.Operation]framework.OperationFunc{ |
| logical.UpdateOperation: b.loginPathWrapper(b.pathLogin), |
| logical.AliasLookaheadOperation: b.pathLoginAliasLookahead, |
| logical.ResolveRoleOperation: b.loginPathWrapper(b.pathLoginResolveRole), |
| }, |
| } |
| } |
| |
| func (b *backend) loginPathWrapper(wrappedOp func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error)) framework.OperationFunc { |
| return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { |
| // Make sure that the CRLs have been loaded before processing a login request, |
| // they might have been nil'd by an invalidate func call. |
| if err := b.populateCrlsIfNil(ctx, req.Storage); err != nil { |
| return nil, err |
| } |
| return wrappedOp(ctx, req, data) |
| } |
| } |
| |
| func (b *backend) pathLoginResolveRole(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { |
| var matched *ParsedCert |
| |
| if verifyResp, resp, err := b.verifyCredentials(ctx, req, data); err != nil { |
| return nil, err |
| } else if resp != nil { |
| return resp, nil |
| } else { |
| matched = verifyResp |
| } |
| |
| if matched == nil { |
| return logical.ErrorResponse("no certificate was matched by this request"), nil |
| } |
| |
| return logical.ResolveRoleResponse(matched.Entry.Name) |
| } |
| |
| func (b *backend) pathLoginAliasLookahead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { |
| if req.Connection == nil || req.Connection.ConnState == nil { |
| return nil, fmt.Errorf("tls connection not found") |
| } |
| clientCerts := req.Connection.ConnState.PeerCertificates |
| if len(clientCerts) == 0 { |
| return nil, fmt.Errorf("no client certificate found") |
| } |
| |
| return &logical.Response{ |
| Auth: &logical.Auth{ |
| Alias: &logical.Alias{ |
| Name: clientCerts[0].Subject.CommonName, |
| }, |
| }, |
| }, nil |
| } |
| |
| func (b *backend) pathLogin(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { |
| config, err := b.Config(ctx, req.Storage) |
| if err != nil { |
| return nil, err |
| } |
| if b.configUpdated.Load() { |
| b.updatedConfig(config) |
| } |
| |
| var matched *ParsedCert |
| if verifyResp, resp, err := b.verifyCredentials(ctx, req, data); err != nil { |
| return nil, err |
| } else if resp != nil { |
| return resp, nil |
| } else { |
| matched = verifyResp |
| } |
| |
| if matched == nil { |
| return nil, nil |
| } |
| |
| if len(matched.Entry.TokenBoundCIDRs) > 0 { |
| if req.Connection == nil { |
| b.Logger().Warn("token bound CIDRs found but no connection information available for validation") |
| return nil, logical.ErrPermissionDenied |
| } |
| if !cidrutil.RemoteAddrIsOk(req.Connection.RemoteAddr, matched.Entry.TokenBoundCIDRs) { |
| return nil, logical.ErrPermissionDenied |
| } |
| } |
| |
| clientCerts := req.Connection.ConnState.PeerCertificates |
| if len(clientCerts) == 0 { |
| return logical.ErrorResponse("no client certificate found"), nil |
| } |
| skid := base64.StdEncoding.EncodeToString(clientCerts[0].SubjectKeyId) |
| akid := base64.StdEncoding.EncodeToString(clientCerts[0].AuthorityKeyId) |
| |
| metadata := map[string]string{ |
| "cert_name": matched.Entry.Name, |
| "common_name": clientCerts[0].Subject.CommonName, |
| "serial_number": clientCerts[0].SerialNumber.String(), |
| "subject_key_id": certutil.GetHexFormatted(clientCerts[0].SubjectKeyId, ":"), |
| "authority_key_id": certutil.GetHexFormatted(clientCerts[0].AuthorityKeyId, ":"), |
| } |
| |
| // Add metadata from allowed_metadata_extensions when present, |
| // with sanitized oids (dash-separated instead of dot-separated) as keys. |
| for k, v := range b.certificateExtensionsMetadata(clientCerts[0], matched) { |
| metadata[k] = v |
| } |
| |
| auth := &logical.Auth{ |
| InternalData: map[string]interface{}{ |
| "subject_key_id": skid, |
| "authority_key_id": akid, |
| }, |
| DisplayName: matched.Entry.DisplayName, |
| Metadata: metadata, |
| Alias: &logical.Alias{ |
| Name: clientCerts[0].Subject.CommonName, |
| }, |
| } |
| |
| if config.EnableIdentityAliasMetadata { |
| auth.Alias.Metadata = metadata |
| } |
| |
| matched.Entry.PopulateTokenAuth(auth) |
| |
| return &logical.Response{ |
| Auth: auth, |
| }, nil |
| } |
| |
| func (b *backend) pathLoginRenew(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { |
| config, err := b.Config(ctx, req.Storage) |
| if err != nil { |
| return nil, err |
| } |
| if b.configUpdated.Load() { |
| b.updatedConfig(config) |
| } |
| |
| if !config.DisableBinding { |
| var matched *ParsedCert |
| if verifyResp, resp, err := b.verifyCredentials(ctx, req, d); err != nil { |
| return nil, err |
| } else if resp != nil { |
| return resp, nil |
| } else { |
| matched = verifyResp |
| } |
| |
| if matched == nil { |
| return nil, nil |
| } |
| |
| clientCerts := req.Connection.ConnState.PeerCertificates |
| if len(clientCerts) == 0 { |
| return logical.ErrorResponse("no client certificate found"), nil |
| } |
| skid := base64.StdEncoding.EncodeToString(clientCerts[0].SubjectKeyId) |
| akid := base64.StdEncoding.EncodeToString(clientCerts[0].AuthorityKeyId) |
| |
| // Certificate should not only match a registered certificate policy. |
| // Also, the identity of the certificate presented should match the identity of the certificate used during login |
| if req.Auth.InternalData["subject_key_id"] != skid && req.Auth.InternalData["authority_key_id"] != akid { |
| return nil, fmt.Errorf("client identity during renewal not matching client identity used during login") |
| } |
| |
| } |
| // Get the cert and use its TTL |
| cert, err := b.Cert(ctx, req.Storage, req.Auth.Metadata["cert_name"]) |
| if err != nil { |
| return nil, err |
| } |
| if cert == nil { |
| // User no longer exists, do not renew |
| return nil, nil |
| } |
| |
| if !policyutil.EquivalentPolicies(cert.TokenPolicies, req.Auth.TokenPolicies) { |
| return nil, fmt.Errorf("policies have changed, not renewing") |
| } |
| |
| resp := &logical.Response{Auth: req.Auth} |
| resp.Auth.TTL = cert.TokenTTL |
| resp.Auth.MaxTTL = cert.TokenMaxTTL |
| resp.Auth.Period = cert.TokenPeriod |
| return resp, nil |
| } |
| |
| func (b *backend) verifyCredentials(ctx context.Context, req *logical.Request, d *framework.FieldData) (*ParsedCert, *logical.Response, error) { |
| // Get the connection state |
| if req.Connection == nil || req.Connection.ConnState == nil { |
| return nil, logical.ErrorResponse("tls connection required"), nil |
| } |
| connState := req.Connection.ConnState |
| |
| if connState.PeerCertificates == nil || len(connState.PeerCertificates) == 0 { |
| return nil, logical.ErrorResponse("client certificate must be supplied"), nil |
| } |
| clientCert := connState.PeerCertificates[0] |
| |
| // Allow constraining the login request to a single CertEntry |
| var certName string |
| if req.Auth != nil { // It's a renewal, use the saved certName |
| certName = req.Auth.Metadata["cert_name"] |
| } else if d != nil { // d is nil if handleAuthRenew call the authRenew |
| certName = d.Get("name").(string) |
| } |
| |
| // Load the trusted certificates and other details |
| roots, trusted, trustedNonCAs, verifyConf := b.loadTrustedCerts(ctx, req.Storage, certName) |
| |
| // Get the list of full chains matching the connection and validates the |
| // certificate itself |
| trustedChains, err := validateConnState(roots, connState) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| var extraCas []*x509.Certificate |
| for _, t := range trusted { |
| extraCas = append(extraCas, t.Certificates...) |
| } |
| |
| // If trustedNonCAs is not empty it means that client had registered a non-CA cert |
| // with the backend. |
| var retErr error |
| if len(trustedNonCAs) != 0 { |
| for _, trustedNonCA := range trustedNonCAs { |
| tCert := trustedNonCA.Certificates[0] |
| // Check for client cert being explicitly listed in the config (and matching other constraints) |
| if tCert.SerialNumber.Cmp(clientCert.SerialNumber) == 0 && |
| bytes.Equal(tCert.AuthorityKeyId, clientCert.AuthorityKeyId) { |
| matches, err := b.matchesConstraints(ctx, clientCert, trustedNonCA.Certificates, trustedNonCA, verifyConf) |
| |
| // matchesConstraints returns an error when OCSP verification fails, |
| // but some other path might still give us success. Add to the |
| // retErr multierror, but avoid duplicates. This way, if we reach a |
| // failure later, we can give additional context. |
| // |
| // XXX: If matchesConstraints is updated to generate additional, |
| // immediately fatal errors, we likely need to extend it to return |
| // another boolean (fatality) or other detection scheme. |
| if err != nil && (retErr == nil || !errwrap.Contains(retErr, err.Error())) { |
| retErr = multierror.Append(retErr, err) |
| } |
| |
| if matches { |
| return trustedNonCA, nil, nil |
| } |
| } |
| } |
| } |
| |
| // If no trusted chain was found, client is not authenticated |
| // This check happens after checking for a matching configured non-CA certs |
| if len(trustedChains) == 0 { |
| if retErr == nil { |
| return nil, logical.ErrorResponse(fmt.Sprintf("invalid certificate or no client certificate supplied; additionally got errors during verification: %v", retErr)), nil |
| } |
| return nil, logical.ErrorResponse("invalid certificate or no client certificate supplied"), nil |
| } |
| |
| // Search for a ParsedCert that intersects with the validated chains and any additional constraints |
| for _, trust := range trusted { // For each ParsedCert in the config |
| for _, tCert := range trust.Certificates { // For each certificate in the entry |
| for _, chain := range trustedChains { // For each root chain that we matched |
| for _, cCert := range chain { // For each cert in the matched chain |
| if tCert.Equal(cCert) { // ParsedCert intersects with matched chain |
| match, err := b.matchesConstraints(ctx, clientCert, chain, trust, verifyConf) // validate client cert + matched chain against the config |
| |
| // See note above. |
| if err != nil && (retErr == nil || !errwrap.Contains(retErr, err.Error())) { |
| retErr = multierror.Append(retErr, err) |
| } |
| |
| // Return the first matching entry (for backwards |
| // compatibility, we continue to just pick the first |
| // one if we have multiple matches). |
| // |
| // Here, we return directly: this means that any |
| // future OCSP errors would be ignored; in the future, |
| // if these become fatal, we could revisit this |
| // choice and choose the first match after evaluating |
| // all possible candidates. |
| if match && err == nil { |
| return trust, nil, nil |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| if retErr != nil { |
| return nil, logical.ErrorResponse(fmt.Sprintf("no chain matching all constraints could be found for this login certificate; additionally got errors during verification: %v", retErr)), nil |
| } |
| |
| return nil, logical.ErrorResponse("no chain matching all constraints could be found for this login certificate"), nil |
| } |
| |
| func (b *backend) matchesConstraints(ctx context.Context, clientCert *x509.Certificate, trustedChain []*x509.Certificate, |
| config *ParsedCert, conf *ocsp.VerifyConfig, |
| ) (bool, error) { |
| soFar := !b.checkForChainInCRLs(trustedChain) && |
| b.matchesNames(clientCert, config) && |
| b.matchesCommonName(clientCert, config) && |
| b.matchesDNSSANs(clientCert, config) && |
| b.matchesEmailSANs(clientCert, config) && |
| b.matchesURISANs(clientCert, config) && |
| b.matchesOrganizationalUnits(clientCert, config) && |
| b.matchesCertificateExtensions(clientCert, config) |
| if config.Entry.OcspEnabled { |
| ocspGood, err := b.checkForCertInOCSP(ctx, clientCert, trustedChain, conf) |
| if err != nil { |
| return false, err |
| } |
| soFar = soFar && ocspGood |
| } |
| return soFar, nil |
| } |
| |
| // matchesNames verifies that the certificate matches at least one configured |
| // allowed name |
| func (b *backend) matchesNames(clientCert *x509.Certificate, config *ParsedCert) bool { |
| // Default behavior (no names) is to allow all names |
| if len(config.Entry.AllowedNames) == 0 { |
| return true |
| } |
| // At least one pattern must match at least one name if any patterns are specified |
| for _, allowedName := range config.Entry.AllowedNames { |
| if glob.Glob(allowedName, clientCert.Subject.CommonName) { |
| return true |
| } |
| |
| for _, name := range clientCert.DNSNames { |
| if glob.Glob(allowedName, name) { |
| return true |
| } |
| } |
| |
| for _, name := range clientCert.EmailAddresses { |
| if glob.Glob(allowedName, name) { |
| return true |
| } |
| } |
| |
| } |
| return false |
| } |
| |
| // matchesCommonName verifies that the certificate matches at least one configured |
| // allowed common name |
| func (b *backend) matchesCommonName(clientCert *x509.Certificate, config *ParsedCert) bool { |
| // Default behavior (no names) is to allow all names |
| if len(config.Entry.AllowedCommonNames) == 0 { |
| return true |
| } |
| // At least one pattern must match at least one name if any patterns are specified |
| for _, allowedCommonName := range config.Entry.AllowedCommonNames { |
| if glob.Glob(allowedCommonName, clientCert.Subject.CommonName) { |
| return true |
| } |
| } |
| |
| return false |
| } |
| |
| // matchesDNSSANs verifies that the certificate matches at least one configured |
| // allowed dns entry in the subject alternate name extension |
| func (b *backend) matchesDNSSANs(clientCert *x509.Certificate, config *ParsedCert) bool { |
| // Default behavior (no names) is to allow all names |
| if len(config.Entry.AllowedDNSSANs) == 0 { |
| return true |
| } |
| // At least one pattern must match at least one name if any patterns are specified |
| for _, allowedDNS := range config.Entry.AllowedDNSSANs { |
| for _, name := range clientCert.DNSNames { |
| if glob.Glob(allowedDNS, name) { |
| return true |
| } |
| } |
| } |
| |
| return false |
| } |
| |
| // matchesEmailSANs verifies that the certificate matches at least one configured |
| // allowed email in the subject alternate name extension |
| func (b *backend) matchesEmailSANs(clientCert *x509.Certificate, config *ParsedCert) bool { |
| // Default behavior (no names) is to allow all names |
| if len(config.Entry.AllowedEmailSANs) == 0 { |
| return true |
| } |
| // At least one pattern must match at least one name if any patterns are specified |
| for _, allowedEmail := range config.Entry.AllowedEmailSANs { |
| for _, email := range clientCert.EmailAddresses { |
| if glob.Glob(allowedEmail, email) { |
| return true |
| } |
| } |
| } |
| |
| return false |
| } |
| |
| // matchesURISANs verifies that the certificate matches at least one configured |
| // allowed uri in the subject alternate name extension |
| func (b *backend) matchesURISANs(clientCert *x509.Certificate, config *ParsedCert) bool { |
| // Default behavior (no names) is to allow all names |
| if len(config.Entry.AllowedURISANs) == 0 { |
| return true |
| } |
| // At least one pattern must match at least one name if any patterns are specified |
| for _, allowedURI := range config.Entry.AllowedURISANs { |
| for _, name := range clientCert.URIs { |
| if glob.Glob(allowedURI, name.String()) { |
| return true |
| } |
| } |
| } |
| |
| return false |
| } |
| |
| // matchesOrganizationalUnits verifies that the certificate matches at least one configurd allowed OU |
| func (b *backend) matchesOrganizationalUnits(clientCert *x509.Certificate, config *ParsedCert) bool { |
| // Default behavior (no OUs) is to allow all OUs |
| if len(config.Entry.AllowedOrganizationalUnits) == 0 { |
| return true |
| } |
| |
| // At least one pattern must match at least one name if any patterns are specified |
| for _, allowedOrganizationalUnits := range config.Entry.AllowedOrganizationalUnits { |
| for _, ou := range clientCert.Subject.OrganizationalUnit { |
| if glob.Glob(allowedOrganizationalUnits, ou) { |
| return true |
| } |
| } |
| } |
| |
| return false |
| } |
| |
| // matchesCertificateExtensions verifies that the certificate matches configured |
| // required extensions |
| func (b *backend) matchesCertificateExtensions(clientCert *x509.Certificate, config *ParsedCert) bool { |
| // If no required extensions, nothing to check here |
| if len(config.Entry.RequiredExtensions) == 0 { |
| return true |
| } |
| // Fail fast if we have required extensions but no extensions on the cert |
| if len(clientCert.Extensions) == 0 { |
| return false |
| } |
| |
| // Build Client Extensions Map for Constraint Matching |
| // x509 Writes Extensions in ASN1 with a bitstring tag, which results in the field |
| // including its ASN.1 type tag bytes. For the sake of simplicity, assume string type |
| // and drop the tag bytes. And get the number of bytes from the tag. |
| clientExtMap := make(map[string]string, len(clientCert.Extensions)) |
| for _, ext := range clientCert.Extensions { |
| var parsedValue string |
| asn1.Unmarshal(ext.Value, &parsedValue) |
| clientExtMap[ext.Id.String()] = parsedValue |
| } |
| // If any of the required extensions don'log match the constraint fails |
| for _, requiredExt := range config.Entry.RequiredExtensions { |
| reqExt := strings.SplitN(requiredExt, ":", 2) |
| clientExtValue, clientExtValueOk := clientExtMap[reqExt[0]] |
| if !clientExtValueOk || !glob.Glob(reqExt[1], clientExtValue) { |
| return false |
| } |
| } |
| return true |
| } |
| |
| // certificateExtensionsMetadata returns the metadata from configured |
| // metadata extensions |
| func (b *backend) certificateExtensionsMetadata(clientCert *x509.Certificate, config *ParsedCert) map[string]string { |
| // If no metadata extensions are configured, return an empty map |
| if len(config.Entry.AllowedMetadataExtensions) == 0 { |
| return map[string]string{} |
| } |
| |
| // Build a map with the accepted oid strings as keys, and the metadata keys as values. |
| allowedOidMap := make(map[string]string, len(config.Entry.AllowedMetadataExtensions)) |
| for _, oidString := range config.Entry.AllowedMetadataExtensions { |
| // Avoid dots in metadata keys and put dashes instead, |
| // to allow use policy templates. |
| allowedOidMap[oidString] = strings.ReplaceAll(oidString, ".", "-") |
| } |
| |
| // Collect the metadata from accepted certificate extensions. |
| metadata := make(map[string]string, len(config.Entry.AllowedMetadataExtensions)) |
| for _, ext := range clientCert.Extensions { |
| if metadataKey, ok := allowedOidMap[ext.Id.String()]; ok { |
| // x509 Writes Extensions in ASN1 with a bitstring tag, which results in the field |
| // including its ASN.1 type tag bytes. For the sake of simplicity, assume string type |
| // and drop the tag bytes. And get the number of bytes from the tag. |
| var parsedValue string |
| asn1.Unmarshal(ext.Value, &parsedValue) |
| metadata[metadataKey] = parsedValue |
| } |
| } |
| |
| return metadata |
| } |
| |
| // loadTrustedCerts is used to load all the trusted certificates from the backend |
| func (b *backend) loadTrustedCerts(ctx context.Context, storage logical.Storage, certName string) (pool *x509.CertPool, trusted []*ParsedCert, trustedNonCAs []*ParsedCert, conf *ocsp.VerifyConfig) { |
| pool = x509.NewCertPool() |
| trusted = make([]*ParsedCert, 0) |
| trustedNonCAs = make([]*ParsedCert, 0) |
| |
| var names []string |
| if certName != "" { |
| names = append(names, certName) |
| } else { |
| var err error |
| names, err = storage.List(ctx, "cert/") |
| if err != nil { |
| b.Logger().Error("failed to list trusted certs", "error", err) |
| return |
| } |
| } |
| |
| conf = &ocsp.VerifyConfig{} |
| for _, name := range names { |
| entry, err := b.Cert(ctx, storage, strings.TrimPrefix(name, "cert/")) |
| if err != nil { |
| b.Logger().Error("failed to load trusted cert", "name", name, "error", err) |
| continue |
| } |
| if entry == nil { |
| // This could happen when the certName was provided and the cert doesn'log exist, |
| // or just if between the LIST and the GET the cert was deleted. |
| continue |
| } |
| |
| parsed := parsePEM([]byte(entry.Certificate)) |
| if len(parsed) == 0 { |
| b.Logger().Error("failed to parse certificate", "name", name) |
| continue |
| } |
| parsed = append(parsed, parsePEM([]byte(entry.OcspCaCertificates))...) |
| |
| if !parsed[0].IsCA { |
| trustedNonCAs = append(trustedNonCAs, &ParsedCert{ |
| Entry: entry, |
| Certificates: parsed, |
| }) |
| } else { |
| for _, p := range parsed { |
| pool.AddCert(p) |
| } |
| |
| // Create a ParsedCert entry |
| trusted = append(trusted, &ParsedCert{ |
| Entry: entry, |
| Certificates: parsed, |
| }) |
| } |
| if entry.OcspEnabled { |
| conf.OcspEnabled = true |
| conf.OcspServersOverride = append(conf.OcspServersOverride, entry.OcspServersOverride...) |
| if entry.OcspFailOpen { |
| conf.OcspFailureMode = ocsp.FailOpenTrue |
| } else { |
| conf.OcspFailureMode = ocsp.FailOpenFalse |
| } |
| conf.QueryAllServers = conf.QueryAllServers || entry.OcspQueryAllServers |
| } |
| } |
| return |
| } |
| |
| func (b *backend) checkForCertInOCSP(ctx context.Context, clientCert *x509.Certificate, chain []*x509.Certificate, conf *ocsp.VerifyConfig) (bool, error) { |
| if !conf.OcspEnabled || len(chain) < 2 { |
| return true, nil |
| } |
| b.ocspClientMutex.RLock() |
| defer b.ocspClientMutex.RUnlock() |
| err := b.ocspClient.VerifyLeafCertificate(ctx, clientCert, chain[1], conf) |
| if err != nil { |
| // We want to preserve error messages when they have additional, |
| // potentially useful information. Just having a revoked cert |
| // isn't additionally useful. |
| if !strings.Contains(err.Error(), "has been revoked") { |
| return false, err |
| } |
| return false, nil |
| } |
| return true, nil |
| } |
| |
| func (b *backend) checkForChainInCRLs(chain []*x509.Certificate) bool { |
| badChain := false |
| for _, cert := range chain { |
| badCRLs := b.findSerialInCRLs(cert.SerialNumber) |
| if len(badCRLs) != 0 { |
| badChain = true |
| break |
| } |
| |
| } |
| return badChain |
| } |
| |
| func (b *backend) checkForValidChain(chains [][]*x509.Certificate) bool { |
| for _, chain := range chains { |
| if !b.checkForChainInCRLs(chain) { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // parsePEM parses a PEM encoded x509 certificate |
| func parsePEM(raw []byte) (certs []*x509.Certificate) { |
| for len(raw) > 0 { |
| var block *pem.Block |
| block, raw = pem.Decode(raw) |
| if block == nil { |
| break |
| } |
| if (block.Type != "CERTIFICATE" && block.Type != "TRUSTED CERTIFICATE") || len(block.Headers) != 0 { |
| continue |
| } |
| |
| cert, err := x509.ParseCertificate(block.Bytes) |
| if err != nil { |
| continue |
| } |
| certs = append(certs, cert) |
| } |
| return |
| } |
| |
| // validateConnState is used to validate that the TLS client is authorized |
| // by at trusted certificate. Most of this logic is lifted from the client |
| // verification logic here: http://golang.org/src/crypto/tls/handshake_server.go |
| // The trusted chains are returned. |
| func validateConnState(roots *x509.CertPool, cs *tls.ConnectionState) ([][]*x509.Certificate, error) { |
| certs := cs.PeerCertificates |
| if len(certs) == 0 { |
| return nil, nil |
| } |
| |
| opts := x509.VerifyOptions{ |
| Roots: roots, |
| Intermediates: x509.NewCertPool(), |
| KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, |
| } |
| |
| if len(certs) > 1 { |
| for _, cert := range certs[1:] { |
| opts.Intermediates.AddCert(cert) |
| } |
| } |
| |
| chains, err := certs[0].Verify(opts) |
| if err != nil { |
| if _, ok := err.(x509.UnknownAuthorityError); ok { |
| return nil, nil |
| } |
| return nil, errors.New("failed to verify client's certificate: " + err.Error()) |
| } |
| |
| return chains, nil |
| } |