th5/subcerts_test.go
Christopher Patton 84fe9084cd Implement the delegated_credential extension for TLS
This complies with the standard, currently an Internet draft:
https://tlswg.github.io/tls-subcerts/draft-ietf-tls-subcerts.html

It also adds a minimal interface for generating new delegated
credentials.
2018-07-03 09:57:54 -07:00

501 lines
15 KiB
Go

// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tls
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/x509"
"errors"
"fmt"
"testing"
"time"
)
// dcWithPrivateKey stores a delegated credential and its corresponding private
// key.
type dcWithPrivateKey struct {
*DelegatedCredential
privateKey crypto.PrivateKey
}
// These test keys were generated with the following program, available in the
// crypto/tls directory:
//
// go run generate_cert.go -ecdsa-curve P256 -host 127.0.0.1 -dc
//
// To get a certificate without the DelegationUsage extension, remove the `-dc`
// parameter.
var delegatorCertPEM = `-----BEGIN CERTIFICATE-----
MIIBdzCCAR2gAwIBAgIQLVIvEpo0/0TzRja4ImvB1TAKBggqhkjOPQQDAjASMRAw
DgYDVQQKEwdBY21lIENvMB4XDTE4MDcwMzE2NTE1M1oXDTE5MDcwMzE2NTE1M1ow
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOhB
U6adaAgliLaFc1PAo9HBO4Wish1G4df3IK5EXLy+ooYfmkfzT1FxqbNLZufNYzve
25fmpal/1VJAjpVyKq2jVTBTMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr
BgEFBQcDATAMBgNVHRMBAf8EAjAAMA8GA1UdEQQIMAaHBH8AAAEwDQYJKwYBBAGC
2kssBAAwCgYIKoZIzj0EAwIDSAAwRQIhAPNwRk6cygm6zO5rjOzohKYWS+1KuWCM
OetDIvU4mdyoAiAGN97y3GJccYn9ZOJS4UOqhr9oO8PuZMLgdq4OrMRiiA==
-----END CERTIFICATE-----
`
var delegatorKeyPEM = `-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIJDVlo+sJolMcNjMkfCGDUjMJcE4UgclcXGCrOtbJAi2oAoGCCqGSM49
AwEHoUQDQgAE6EFTpp1oCCWItoVzU8Cj0cE7haKyHUbh1/cgrkRcvL6ihh+aR/NP
UXGps0tm581jO97bl+alqX/VUkCOlXIqrQ==
-----END EC PRIVATE KEY-----
`
var nonDelegatorCertPEM = `-----BEGIN CERTIFICATE-----
MIIBaTCCAQ6gAwIBAgIQSUo+9uaip3qCW+1EPeHZgDAKBggqhkjOPQQDAjASMRAw
DgYDVQQKEwdBY21lIENvMB4XDTE4MDYxMjIzNDAyNloXDTE5MDYxMjIzNDAyNlow
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLf7
fiznPVdc3V5mM3ymswU2/IoJaq/deA6dgdj50ozdYyRiAPjxzcz9zRsZw1apTF/h
yNfiLhV4EE1VrwXcT5OjRjBEMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr
BgEFBQcDATAMBgNVHRMBAf8EAjAAMA8GA1UdEQQIMAaHBH8AAAEwCgYIKoZIzj0E
AwIDSQAwRgIhANXG0zmrVtQBK0TNZZoEGMOtSwxmiZzXNe+IjdpxO3TiAiEA5VYx
0CWJq5zqpVXbJMeKVMASo2nrXZoA6NhJvFQ97hw=
-----END CERTIFICATE-----
`
var nonDelegatorKeyPEM = `-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIMw9DiOfGI1E/XZrrW2huZSjYi0EKwvVjAe+dYtyFsSloAoGCCqGSM49
AwEHoUQDQgAEt/t+LOc9V1zdXmYzfKazBTb8iglqr914Dp2B2PnSjN1jJGIA+PHN
zP3NGxnDVqlMX+HI1+IuFXgQTVWvBdxPkw==
-----END EC PRIVATE KEY-----
`
// Invalid TLS versions used for testing purposes.
const (
versionInvalidDC uint16 = 0xff00
versionMalformedDC12 uint16 = 0xff12
versionMalformedDC13 uint16 = 0xff13
)
var dcTestConfig *Config
var dcTestCerts map[string]*Certificate
var dcTestDCs map[uint16]dcWithPrivateKey
var dcNow time.Time
var dcTestDCScheme = ECDSAWithP521AndSHA512
var dcTestDCVersions = []uint16{
VersionTLS12,
VersionTLS13,
VersionTLS13Draft23,
versionInvalidDC,
}
func init() {
// Use a static time for testing at whcih time the test certificates are
// valid.
dcNow = time.Date(2018, 07, 03, 18, 0, 0, 234234, time.UTC)
dcTestConfig = &Config{
Time: func() time.Time {
return dcNow
},
Rand: zeroSource{},
Certificates: nil,
MinVersion: VersionTLS10,
MaxVersion: VersionTLS13Draft22,
CipherSuites: allCipherSuites(),
}
// The certificates of the server.
dcTestCerts = make(map[string]*Certificate)
var err error
// The delegation certificate.
dcCert := new(Certificate)
*dcCert, err = X509KeyPair([]byte(delegatorCertPEM), []byte(delegatorKeyPEM))
if err != nil {
panic(err)
}
dcCert.Leaf, err = x509.ParseCertificate(dcCert.Certificate[0])
if err != nil {
panic(err)
}
dcTestCerts["dc"] = dcCert
// The standard certificate.
ndcCert := new(Certificate)
*ndcCert, err = X509KeyPair([]byte(nonDelegatorCertPEM), []byte(nonDelegatorKeyPEM))
if err != nil {
panic(err)
}
ndcCert.Leaf, err = x509.ParseCertificate(ndcCert.Certificate[0])
if err != nil {
panic(err)
}
dcTestCerts["no dc"] = ndcCert
// The root certificates for the client.
dcTestConfig.RootCAs = x509.NewCertPool()
dcRoot, err := x509.ParseCertificate(dcCert.Certificate[len(dcCert.Certificate)-1])
if err != nil {
panic(err)
}
dcTestConfig.RootCAs.AddCert(dcRoot)
ndcRoot, err := x509.ParseCertificate(ndcCert.Certificate[len(ndcCert.Certificate)-1])
if err != nil {
panic(err)
}
dcTestConfig.RootCAs.AddCert(ndcRoot)
// A pool of DCs.
dcTestDCs = make(map[uint16]dcWithPrivateKey)
for _, vers := range dcTestDCVersions {
dc, sk, err := NewDelegatedCredential(dcCert, dcTestDCScheme, dcNow.Sub(dcCert.Leaf.NotBefore)+dcMaxTTL, vers)
if err != nil {
panic(err)
}
dcTestDCs[vers] = dcWithPrivateKey{dc, sk}
}
// Add two DCs with invalid private keys, one for TLS 1.2 and another for
// 1.3.
malformedDC12 := new(DelegatedCredential)
*malformedDC12 = *dcTestDCs[VersionTLS12].DelegatedCredential
dcTestDCs[versionMalformedDC12] = dcWithPrivateKey{
malformedDC12,
dcTestDCs[versionInvalidDC].privateKey,
}
malformedDC13 := new(DelegatedCredential)
*malformedDC13 = *dcTestDCs[VersionTLS13].DelegatedCredential
dcTestDCs[versionMalformedDC13] = dcWithPrivateKey{
malformedDC13,
dcTestDCs[versionInvalidDC].privateKey,
}
}
func checkECDSAPublicKeysEqual(
publicKey, publicKey2 crypto.PublicKey, scheme SignatureScheme) error {
curve := getCurve(scheme)
pk := publicKey.(*ecdsa.PublicKey)
pk2 := publicKey2.(*ecdsa.PublicKey)
serializedPublicKey := elliptic.Marshal(curve, pk.X, pk.Y)
serializedPublicKey2 := elliptic.Marshal(curve, pk2.X, pk2.Y)
if !bytes.Equal(serializedPublicKey2, serializedPublicKey) {
return errors.New("PublicKey mismatch")
}
return nil
}
// Test that cred and cred2 are equal.
func checkCredentialsEqual(dc, dc2 *DelegatedCredential) error {
if dc2.ValidTime != dc.ValidTime {
return fmt.Errorf("ValidTime mismatch: got %d; want %d", dc2.ValidTime, dc.ValidTime)
}
if dc2.publicKeyScheme != dc.publicKeyScheme {
return fmt.Errorf("scheme mismatch: got %04x; want %04x", dc2.publicKeyScheme, dc.publicKeyScheme)
}
return checkECDSAPublicKeysEqual(dc.PublicKey, dc2.PublicKey, dc.publicKeyScheme)
}
// Test delegation and validation of credentials.
func TestDelegateValidate(t *testing.T) {
ver := uint16(VersionTLS12)
cert := dcTestCerts["dc"]
validTime := dcNow.Sub(cert.Leaf.NotBefore) + dcMaxTTL
shortValidTime := dcNow.Sub(cert.Leaf.NotBefore) + time.Second
delegatedCred, _, err := NewDelegatedCredential(cert, ECDSAWithP256AndSHA256, validTime, ver)
if err != nil {
t.Fatal(err)
}
// Test validation of good DC.
if v, err := delegatedCred.Validate(cert.Leaf, ver, dcNow); err != nil {
t.Error(err)
} else if !v {
t.Error("good DC is invalid; want valid")
}
// Test validation of expired DC.
tooLate := dcNow.Add(dcMaxTTL).Add(time.Nanosecond)
if v, err := delegatedCred.Validate(cert.Leaf, ver, tooLate); err == nil {
t.Error("expired DC validation succeeded; want failure")
} else if v {
t.Error("expired DC is valid; want invalid")
}
// Test protocol binding.
if v, err := delegatedCred.Validate(cert.Leaf, VersionSSL30, dcNow); err != nil {
t.Fatal(err)
} else if v {
t.Error("DC with wrong version is valid; want invalid")
}
// Test signature algorithm binding.
delegatedCred.Scheme = ECDSAWithP521AndSHA512
if v, err := delegatedCred.Validate(cert.Leaf, ver, dcNow); err != nil {
t.Fatal(err)
} else if v {
t.Error("DC with wrong scheme is valid; want invalid")
}
delegatedCred.Scheme = ECDSAWithP256AndSHA256
// Test delegation cedrtificate binding.
cert.Leaf.Raw[0] ^= byte(42)
if v, err := delegatedCred.Validate(cert.Leaf, ver, dcNow); err != nil {
t.Fatal(err)
} else if v {
t.Error("DC with wrong cert is valid; want invalid")
}
cert.Leaf.Raw[0] ^= byte(42)
// Test validation of DC who's TTL is too long.
delegatedCred2, _, err := NewDelegatedCredential(cert, ECDSAWithP256AndSHA256, validTime+time.Second, ver)
if err != nil {
t.Fatal(err)
}
if v, err := delegatedCred2.Validate(cert.Leaf, ver, dcNow); err == nil {
t.Error("DC validation with long TTL succeeded; want failure")
} else if v {
t.Error("DC with long TTL is valid; want invalid")
}
// Test validation of DC who's TTL is short.
delegatedCred3, _, err := NewDelegatedCredential(cert, ECDSAWithP256AndSHA256, shortValidTime, ver)
if err != nil {
t.Fatal(err)
}
if v, err := delegatedCred3.Validate(cert.Leaf, ver, dcNow); err != nil {
t.Error(err)
} else if !v {
t.Error("good DC is invalid; want valid")
}
// Test validation of DC using a certificate that can't delegate.
if v, err := delegatedCred.Validate(
dcTestCerts["no dc"].Leaf, ver, dcNow); err != errNoDelegationUsage {
t.Error("DC validation with non-delegation cert succeeded; want failure")
} else if v {
t.Error("DC with non-delegation cert is valid; want invalid")
}
}
// Test encoding/decoding of delegated credentials.
func TestDelegatedCredentialMarshalUnmarshal(t *testing.T) {
cert := dcTestCerts["dc"]
delegatedCred, _, err := NewDelegatedCredential(cert,
ECDSAWithP256AndSHA256,
dcNow.Sub(cert.Leaf.NotBefore)+dcMaxTTL,
VersionTLS12)
if err != nil {
t.Fatal(err)
}
serialized, err := delegatedCred.Marshal()
if err != nil {
t.Error(err)
}
delegatedCred2, err := UnmarshalDelegatedCredential(serialized)
if err != nil {
t.Error(err)
}
err = checkCredentialsEqual(delegatedCred, delegatedCred2)
if err != nil {
t.Error(err)
}
if delegatedCred.Scheme != delegatedCred2.Scheme {
t.Errorf("scheme mismatch: got %04x; want %04x",
delegatedCred2.Scheme, delegatedCred.Scheme)
}
if !bytes.Equal(delegatedCred2.Signature, delegatedCred.Signature) {
t.Error("Signature mismatch")
}
}
// Tests the handshake and one round of application data. Returns true if the
// connection used a DC.
func testConnWithDC(t *testing.T,
clientMsg, serverMsg string,
clientConfig, serverConfig *Config) (bool, error) {
ln := newLocalListener(t)
defer ln.Close()
srvCh := make(chan *Conn, 1)
var serr error
go func() {
sconn, err := ln.Accept()
if err != nil {
serr = err
srvCh <- nil
return
}
srv := Server(sconn, serverConfig)
if err := srv.Handshake(); err != nil {
serr = fmt.Errorf("handshake: %v", err)
srvCh <- nil
return
}
srvCh <- srv
}()
cli, err := Dial("tcp", ln.Addr().String(), clientConfig)
if err != nil {
return false, err
}
defer cli.Close()
srv := <-srvCh
if srv == nil {
return false, serr
}
bufLen := len(clientMsg)
if len(serverMsg) > len(clientMsg) {
bufLen = len(serverMsg)
}
buf := make([]byte, bufLen)
cli.Write([]byte(clientMsg))
n, err := srv.Read(buf)
if n != len(clientMsg) || string(buf[:n]) != clientMsg {
return false, fmt.Errorf("Server read = %d, buf= %q; want %d, %s", n, buf, len(clientMsg), clientMsg)
}
srv.Write([]byte(serverMsg))
n, err = cli.Read(buf)
if n != len(serverMsg) || err != nil || string(buf[:n]) != serverMsg {
return false, fmt.Errorf("Client read = %d, %v, data %q; want %d, nil, %s", n, err, buf, len(serverMsg), serverMsg)
}
// Return true if the client's conn.dc structure was instantiated.
return (cli.verifiedDc != nil), nil
}
// Checks that the client suppports a version >= 1.2 and accepts delegated
// credentials. If so, it returns the delegation certificate; otherwise it
// returns a plain certificate.
func testServerGetCertificate(ch *ClientHelloInfo) (*Certificate, error) {
versOk := false
for _, vers := range ch.SupportedVersions {
versOk = versOk || (vers >= uint16(VersionTLS12))
}
if versOk && ch.AcceptsDelegatedCredential {
return dcTestCerts["dc"], nil
}
return dcTestCerts["no dc"], nil
}
// Checks that the ciient supports the signature algorithm supported by the test
// server, and that the server has a DC for the selected protocol version.
func testServerGetDC(ch *ClientHelloInfo, vers uint16) (*DelegatedCredential, crypto.PrivateKey, error) {
schemeOk := false
for _, scheme := range ch.SignatureSchemes {
schemeOk = schemeOk || (scheme == dcTestDCScheme)
}
versOk := false
for _, testVers := range dcTestDCVersions {
versOk = versOk || (vers == testVers)
}
if schemeOk && versOk && ch.AcceptsDelegatedCredential {
d := dcTestDCs[vers]
return d.DelegatedCredential, d.privateKey, nil
}
return nil, nil, nil
}
// Returns a DC signed with a bad version number.
func testServerGetInvalidDC(ch *ClientHelloInfo, vers uint16) (*DelegatedCredential, crypto.PrivateKey, error) {
d := dcTestDCs[versionInvalidDC]
return d.DelegatedCredential, d.privateKey, nil
}
// Returns a DC with the wrong private key.
func testServerGetMalformedDC(ch *ClientHelloInfo, vers uint16) (*DelegatedCredential, crypto.PrivateKey, error) {
if vers == VersionTLS12 {
d := dcTestDCs[versionMalformedDC12]
return d.DelegatedCredential, d.privateKey, nil
} else if vers == VersionTLS13 {
d := dcTestDCs[versionMalformedDC13]
return d.DelegatedCredential, d.privateKey, nil
} else {
return nil, nil, fmt.Errorf("testServerGetMalformedDC: unsupported version %x", vers)
}
}
var dcTests = []struct {
clientDC bool
serverDC bool
clientSkipVerify bool
clientMaxVers uint16
serverMaxVers uint16
useMalformedDC bool
useInvalidDC bool
expectSuccess bool
expectDC bool
name string
}{
{true, true, false, VersionTLS12, VersionTLS12, false, false, true, true, "tls12"},
{true, true, false, VersionTLS13, VersionTLS13, false, false, true, true, "tls13"},
{true, true, false, VersionTLS12, VersionTLS12, true, false, false, false, "tls12, malformed dc"},
{true, true, false, VersionTLS13, VersionTLS13, true, false, false, false, "tls13, malformed dc"},
{true, true, true, VersionTLS12, VersionTLS12, false, true, true, true, "tls12, invalid dc, skip verify"},
{true, true, true, VersionTLS13, VersionTLS13, false, true, true, true, "tls13, invalid dc, skip verify"},
{false, true, false, VersionTLS12, VersionTLS12, false, false, true, false, "client no dc"},
{true, false, false, VersionTLS12, VersionTLS12, false, false, true, false, "server no dc"},
{true, true, false, VersionTLS11, VersionTLS12, false, false, true, false, "client old"},
{true, true, false, VersionTLS12, VersionTLS11, false, false, true, false, "server old"},
}
// Tests the handshake with the delegated credential extension.
func TestDCHandshake(t *testing.T) {
serverMsg := "hello"
clientMsg := "world"
clientConfig := dcTestConfig.Clone()
serverConfig := dcTestConfig.Clone()
serverConfig.GetCertificate = testServerGetCertificate
for i, test := range dcTests {
clientConfig.AcceptDelegatedCredential = test.clientDC
clientConfig.InsecureSkipVerify = test.clientSkipVerify
if test.serverDC {
if test.useInvalidDC {
serverConfig.GetDelegatedCredential = testServerGetInvalidDC
} else if test.useMalformedDC {
serverConfig.GetDelegatedCredential = testServerGetMalformedDC
} else {
serverConfig.GetDelegatedCredential = testServerGetDC
}
} else {
serverConfig.GetDelegatedCredential = nil
}
clientConfig.MaxVersion = test.clientMaxVers
serverConfig.MaxVersion = test.serverMaxVers
usedDC, err := testConnWithDC(t, clientMsg, serverMsg, clientConfig, serverConfig)
if err != nil && test.expectSuccess {
t.Errorf("test #%d (%s) fails: %s", i+1, test.name, err)
} else if err == nil && !test.expectSuccess {
t.Errorf("test #%d (%s) succeeds; expected failure", i+1, test.name)
}
if usedDC != test.expectDC {
t.Errorf("test #%d (%s) usedDC = %v; expected %v", i+1, test.name, usedDC, test.expectDC)
}
}
}