Skip to content

Commit

Permalink
Gracefully handle CSRs without CNs (hashicorp#20982)
Browse files Browse the repository at this point in the history
* Allow not specifying CN on CSR

Signed-off-by: Alexander Scheel <[email protected]>

* Add test case validating behavior

Signed-off-by: Alexander Scheel <[email protected]>

* Add notice about failure to validate

Signed-off-by: Alexander Scheel <[email protected]>

---------

Signed-off-by: Alexander Scheel <[email protected]>
  • Loading branch information
cipherboy authored Jun 7, 2023
1 parent 9f87bcf commit 08c1efa
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 3 deletions.
6 changes: 4 additions & 2 deletions builtin/logical/pki/acme_challenge_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ var MaxChallengeTimeout = 1 * time.Minute

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 {
// Account KID that this validation attempt is recorded under.
Account string `json:"account"`
Expand Down Expand Up @@ -401,7 +403,7 @@ func (ace *ACMEChallengeEngine) _verifyChallenge(sc *storageContext, id string,

valid, err = ValidateHTTP01Challenge(authz.Identifier.Value, cv.Token, cv.Thumbprint, config)
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)
}
case ACMEDNSChallenge:
Expand All @@ -412,7 +414,7 @@ func (ace *ACMEChallengeEngine) _verifyChallenge(sc *storageContext, id string,

valid, err = ValidateDNS01Challenge(authz.Identifier.Value, cv.Token, cv.Thumbprint, config)
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)
}
default:
Expand Down
34 changes: 34 additions & 0 deletions builtin/logical/pki/path_acme_order.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"net"
"net/http"
"sort"
"strings"
"time"

"github.com/hashicorp/vault/sdk/helper/strutil"
Expand Down Expand Up @@ -480,6 +481,34 @@ func storeCertificate(sc *storageContext, signedCertBundle *certutil.ParsedCertB
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) {
pemBlock := &pem.Block{
Type: "CERTIFICATE REQUEST",
Expand All @@ -495,6 +524,11 @@ func issueCertFromCsr(ac *acmeContext, csr *x509.CertificateRequest) (*certutil.
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)
if err != nil {
return nil, "", fmt.Errorf("failed loading CA %s: %w", ac.issuer.ID.String(), err)
Expand Down
2 changes: 1 addition & 1 deletion builtin/logical/pki/path_acme_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -784,7 +784,7 @@ func TestAcmeIgnoresRoleExtKeyUsage(t *testing.T) {
"allowed_domains": "localdomain",
"allow_subdomains": "true",
"allow_wildcard_certificates": "true",
"require_cn": "false",
"require_cn": "true", /* explicit default */
"server_flag": "true",
"client_flag": "true",
"code_signing_flag": "true",
Expand Down

0 comments on commit 08c1efa

Please sign in to comment.