mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 18:48:08 +00:00 
			
		
		
		
	Return OCSP errors on cert auth login failures (#20234)
* Return OCSP errors on cert auth login failures Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Switch to immediately returning the first match Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add changelog entry Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> --------- Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
This commit is contained in:
		| @@ -15,14 +15,15 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"github.com/hashicorp/vault/sdk/helper/ocsp" |  | ||||||
|  |  | ||||||
| 	"github.com/hashicorp/vault/sdk/framework" | 	"github.com/hashicorp/vault/sdk/framework" | ||||||
| 	"github.com/hashicorp/vault/sdk/helper/certutil" | 	"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/helper/policyutil" | ||||||
| 	"github.com/hashicorp/vault/sdk/logical" | 	"github.com/hashicorp/vault/sdk/logical" | ||||||
|  |  | ||||||
| 	"github.com/hashicorp/vault/sdk/helper/cidrutil" | 	"github.com/hashicorp/errwrap" | ||||||
|  | 	"github.com/hashicorp/go-multierror" | ||||||
| 	glob "github.com/ryanuber/go-glob" | 	glob "github.com/ryanuber/go-glob" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -271,6 +272,7 @@ func (b *backend) verifyCredentials(ctx context.Context, req *logical.Request, d | |||||||
|  |  | ||||||
| 	// If trustedNonCAs is not empty it means that client had registered a non-CA cert | 	// If trustedNonCAs is not empty it means that client had registered a non-CA cert | ||||||
| 	// with the backend. | 	// with the backend. | ||||||
|  | 	var retErr error | ||||||
| 	if len(trustedNonCAs) != 0 { | 	if len(trustedNonCAs) != 0 { | ||||||
| 		for _, trustedNonCA := range trustedNonCAs { | 		for _, trustedNonCA := range trustedNonCAs { | ||||||
| 			tCert := trustedNonCA.Certificates[0] | 			tCert := trustedNonCA.Certificates[0] | ||||||
| @@ -278,9 +280,19 @@ func (b *backend) verifyCredentials(ctx context.Context, req *logical.Request, d | |||||||
| 			if tCert.SerialNumber.Cmp(clientCert.SerialNumber) == 0 && | 			if tCert.SerialNumber.Cmp(clientCert.SerialNumber) == 0 && | ||||||
| 				bytes.Equal(tCert.AuthorityKeyId, clientCert.AuthorityKeyId) { | 				bytes.Equal(tCert.AuthorityKeyId, clientCert.AuthorityKeyId) { | ||||||
| 				matches, err := b.matchesConstraints(ctx, clientCert, trustedNonCA.Certificates, trustedNonCA, verifyConf) | 				matches, err := b.matchesConstraints(ctx, clientCert, trustedNonCA.Certificates, trustedNonCA, verifyConf) | ||||||
| 				if err != nil { |  | ||||||
| 					return nil, nil, err | 				// 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 { | 				if matches { | ||||||
| 					return trustedNonCA, nil, nil | 					return trustedNonCA, nil, nil | ||||||
| 				} | 				} | ||||||
| @@ -291,23 +303,36 @@ func (b *backend) verifyCredentials(ctx context.Context, req *logical.Request, d | |||||||
| 	// If no trusted chain was found, client is not authenticated | 	// If no trusted chain was found, client is not authenticated | ||||||
| 	// This check happens after checking for a matching configured non-CA certs | 	// This check happens after checking for a matching configured non-CA certs | ||||||
| 	if len(trustedChains) == 0 { | 	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 | 		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 | 	// Search for a ParsedCert that intersects with the validated chains and any additional constraints | ||||||
| 	matches := make([]*ParsedCert, 0) |  | ||||||
| 	for _, trust := range trusted { // For each ParsedCert in the config | 	for _, trust := range trusted { // For each ParsedCert in the config | ||||||
| 		for _, tCert := range trust.Certificates { // For each certificate in the entry | 		for _, tCert := range trust.Certificates { // For each certificate in the entry | ||||||
| 			for _, chain := range trustedChains { // For each root chain that we matched | 			for _, chain := range trustedChains { // For each root chain that we matched | ||||||
| 				for _, cCert := range chain { // For each cert in the matched chain | 				for _, cCert := range chain { // For each cert in the matched chain | ||||||
| 					if tCert.Equal(cCert) { // ParsedCert intersects with 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 | 						match, err := b.matchesConstraints(ctx, clientCert, chain, trust, verifyConf) // validate client cert + matched chain against the config | ||||||
| 						if err != nil { |  | ||||||
| 							return nil, nil, err | 						// See note above. | ||||||
|  | 						if err != nil && (retErr == nil || !errwrap.Contains(retErr, err.Error())) { | ||||||
|  | 							retErr = multierror.Append(retErr, err) | ||||||
| 						} | 						} | ||||||
| 						if match { |  | ||||||
| 							// Add the match to the list | 						// Return the first matching entry (for backwards | ||||||
| 							matches = append(matches, trust) | 						// 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 | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| @@ -315,15 +340,13 @@ func (b *backend) verifyCredentials(ctx context.Context, req *logical.Request, d | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Fail on no matches | 	if retErr != nil { | ||||||
| 	if len(matches) == 0 { | 		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 | 	return nil, logical.ErrorResponse("no chain matching all constraints could be found for this login certificate"), nil | ||||||
| } | } | ||||||
|  |  | ||||||
| 	// Return the first matching entry (for backwards compatibility, we continue to just pick one if multiple match) |  | ||||||
| 	return matches[0], nil, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (b *backend) matchesConstraints(ctx context.Context, clientCert *x509.Certificate, trustedChain []*x509.Certificate, | func (b *backend) matchesConstraints(ctx context.Context, clientCert *x509.Certificate, trustedChain []*x509.Certificate, | ||||||
| 	config *ParsedCert, conf *ocsp.VerifyConfig, | 	config *ParsedCert, conf *ocsp.VerifyConfig, | ||||||
| ) (bool, error) { | ) (bool, error) { | ||||||
| @@ -608,6 +631,12 @@ func (b *backend) checkForCertInOCSP(ctx context.Context, clientCert *x509.Certi | |||||||
| 	defer b.ocspClientMutex.RUnlock() | 	defer b.ocspClientMutex.RUnlock() | ||||||
| 	err := b.ocspClient.VerifyLeafCertificate(ctx, clientCert, chain[1], conf) | 	err := b.ocspClient.VerifyLeafCertificate(ctx, clientCert, chain[1], conf) | ||||||
| 	if err != nil { | 	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 false, nil | ||||||
| 	} | 	} | ||||||
| 	return true, nil | 	return true, nil | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								changelog/20234.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								changelog/20234.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | ```release-note:improvement | ||||||
|  | auth/cert: Better return OCSP validation errors during login to the caller. | ||||||
|  | ``` | ||||||
		Reference in New Issue
	
	Block a user
	 Alexander Scheel
					Alexander Scheel