mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-02 03:27:54 +00:00
* Fix ACME tidy to not reference acmeCtx acmeContext is useful for when we need to reference things with a ACME base URL, but everything used in tidy doesn't need this URL as it is not coming from an ACME request. Refactor tidy to remove references to acmeContext, including dependent functions in acme_state.go. Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Remove spurious log message Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Draft Tidy Acme Test with Backdate Storage + Backdate Sysxsx * Fixes to ACME tidy testing Co-authored-by: kitography <khaines@mit.edu> Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Correctly set account kid to update account status Co-authored-by: kitography <khaines@mit.edu> Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add TestTidyAcmeWithSafetyBuffer Co-authored-by: kitography <khaines@mit.edu> Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add test for disabling tidy operation Co-authored-by: kitography <khaines@mit.edu> Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add acme_account_safety_buffer to auto-tidy config Resolve: #21872 Co-authored-by: kitography <khaines@mit.edu> Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add tests verifying tidy safety buffers Co-authored-by: kitography <khaines@mit.edu> Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add changelog entry Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> * Add account status validations and order cleanup tests --------- Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> Co-authored-by: kitography <khaines@mit.edu> Co-authored-by: Steve Clark <steven.clark@hashicorp.com>
713 lines
22 KiB
Go
713 lines
22 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package pki
|
|
|
|
import (
|
|
"context"
|
|
"crypto"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"math/big"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/builtin/logical/pki/dnstest"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type keyAuthorizationTestCase struct {
|
|
keyAuthz string
|
|
token string
|
|
thumbprint string
|
|
shouldFail bool
|
|
}
|
|
|
|
var keyAuthorizationTestCases = []keyAuthorizationTestCase{
|
|
{
|
|
// Entirely empty
|
|
"",
|
|
"non-empty-token",
|
|
"non-empty-thumbprint",
|
|
true,
|
|
},
|
|
{
|
|
// Both empty
|
|
".",
|
|
"non-empty-token",
|
|
"non-empty-thumbprint",
|
|
true,
|
|
},
|
|
{
|
|
// Not equal
|
|
"non-.non-",
|
|
"non-empty-token",
|
|
"non-empty-thumbprint",
|
|
true,
|
|
},
|
|
{
|
|
// Empty thumbprint
|
|
"non-.",
|
|
"non-empty-token",
|
|
"non-empty-thumbprint",
|
|
true,
|
|
},
|
|
{
|
|
// Empty token
|
|
".non-",
|
|
"non-empty-token",
|
|
"non-empty-thumbprint",
|
|
true,
|
|
},
|
|
{
|
|
// Wrong order
|
|
"non-empty-thumbprint.non-empty-token",
|
|
"non-empty-token",
|
|
"non-empty-thumbprint",
|
|
true,
|
|
},
|
|
{
|
|
// Too many pieces
|
|
"one.two.three",
|
|
"non-empty-token",
|
|
"non-empty-thumbprint",
|
|
true,
|
|
},
|
|
{
|
|
// Valid
|
|
"non-empty-token.non-empty-thumbprint",
|
|
"non-empty-token",
|
|
"non-empty-thumbprint",
|
|
false,
|
|
},
|
|
}
|
|
|
|
func TestAcmeValidateKeyAuthorization(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for index, tc := range keyAuthorizationTestCases {
|
|
isValid, err := ValidateKeyAuthorization(tc.keyAuthz, tc.token, tc.thumbprint)
|
|
if !isValid && err == nil {
|
|
t.Fatalf("[%d] expected failure to give reason via err (%v / %v)", index, isValid, err)
|
|
}
|
|
|
|
expectedValid := !tc.shouldFail
|
|
if expectedValid != isValid {
|
|
t.Fatalf("[%d] got ret=%v, expected ret=%v (shouldFail=%v)", index, isValid, expectedValid, tc.shouldFail)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAcmeValidateHTTP01Challenge(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for index, tc := range keyAuthorizationTestCases {
|
|
validFunc := func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte(tc.keyAuthz))
|
|
}
|
|
withPadding := func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte(" " + tc.keyAuthz + " "))
|
|
}
|
|
withRedirect := func(w http.ResponseWriter, r *http.Request) {
|
|
if strings.Contains(r.URL.Path, "/.well-known/") {
|
|
http.Redirect(w, r, "/my-http-01-challenge-response", 301)
|
|
return
|
|
}
|
|
|
|
w.Write([]byte(tc.keyAuthz))
|
|
}
|
|
withSleep := func(w http.ResponseWriter, r *http.Request) {
|
|
// Long enough to ensure any excessively short timeouts are hit,
|
|
// not long enough to trigger a failure (hopefully).
|
|
time.Sleep(5 * time.Second)
|
|
w.Write([]byte(tc.keyAuthz))
|
|
}
|
|
|
|
validHandlers := []http.HandlerFunc{
|
|
http.HandlerFunc(validFunc), http.HandlerFunc(withPadding),
|
|
http.HandlerFunc(withRedirect), http.HandlerFunc(withSleep),
|
|
}
|
|
|
|
for handlerIndex, handler := range validHandlers {
|
|
func() {
|
|
ts := httptest.NewServer(handler)
|
|
defer ts.Close()
|
|
|
|
host := ts.URL[7:]
|
|
isValid, err := ValidateHTTP01Challenge(host, tc.token, tc.thumbprint, &acmeConfigEntry{})
|
|
if !isValid && err == nil {
|
|
t.Fatalf("[tc=%d/handler=%d] expected failure to give reason via err (%v / %v)", index, handlerIndex, isValid, err)
|
|
}
|
|
|
|
expectedValid := !tc.shouldFail
|
|
if expectedValid != isValid {
|
|
t.Fatalf("[tc=%d/handler=%d] got ret=%v (err=%v), expected ret=%v (shouldFail=%v)", index, handlerIndex, isValid, err, expectedValid, tc.shouldFail)
|
|
}
|
|
}()
|
|
}
|
|
}
|
|
|
|
// Negative test cases for various HTTP-specific scenarios.
|
|
redirectLoop := func(w http.ResponseWriter, r *http.Request) {
|
|
http.Redirect(w, r, "/my-http-01-challenge-response", 301)
|
|
}
|
|
publicRedirect := func(w http.ResponseWriter, r *http.Request) {
|
|
http.Redirect(w, r, "http://hashicorp.com/", 301)
|
|
}
|
|
noData := func(w http.ResponseWriter, r *http.Request) {}
|
|
noContent := func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
notFound := func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}
|
|
simulateHang := func(w http.ResponseWriter, r *http.Request) {
|
|
time.Sleep(30 * time.Second)
|
|
w.Write([]byte("my-token.my-thumbprint"))
|
|
}
|
|
tooLarge := func(w http.ResponseWriter, r *http.Request) {
|
|
for i := 0; i < 512; i++ {
|
|
w.Write([]byte("my-token.my-thumbprint\n"))
|
|
}
|
|
}
|
|
|
|
validHandlers := []http.HandlerFunc{
|
|
http.HandlerFunc(redirectLoop), http.HandlerFunc(publicRedirect),
|
|
http.HandlerFunc(noData), http.HandlerFunc(noContent),
|
|
http.HandlerFunc(notFound), http.HandlerFunc(simulateHang),
|
|
http.HandlerFunc(tooLarge),
|
|
}
|
|
for handlerIndex, handler := range validHandlers {
|
|
func() {
|
|
ts := httptest.NewServer(handler)
|
|
defer ts.Close()
|
|
|
|
host := ts.URL[7:]
|
|
isValid, err := ValidateHTTP01Challenge(host, "my-token", "my-thumbprint", &acmeConfigEntry{})
|
|
if isValid || err == nil {
|
|
t.Fatalf("[handler=%d] expected failure validating challenge (%v / %v)", handlerIndex, isValid, err)
|
|
}
|
|
}()
|
|
}
|
|
}
|
|
|
|
func TestAcmeValidateDNS01Challenge(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
host := "dadgarcorp.com"
|
|
resolver := dnstest.SetupResolver(t, host)
|
|
defer resolver.Cleanup()
|
|
|
|
t.Logf("DNS Server Address: %v", resolver.GetLocalAddr())
|
|
|
|
config := &acmeConfigEntry{
|
|
DNSResolver: resolver.GetLocalAddr(),
|
|
}
|
|
|
|
for index, tc := range keyAuthorizationTestCases {
|
|
checksum := sha256.Sum256([]byte(tc.keyAuthz))
|
|
authz := base64.RawURLEncoding.EncodeToString(checksum[:])
|
|
resolver.AddRecord(DNSChallengePrefix+host, "TXT", authz)
|
|
resolver.PushConfig()
|
|
|
|
isValid, err := ValidateDNS01Challenge(host, tc.token, tc.thumbprint, config)
|
|
if !isValid && err == nil {
|
|
t.Fatalf("[tc=%d] expected failure to give reason via err (%v / %v)", index, isValid, err)
|
|
}
|
|
|
|
expectedValid := !tc.shouldFail
|
|
if expectedValid != isValid {
|
|
t.Fatalf("[tc=%d] got ret=%v (err=%v), expected ret=%v (shouldFail=%v)", index, isValid, err, expectedValid, tc.shouldFail)
|
|
}
|
|
|
|
resolver.RemoveAllRecords()
|
|
}
|
|
}
|
|
|
|
func TestAcmeValidateTLSALPN01Challenge(t *testing.T) {
|
|
// This test is not parallel because we modify ALPNPort to use a custom
|
|
// non-standard port _just for testing purposes_.
|
|
host := "localhost"
|
|
config := &acmeConfigEntry{}
|
|
|
|
returnedProtocols := []string{ALPNProtocol}
|
|
var certificates []*x509.Certificate
|
|
var privateKey crypto.PrivateKey
|
|
|
|
tlsCfg := &tls.Config{}
|
|
tlsCfg.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) {
|
|
var retCfg tls.Config = *tlsCfg
|
|
retCfg.NextProtos = returnedProtocols
|
|
t.Logf("[alpn-server] returned protocol: %v", returnedProtocols)
|
|
return &retCfg, nil
|
|
}
|
|
tlsCfg.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
var ret tls.Certificate
|
|
for index, cert := range certificates {
|
|
ret.Certificate = append(ret.Certificate, cert.Raw)
|
|
if index == 0 {
|
|
ret.Leaf = cert
|
|
}
|
|
}
|
|
ret.PrivateKey = privateKey
|
|
t.Logf("[alpn-server] returned certificates: %v", ret)
|
|
return &ret, nil
|
|
}
|
|
|
|
ln, err := tls.Listen("tcp", host+":0", tlsCfg)
|
|
require.NoError(t, err, "failed to listen with TLS config")
|
|
|
|
doOneAccept := func() {
|
|
t.Logf("[alpn-server] starting accept...")
|
|
connRaw, err := ln.Accept()
|
|
require.NoError(t, err, "failed to accept TLS connection")
|
|
|
|
t.Logf("[alpn-server] got connection...")
|
|
conn := tls.Server(connRaw.(*tls.Conn), tlsCfg)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
|
|
defer func() {
|
|
cancel()
|
|
}()
|
|
|
|
t.Logf("[alpn-server] starting handshake...")
|
|
if err := conn.HandshakeContext(ctx); err != nil {
|
|
t.Logf("[alpn-server] got non-fatal error while handshaking connection: %v", err)
|
|
}
|
|
|
|
t.Logf("[alpn-server] closing connection...")
|
|
if err := conn.Close(); err != nil {
|
|
t.Logf("[alpn-server] got non-fatal error while closing connection: %v", err)
|
|
}
|
|
}
|
|
|
|
ALPNPort = strings.Split(ln.Addr().String(), ":")[1]
|
|
|
|
type alpnTestCase struct {
|
|
name string
|
|
certificates []*x509.Certificate
|
|
privateKey crypto.PrivateKey
|
|
protocols []string
|
|
token string
|
|
thumbprint string
|
|
shouldFail bool
|
|
}
|
|
|
|
var alpnTestCases []alpnTestCase
|
|
// Add all of our keyAuthorizationTestCases into alpnTestCases
|
|
for index, tc := range keyAuthorizationTestCases {
|
|
t.Logf("using keyAuthorizationTestCase [tc=%d] as alpnTestCase [tc=%d]...", index, len(alpnTestCases))
|
|
// Properly encode the authorization.
|
|
checksum := sha256.Sum256([]byte(tc.keyAuthz))
|
|
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)
|
|
require.NoError(t, err, "failed generating private key")
|
|
tmpl := &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: host,
|
|
},
|
|
Issuer: pkix.Name{
|
|
CommonName: host,
|
|
},
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
|
PublicKey: key.Public(),
|
|
SerialNumber: big.NewInt(1),
|
|
DNSNames: []string{host},
|
|
ExtraExtensions: []pkix.Extension{
|
|
{
|
|
Id: OIDACMEIdentifier,
|
|
Critical: true,
|
|
Value: authz,
|
|
},
|
|
},
|
|
BasicConstraintsValid: true,
|
|
IsCA: false,
|
|
}
|
|
certBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
|
|
require.NoError(t, err, "failed to create certificate")
|
|
cert, err := x509.ParseCertificate(certBytes)
|
|
require.NoError(t, err, "failed to parse newly generated certificate")
|
|
|
|
newTc := alpnTestCase{
|
|
name: fmt.Sprintf("keyAuthorizationTestCase[%d]", index),
|
|
certificates: []*x509.Certificate{cert},
|
|
privateKey: key,
|
|
protocols: []string{ALPNProtocol},
|
|
token: tc.token,
|
|
thumbprint: tc.thumbprint,
|
|
shouldFail: tc.shouldFail,
|
|
}
|
|
alpnTestCases = append(alpnTestCases, newTc)
|
|
}
|
|
|
|
{
|
|
// Test case: Longer chain
|
|
// Build a self-signed certificate.
|
|
rootKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
require.NoError(t, err, "failed generating root private key")
|
|
tmpl := &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "Root CA",
|
|
},
|
|
Issuer: pkix.Name{
|
|
CommonName: "Root CA",
|
|
},
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
|
PublicKey: rootKey.Public(),
|
|
SerialNumber: big.NewInt(1),
|
|
BasicConstraintsValid: true,
|
|
IsCA: true,
|
|
}
|
|
rootCertBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, rootKey.Public(), rootKey)
|
|
require.NoError(t, err, "failed to create root certificate")
|
|
rootCert, err := x509.ParseCertificate(rootCertBytes)
|
|
require.NoError(t, err, "failed to parse newly generated root certificate")
|
|
|
|
// Compute our authorization.
|
|
checksum := sha256.Sum256([]byte("valid.valid"))
|
|
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)
|
|
require.NoError(t, err, "failed generating leaf private key")
|
|
tmpl = &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: host,
|
|
},
|
|
Issuer: pkix.Name{
|
|
CommonName: "Root CA",
|
|
},
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
|
PublicKey: key.Public(),
|
|
SerialNumber: big.NewInt(2),
|
|
DNSNames: []string{host},
|
|
ExtraExtensions: []pkix.Extension{
|
|
{
|
|
Id: OIDACMEIdentifier,
|
|
Critical: true,
|
|
Value: authz,
|
|
},
|
|
},
|
|
BasicConstraintsValid: true,
|
|
IsCA: false,
|
|
}
|
|
certBytes, err := x509.CreateCertificate(rand.Reader, tmpl, rootCert, key.Public(), rootKey)
|
|
require.NoError(t, err, "failed to create leaf certificate")
|
|
cert, err := x509.ParseCertificate(certBytes)
|
|
require.NoError(t, err, "failed to parse newly generated leaf certificate")
|
|
|
|
newTc := alpnTestCase{
|
|
name: "longer chain with valid leaf",
|
|
certificates: []*x509.Certificate{cert, rootCert},
|
|
privateKey: key,
|
|
protocols: []string{ALPNProtocol},
|
|
token: "valid",
|
|
thumbprint: "valid",
|
|
shouldFail: true,
|
|
}
|
|
alpnTestCases = append(alpnTestCases, newTc)
|
|
}
|
|
|
|
{
|
|
// Test case: cert without DNSSan
|
|
// Compute our authorization.
|
|
checksum := sha256.Sum256([]byte("valid.valid"))
|
|
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)
|
|
require.NoError(t, err, "failed generating leaf private key")
|
|
tmpl := &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: host,
|
|
},
|
|
Issuer: pkix.Name{
|
|
CommonName: host,
|
|
},
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
|
PublicKey: key.Public(),
|
|
SerialNumber: big.NewInt(2),
|
|
// NO DNSNames
|
|
ExtraExtensions: []pkix.Extension{
|
|
{
|
|
Id: OIDACMEIdentifier,
|
|
Critical: true,
|
|
Value: authz,
|
|
},
|
|
},
|
|
BasicConstraintsValid: true,
|
|
IsCA: false,
|
|
}
|
|
certBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
|
|
require.NoError(t, err, "failed to create leaf certificate")
|
|
cert, err := x509.ParseCertificate(certBytes)
|
|
require.NoError(t, err, "failed to parse newly generated leaf certificate")
|
|
|
|
newTc := alpnTestCase{
|
|
name: "valid keyauthz without valid dnsname",
|
|
certificates: []*x509.Certificate{cert},
|
|
privateKey: key,
|
|
protocols: []string{ALPNProtocol},
|
|
token: "valid",
|
|
thumbprint: "valid",
|
|
shouldFail: true,
|
|
}
|
|
alpnTestCases = append(alpnTestCases, newTc)
|
|
}
|
|
|
|
{
|
|
// Test case: cert without matching DNSSan
|
|
// Compute our authorization.
|
|
checksum := sha256.Sum256([]byte("valid.valid"))
|
|
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)
|
|
require.NoError(t, err, "failed generating leaf private key")
|
|
tmpl := &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: host,
|
|
},
|
|
Issuer: pkix.Name{
|
|
CommonName: host,
|
|
},
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
|
PublicKey: key.Public(),
|
|
SerialNumber: big.NewInt(2),
|
|
DNSNames: []string{host + ".dadgarcorp.com" /* not matching host! */},
|
|
ExtraExtensions: []pkix.Extension{
|
|
{
|
|
Id: OIDACMEIdentifier,
|
|
Critical: true,
|
|
Value: authz,
|
|
},
|
|
},
|
|
BasicConstraintsValid: true,
|
|
IsCA: false,
|
|
}
|
|
certBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
|
|
require.NoError(t, err, "failed to create leaf certificate")
|
|
cert, err := x509.ParseCertificate(certBytes)
|
|
require.NoError(t, err, "failed to parse newly generated leaf certificate")
|
|
|
|
newTc := alpnTestCase{
|
|
name: "valid keyauthz without matching dnsname",
|
|
certificates: []*x509.Certificate{cert},
|
|
privateKey: key,
|
|
protocols: []string{ALPNProtocol},
|
|
token: "valid",
|
|
thumbprint: "valid",
|
|
shouldFail: true,
|
|
}
|
|
alpnTestCases = append(alpnTestCases, newTc)
|
|
}
|
|
|
|
{
|
|
// Test case: cert with additional SAN
|
|
// Compute our authorization.
|
|
checksum := sha256.Sum256([]byte("valid.valid"))
|
|
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)
|
|
require.NoError(t, err, "failed generating leaf private key")
|
|
tmpl := &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: host,
|
|
},
|
|
Issuer: pkix.Name{
|
|
CommonName: host,
|
|
},
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
|
PublicKey: key.Public(),
|
|
SerialNumber: big.NewInt(2),
|
|
DNSNames: []string{host},
|
|
EmailAddresses: []string{"webmaster@" + host}, /* unexpected */
|
|
ExtraExtensions: []pkix.Extension{
|
|
{
|
|
Id: OIDACMEIdentifier,
|
|
Critical: true,
|
|
Value: authz,
|
|
},
|
|
},
|
|
BasicConstraintsValid: true,
|
|
IsCA: false,
|
|
}
|
|
certBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
|
|
require.NoError(t, err, "failed to create leaf certificate")
|
|
cert, err := x509.ParseCertificate(certBytes)
|
|
require.NoError(t, err, "failed to parse newly generated leaf certificate")
|
|
|
|
newTc := alpnTestCase{
|
|
name: "valid keyauthz with additional email SANs",
|
|
certificates: []*x509.Certificate{cert},
|
|
privateKey: key,
|
|
protocols: []string{ALPNProtocol},
|
|
token: "valid",
|
|
thumbprint: "valid",
|
|
shouldFail: true,
|
|
}
|
|
alpnTestCases = append(alpnTestCases, newTc)
|
|
}
|
|
|
|
{
|
|
// Test case: cert without CN
|
|
// Compute our authorization.
|
|
checksum := sha256.Sum256([]byte("valid.valid"))
|
|
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)
|
|
require.NoError(t, err, "failed generating leaf private key")
|
|
tmpl := &x509.Certificate{
|
|
Subject: pkix.Name{},
|
|
Issuer: pkix.Name{},
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
|
PublicKey: key.Public(),
|
|
SerialNumber: big.NewInt(2),
|
|
DNSNames: []string{host},
|
|
ExtraExtensions: []pkix.Extension{
|
|
{
|
|
Id: OIDACMEIdentifier,
|
|
Critical: true,
|
|
Value: authz,
|
|
},
|
|
},
|
|
BasicConstraintsValid: true,
|
|
IsCA: false,
|
|
}
|
|
certBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
|
|
require.NoError(t, err, "failed to create leaf certificate")
|
|
cert, err := x509.ParseCertificate(certBytes)
|
|
require.NoError(t, err, "failed to parse newly generated leaf certificate")
|
|
|
|
newTc := alpnTestCase{
|
|
name: "valid certificate; no Subject/Issuer (missing CN)",
|
|
certificates: []*x509.Certificate{cert},
|
|
privateKey: key,
|
|
protocols: []string{ALPNProtocol},
|
|
token: "valid",
|
|
thumbprint: "valid",
|
|
shouldFail: false,
|
|
}
|
|
alpnTestCases = append(alpnTestCases, newTc)
|
|
}
|
|
|
|
{
|
|
// Test case: cert without the extension
|
|
// Build a leaf certificate which should fail validation
|
|
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
require.NoError(t, err, "failed generating leaf private key")
|
|
tmpl := &x509.Certificate{
|
|
Subject: pkix.Name{},
|
|
Issuer: pkix.Name{},
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
|
PublicKey: key.Public(),
|
|
SerialNumber: big.NewInt(1),
|
|
DNSNames: []string{host},
|
|
BasicConstraintsValid: true,
|
|
IsCA: true,
|
|
}
|
|
certBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key)
|
|
require.NoError(t, err, "failed to create leaf certificate")
|
|
cert, err := x509.ParseCertificate(certBytes)
|
|
require.NoError(t, err, "failed to parse newly generated leaf certificate")
|
|
|
|
newTc := alpnTestCase{
|
|
name: "missing required acmeIdentifier extension",
|
|
certificates: []*x509.Certificate{cert},
|
|
privateKey: key,
|
|
protocols: []string{ALPNProtocol},
|
|
token: "valid",
|
|
thumbprint: "valid",
|
|
shouldFail: true,
|
|
}
|
|
alpnTestCases = append(alpnTestCases, newTc)
|
|
}
|
|
|
|
{
|
|
// Test case: root without a leaf
|
|
// Build a self-signed certificate.
|
|
rootKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
require.NoError(t, err, "failed generating root private key")
|
|
tmpl := &x509.Certificate{
|
|
Subject: pkix.Name{
|
|
CommonName: "Root CA",
|
|
},
|
|
Issuer: pkix.Name{
|
|
CommonName: "Root CA",
|
|
},
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
|
PublicKey: rootKey.Public(),
|
|
SerialNumber: big.NewInt(1),
|
|
BasicConstraintsValid: true,
|
|
IsCA: true,
|
|
}
|
|
rootCertBytes, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, rootKey.Public(), rootKey)
|
|
require.NoError(t, err, "failed to create root certificate")
|
|
rootCert, err := x509.ParseCertificate(rootCertBytes)
|
|
require.NoError(t, err, "failed to parse newly generated root certificate")
|
|
|
|
newTc := alpnTestCase{
|
|
name: "root without leaf",
|
|
certificates: []*x509.Certificate{rootCert},
|
|
privateKey: rootKey,
|
|
protocols: []string{ALPNProtocol},
|
|
token: "valid",
|
|
thumbprint: "valid",
|
|
shouldFail: true,
|
|
}
|
|
alpnTestCases = append(alpnTestCases, newTc)
|
|
}
|
|
|
|
for index, tc := range alpnTestCases {
|
|
t.Logf("\n\n[tc=%d/name=%s] starting validation", index, tc.name)
|
|
certificates = tc.certificates
|
|
privateKey = tc.privateKey
|
|
returnedProtocols = tc.protocols
|
|
|
|
// Attempt to validate the challenge.
|
|
go doOneAccept()
|
|
isValid, err := ValidateTLSALPN01Challenge(host, tc.token, tc.thumbprint, config)
|
|
if !isValid && err == nil {
|
|
t.Fatalf("[tc=%d/name=%s] expected failure to give reason via err (%v / %v)", index, tc.name, isValid, err)
|
|
}
|
|
|
|
expectedValid := !tc.shouldFail
|
|
if expectedValid != isValid {
|
|
t.Fatalf("[tc=%d/name=%s] got ret=%v (err=%v), expected ret=%v (shouldFail=%v)", index, tc.name, isValid, err, expectedValid, tc.shouldFail)
|
|
} else if err != nil {
|
|
t.Logf("[tc=%d/name=%s] got expected failure: err=%v", index, tc.name, err)
|
|
}
|
|
}
|
|
}
|