ff3a9fb652
Co-authored-by: Steven Clark <steven.clark@hashicorp.com>
717 lines
22 KiB
Go
717 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/go-hclog"
|
|
"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{}
|
|
|
|
log := hclog.L()
|
|
|
|
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
|
|
log.Info(fmt.Sprintf("[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
|
|
log.Info(fmt.Sprintf("[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() {
|
|
log.Info("[alpn-server] starting accept...")
|
|
connRaw, err := ln.Accept()
|
|
require.NoError(t, err, "failed to accept TLS connection")
|
|
|
|
log.Info("[alpn-server] got connection...")
|
|
conn := tls.Server(connRaw.(*tls.Conn), tlsCfg)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
|
|
defer func() {
|
|
log.Info("[alpn-server] canceling listener connection...")
|
|
cancel()
|
|
}()
|
|
|
|
log.Info("[alpn-server] starting handshake...")
|
|
if err := conn.HandshakeContext(ctx); err != nil {
|
|
log.Info("[alpn-server] got non-fatal error while handshaking connection: %v", err)
|
|
}
|
|
|
|
log.Info("[alpn-server] closing connection...")
|
|
if err := conn.Close(); err != nil {
|
|
log.Info("[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 {
|
|
log.Info(fmt.Sprintf("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 {
|
|
log.Info(fmt.Sprintf("\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 {
|
|
log.Info(fmt.Sprintf("[tc=%d/name=%s] got expected failure: err=%v", index, tc.name, err))
|
|
}
|
|
}
|
|
}
|