mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-14 18:47:01 +02:00
* Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License. Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUS-1.1 * Fix test that expected exact offset on hcl file --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Co-authored-by: Sarah Thompson <sthompson@hashicorp.com> Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
717 lines
22 KiB
Go
717 lines
22 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
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))
|
|
}
|
|
}
|
|
}
|