mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 18:17:55 +00:00 
			
		
		
		
	Gracefully handle CSRs without CNs (#20982)
* Allow not specifying CN on CSR Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add test case validating behavior Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add notice about failure to validate Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> --------- Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
This commit is contained in:
		| @@ -14,6 +14,8 @@ var MaxChallengeTimeout = 1 * time.Minute | |||||||
|  |  | ||||||
| const MaxRetryAttempts = 5 | const MaxRetryAttempts = 5 | ||||||
|  |  | ||||||
|  | const ChallengeAttemptFailedMsg = "this may occur if the validation target was misconfigured: check that challenge responses are available at the required locations and retry." | ||||||
|  |  | ||||||
| type ChallengeValidation struct { | type ChallengeValidation struct { | ||||||
| 	// Account KID that this validation attempt is recorded under. | 	// Account KID that this validation attempt is recorded under. | ||||||
| 	Account string `json:"account"` | 	Account string `json:"account"` | ||||||
| @@ -401,7 +403,7 @@ func (ace *ACMEChallengeEngine) _verifyChallenge(sc *storageContext, id string, | |||||||
|  |  | ||||||
| 		valid, err = ValidateHTTP01Challenge(authz.Identifier.Value, cv.Token, cv.Thumbprint, config) | 		valid, err = ValidateHTTP01Challenge(authz.Identifier.Value, cv.Token, cv.Thumbprint, config) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			err = fmt.Errorf("%w: error validating http-01 challenge %v: %s", ErrIncorrectResponse, id, err.Error()) | 			err = fmt.Errorf("%w: error validating http-01 challenge %v: %v; %v", ErrIncorrectResponse, id, err, ChallengeAttemptFailedMsg) | ||||||
| 			return ace._verifyChallengeRetry(sc, cv, authzPath, authz, challenge, err, id) | 			return ace._verifyChallengeRetry(sc, cv, authzPath, authz, challenge, err, id) | ||||||
| 		} | 		} | ||||||
| 	case ACMEDNSChallenge: | 	case ACMEDNSChallenge: | ||||||
| @@ -412,7 +414,7 @@ func (ace *ACMEChallengeEngine) _verifyChallenge(sc *storageContext, id string, | |||||||
|  |  | ||||||
| 		valid, err = ValidateDNS01Challenge(authz.Identifier.Value, cv.Token, cv.Thumbprint, config) | 		valid, err = ValidateDNS01Challenge(authz.Identifier.Value, cv.Token, cv.Thumbprint, config) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			err = fmt.Errorf("%w: error validating dns-01 challenge %v: %s", ErrIncorrectResponse, id, err.Error()) | 			err = fmt.Errorf("%w: error validating dns-01 challenge %v: %v; %v", ErrIncorrectResponse, id, err, ChallengeAttemptFailedMsg) | ||||||
| 			return ace._verifyChallengeRetry(sc, cv, authzPath, authz, challenge, err, id) | 			return ace._verifyChallengeRetry(sc, cv, authzPath, authz, challenge, err, id) | ||||||
| 		} | 		} | ||||||
| 	default: | 	default: | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ import ( | |||||||
| 	"net" | 	"net" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"sort" | 	"sort" | ||||||
|  | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/hashicorp/vault/sdk/helper/strutil" | 	"github.com/hashicorp/vault/sdk/helper/strutil" | ||||||
| @@ -480,6 +481,34 @@ func storeCertificate(sc *storageContext, signedCertBundle *certutil.ParsedCertB | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func maybeAugmentReqDataWithSuitableCN(ac *acmeContext, csr *x509.CertificateRequest, data *framework.FieldData) { | ||||||
|  | 	// Role doesn't require a CN, so we don't care. | ||||||
|  | 	if !ac.role.RequireCN { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// CSR contains a CN, so use that one. | ||||||
|  | 	if csr.Subject.CommonName != "" { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Choose a CN in the order wildcard -> DNS -> IP -> fail. | ||||||
|  | 	for _, name := range csr.DNSNames { | ||||||
|  | 		if strings.Contains(name, "*") { | ||||||
|  | 			data.Raw["common_name"] = name | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if len(csr.DNSNames) > 0 { | ||||||
|  | 		data.Raw["common_name"] = csr.DNSNames[0] | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if len(csr.IPAddresses) > 0 { | ||||||
|  | 		data.Raw["common_name"] = csr.IPAddresses[0].String() | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func issueCertFromCsr(ac *acmeContext, csr *x509.CertificateRequest) (*certutil.ParsedCertBundle, issuerID, error) { | func issueCertFromCsr(ac *acmeContext, csr *x509.CertificateRequest) (*certutil.ParsedCertBundle, issuerID, error) { | ||||||
| 	pemBlock := &pem.Block{ | 	pemBlock := &pem.Block{ | ||||||
| 		Type:    "CERTIFICATE REQUEST", | 		Type:    "CERTIFICATE REQUEST", | ||||||
| @@ -495,6 +524,11 @@ func issueCertFromCsr(ac *acmeContext, csr *x509.CertificateRequest) (*certutil. | |||||||
| 		Schema: getCsrSignVerbatimSchemaFields(), | 		Schema: getCsrSignVerbatimSchemaFields(), | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// XXX: Usability hack: by default, minimalist roles have require_cn=true, | ||||||
|  | 	// but some ACME clients do not provision one in the certificate as modern | ||||||
|  | 	// (TLS) clients are mostly verifying against server's DNS SANs. | ||||||
|  | 	maybeAugmentReqDataWithSuitableCN(ac, csr, data) | ||||||
|  |  | ||||||
| 	signingBundle, issuerId, err := ac.sc.fetchCAInfoWithIssuer(ac.issuer.ID.String(), IssuanceUsage) | 	signingBundle, issuerId, err := ac.sc.fetchCAInfoWithIssuer(ac.issuer.ID.String(), IssuanceUsage) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, "", fmt.Errorf("failed loading CA %s: %w", ac.issuer.ID.String(), err) | 		return nil, "", fmt.Errorf("failed loading CA %s: %w", ac.issuer.ID.String(), err) | ||||||
|   | |||||||
| @@ -784,7 +784,7 @@ func TestAcmeIgnoresRoleExtKeyUsage(t *testing.T) { | |||||||
| 		"allowed_domains":             "localdomain", | 		"allowed_domains":             "localdomain", | ||||||
| 		"allow_subdomains":            "true", | 		"allow_subdomains":            "true", | ||||||
| 		"allow_wildcard_certificates": "true", | 		"allow_wildcard_certificates": "true", | ||||||
| 		"require_cn":                  "false", | 		"require_cn":                  "true", /* explicit default */ | ||||||
| 		"server_flag":                 "true", | 		"server_flag":                 "true", | ||||||
| 		"client_flag":                 "true", | 		"client_flag":                 "true", | ||||||
| 		"code_signing_flag":           "true", | 		"code_signing_flag":           "true", | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Alexander Scheel
					Alexander Scheel