Skip to content

Commit

Permalink
backport of commit 8cc7be2 (#21293)
Browse files Browse the repository at this point in the history
Co-authored-by: Matt Schultz <975680+schultz-is@users.noreply.github.com>
  • Loading branch information
1 parent 9e85fef commit 2fd24b1
Show file tree
Hide file tree
Showing 8 changed files with 442 additions and 23 deletions.
39 changes: 34 additions & 5 deletions builtin/logical/pki/acme_challenges.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"context"
"crypto/sha256"
"crypto/subtle"
"crypto/tls"
"crypto/x509"
"encoding/asn1"
Expand Down Expand Up @@ -55,7 +56,7 @@ func ValidateKeyAuthorization(keyAuthz string, token string, thumbprint string)
// challenge matches our expectation, returning (true, nil) if so, or
// (false, err) if not.
//
// This is for use with DNS challenges, which require
// This is for use with DNS challenges, which require base64 encoding.
func ValidateSHA256KeyAuthorization(keyAuthz string, token string, thumbprint string) (bool, error) {
authzContents := token + "." + thumbprint
checksum := sha256.Sum256([]byte(authzContents))
Expand All @@ -68,6 +69,22 @@ func ValidateSHA256KeyAuthorization(keyAuthz string, token string, thumbprint st
return true, nil
}

// ValidateRawSHA256KeyAuthorization validates that the given keyAuthz from a
// challenge matches our expectation, returning (true, nil) if so, or
// (false, err) if not.
//
// This is for use with TLS challenges, which require the raw hash output.
func ValidateRawSHA256KeyAuthorization(keyAuthz []byte, token string, thumbprint string) (bool, error) {
authzContents := token + "." + thumbprint
expectedAuthz := sha256.Sum256([]byte(authzContents))

if len(keyAuthz) != len(expectedAuthz) || subtle.ConstantTimeCompare(expectedAuthz[:], keyAuthz) != 1 {
return false, fmt.Errorf("sha256 key authorization was invalid")
}

return true, nil
}

func buildResolver(config *acmeConfigEntry) (*net.Resolver, error) {
if len(config.DNSResolver) == 0 {
return net.DefaultResolver, nil
Expand Down Expand Up @@ -286,9 +303,13 @@ func ValidateTLSALPN01Challenge(domain string, token string, thumbprint string,
// Verify that this is a self-signed certificate that isn't signed
// by another certificate (i.e., with the same key material but
// different issuer).
if err := cert.CheckSignatureFrom(cert); err != nil {
return fmt.Errorf("server under test returned a non-self-signed certificate: %w", err)
// NOTE: Do not use cert.CheckSignatureFrom(cert) as we need to bypass the
// checks for the parent certificate having the IsCA basic constraint set.
err := cert.CheckSignature(cert.SignatureAlgorithm, cert.RawTBSCertificate, cert.Signature)
if err != nil {
return fmt.Errorf("server under test returned a non-self-signed certificate: %v", err)
}

if !bytes.Equal(cert.RawSubject, cert.RawIssuer) {
return fmt.Errorf("server under test returned a non-self-signed certificate: invalid subject (%v) <-> issuer (%v) match", cert.Subject.String(), cert.Issuer.String())
}
Expand Down Expand Up @@ -339,8 +360,16 @@ func ValidateTLSALPN01Challenge(domain string, token string, thumbprint string,
return fmt.Errorf("server under test returned a certificate with an acmeIdentifier extension marked non-Critical")
}

keyAuthz := string(ext.Value)
ok, err := ValidateSHA256KeyAuthorization(keyAuthz, token, thumbprint)
var keyAuthz []byte
remainder, err := asn1.Unmarshal(ext.Value, &keyAuthz)
if err != nil {
return fmt.Errorf("server under test returned a certificate with invalid acmeIdentifier extension value: %w", err)
}
if len(remainder) > 0 {
return fmt.Errorf("server under test returned a certificate with invalid acmeIdentifier extension value with additional trailing data")
}

ok, err := ValidateRawSHA256KeyAuthorization(keyAuthz, token, thumbprint)
if !ok || err != nil {
return fmt.Errorf("server under test returned a certificate with an invalid key authorization (%w)", err)
}
Expand Down
43 changes: 25 additions & 18 deletions builtin/logical/pki/acme_challenges_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/base64"
"fmt"
"math/big"
Expand Down Expand Up @@ -308,7 +309,8 @@ func TestAcmeValidateTLSALPN01Challenge(t *testing.T) {
t.Logf("using keyAuthorizationTestCase [tc=%d] as alpnTestCase [tc=%d]...", index, len(alpnTestCases))
// Properly encode the authorization.
checksum := sha256.Sum256([]byte(tc.keyAuthz))
authz := base64.RawURLEncoding.EncodeToString(checksum[:])
authz, err := asn1.Marshal(checksum[:])
require.NoError(t, err, "failed asn.1 marshalling authz")

// Build a self-signed certificate.
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
Expand All @@ -329,11 +331,11 @@ func TestAcmeValidateTLSALPN01Challenge(t *testing.T) {
{
Id: OIDACMEIdentifier,
Critical: true,
Value: []byte(authz),
Value: authz,
},
},
BasicConstraintsValid: true,
IsCA: true,
IsCA: false,
}
certBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
require.NoError(t, err, "failed to create certificate")
Expand Down Expand Up @@ -378,7 +380,8 @@ func TestAcmeValidateTLSALPN01Challenge(t *testing.T) {

// Compute our authorization.
checksum := sha256.Sum256([]byte("valid.valid"))
authz := base64.RawURLEncoding.EncodeToString(checksum[:])
authz, err := asn1.Marshal(checksum[:])
require.NoError(t, err, "failed to marshal authz with asn.1 ")

// Build a leaf certificate which _could_ pass validation
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
Expand All @@ -399,11 +402,11 @@ func TestAcmeValidateTLSALPN01Challenge(t *testing.T) {
{
Id: OIDACMEIdentifier,
Critical: true,
Value: []byte(authz),
Value: authz,
},
},
BasicConstraintsValid: true,
IsCA: true,
IsCA: false,
}
certBytes, err := x509.CreateCertificate(rand.Reader, tmpl, rootCert, key.Public(), rootKey)
require.NoError(t, err, "failed to create leaf certificate")
Expand All @@ -426,7 +429,8 @@ func TestAcmeValidateTLSALPN01Challenge(t *testing.T) {
// Test case: cert without DNSSan
// Compute our authorization.
checksum := sha256.Sum256([]byte("valid.valid"))
authz := base64.RawURLEncoding.EncodeToString(checksum[:])
authz, err := asn1.Marshal(checksum[:])
require.NoError(t, err, "failed to marshal authz with asn.1 ")

// Build a leaf certificate without a DNSSan
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
Expand All @@ -447,11 +451,11 @@ func TestAcmeValidateTLSALPN01Challenge(t *testing.T) {
{
Id: OIDACMEIdentifier,
Critical: true,
Value: []byte(authz),
Value: authz,
},
},
BasicConstraintsValid: true,
IsCA: true,
IsCA: false,
}
certBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
require.NoError(t, err, "failed to create leaf certificate")
Expand All @@ -474,7 +478,8 @@ func TestAcmeValidateTLSALPN01Challenge(t *testing.T) {
// Test case: cert without matching DNSSan
// Compute our authorization.
checksum := sha256.Sum256([]byte("valid.valid"))
authz := base64.RawURLEncoding.EncodeToString(checksum[:])
authz, err := asn1.Marshal(checksum[:])
require.NoError(t, err, "failed to marshal authz with asn.1 ")

// Build a leaf certificate which fails validation due to bad DNSName
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
Expand All @@ -495,11 +500,11 @@ func TestAcmeValidateTLSALPN01Challenge(t *testing.T) {
{
Id: OIDACMEIdentifier,
Critical: true,
Value: []byte(authz),
Value: authz,
},
},
BasicConstraintsValid: true,
IsCA: true,
IsCA: false,
}
certBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
require.NoError(t, err, "failed to create leaf certificate")
Expand All @@ -522,7 +527,8 @@ func TestAcmeValidateTLSALPN01Challenge(t *testing.T) {
// Test case: cert with additional SAN
// Compute our authorization.
checksum := sha256.Sum256([]byte("valid.valid"))
authz := base64.RawURLEncoding.EncodeToString(checksum[:])
authz, err := asn1.Marshal(checksum[:])
require.NoError(t, err, "failed to marshal authz with asn.1 ")

// Build a leaf certificate which has an invalid additional SAN
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
Expand All @@ -544,11 +550,11 @@ func TestAcmeValidateTLSALPN01Challenge(t *testing.T) {
{
Id: OIDACMEIdentifier,
Critical: true,
Value: []byte(authz),
Value: authz,
},
},
BasicConstraintsValid: true,
IsCA: true,
IsCA: false,
}
certBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
require.NoError(t, err, "failed to create leaf certificate")
Expand All @@ -571,7 +577,8 @@ func TestAcmeValidateTLSALPN01Challenge(t *testing.T) {
// Test case: cert without CN
// Compute our authorization.
checksum := sha256.Sum256([]byte("valid.valid"))
authz := base64.RawURLEncoding.EncodeToString(checksum[:])
authz, err := asn1.Marshal(checksum[:])
require.NoError(t, err, "failed to marshal authz with asn.1 ")

// Build a leaf certificate which should pass validation
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
Expand All @@ -588,11 +595,11 @@ func TestAcmeValidateTLSALPN01Challenge(t *testing.T) {
{
Id: OIDACMEIdentifier,
Critical: true,
Value: []byte(authz),
Value: authz,
},
},
BasicConstraintsValid: true,
IsCA: true,
IsCA: false,
}
certBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
require.NoError(t, err, "failed to create leaf certificate")
Expand Down
Loading

0 comments on commit 2fd24b1

Please sign in to comment.