c5280001a4
What's left are the minimal API changes required to use the delegated credential extension in the TLS handshake.
313 lines
11 KiB
Go
313 lines
11 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 (
|
|
"crypto"
|
|
"crypto/x509"
|
|
"encoding/asn1"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// A PEM-encoded "delegation certificate", an X.509 certificate with the
|
|
// DelegationUsage extension. The extension is defined in
|
|
// specified in https://tools.ietf.org/html/draft-ietf-tls-subcerts-01.
|
|
var dcDelegationCertPEM = `-----BEGIN CERTIFICATE-----
|
|
MIIBdzCCAR2gAwIBAgIQLVIvEpo0/0TzRja4ImvB1TAKBggqhkjOPQQDAjASMRAw
|
|
DgYDVQQKEwdBY21lIENvMB4XDTE4MDcwMzE2NTE1M1oXDTE5MDcwMzE2NTE1M1ow
|
|
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOhB
|
|
U6adaAgliLaFc1PAo9HBO4Wish1G4df3IK5EXLy+ooYfmkfzT1FxqbNLZufNYzve
|
|
25fmpal/1VJAjpVyKq2jVTBTMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr
|
|
BgEFBQcDATAMBgNVHRMBAf8EAjAAMA8GA1UdEQQIMAaHBH8AAAEwDQYJKwYBBAGC
|
|
2kssBAAwCgYIKoZIzj0EAwIDSAAwRQIhAPNwRk6cygm6zO5rjOzohKYWS+1KuWCM
|
|
OetDIvU4mdyoAiAGN97y3GJccYn9ZOJS4UOqhr9oO8PuZMLgdq4OrMRiiA==
|
|
-----END CERTIFICATE-----
|
|
`
|
|
|
|
// The PEM-encoded "delegation key", the secret key associated with the
|
|
// delegation certificate.
|
|
var dcDelegationKeyPEM = `-----BEGIN EC PRIVATE KEY-----
|
|
MHcCAQEEIJDVlo+sJolMcNjMkfCGDUjMJcE4UgclcXGCrOtbJAi2oAoGCCqGSM49
|
|
AwEHoUQDQgAE6EFTpp1oCCWItoVzU8Cj0cE7haKyHUbh1/cgrkRcvL6ihh+aR/NP
|
|
UXGps0tm581jO97bl+alqX/VUkCOlXIqrQ==
|
|
-----END EC PRIVATE KEY-----
|
|
`
|
|
|
|
// A certificate without the DelegationUsage extension.
|
|
var dcCertPEM = `-----BEGIN CERTIFICATE-----
|
|
MIIBaTCCAQ6gAwIBAgIQSUo+9uaip3qCW+1EPeHZgDAKBggqhkjOPQQDAjASMRAw
|
|
DgYDVQQKEwdBY21lIENvMB4XDTE4MDYxMjIzNDAyNloXDTE5MDYxMjIzNDAyNlow
|
|
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLf7
|
|
fiznPVdc3V5mM3ymswU2/IoJaq/deA6dgdj50ozdYyRiAPjxzcz9zRsZw1apTF/h
|
|
yNfiLhV4EE1VrwXcT5OjRjBEMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr
|
|
BgEFBQcDATAMBgNVHRMBAf8EAjAAMA8GA1UdEQQIMAaHBH8AAAEwCgYIKoZIzj0E
|
|
AwIDSQAwRgIhANXG0zmrVtQBK0TNZZoEGMOtSwxmiZzXNe+IjdpxO3TiAiEA5VYx
|
|
0CWJq5zqpVXbJMeKVMASo2nrXZoA6NhJvFQ97hw=
|
|
-----END CERTIFICATE-----
|
|
`
|
|
|
|
// The secret key associatted with dcCertPEM.
|
|
var dcKeyPEM = `-----BEGIN EC PRIVATE KEY-----
|
|
MHcCAQEEIMw9DiOfGI1E/XZrrW2huZSjYi0EKwvVjAe+dYtyFsSloAoGCCqGSM49
|
|
AwEHoUQDQgAEt/t+LOc9V1zdXmYzfKazBTb8iglqr914Dp2B2PnSjN1jJGIA+PHN
|
|
zP3NGxnDVqlMX+HI1+IuFXgQTVWvBdxPkw==
|
|
-----END EC PRIVATE KEY-----
|
|
`
|
|
|
|
// dcTestDC stores delegated credentials and their secret keys.
|
|
type dcTestDC struct {
|
|
Name string
|
|
Version int
|
|
Scheme int
|
|
DC []byte
|
|
PrivateKey []byte
|
|
}
|
|
|
|
// Test data used for testing the TLS handshake with the delegated creedential
|
|
// extension. The PEM block encodes a DER encoded slice of dcTestDC's.
|
|
var dcTestDCsPEM = `-----BEGIN DC TEST DATA-----
|
|
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
|
|
HJbn52hmB027JEIMPWsxKxPkr7udk7Q=
|
|
-----END DC TEST DATA-----
|
|
`
|
|
|
|
var dcTestDCs []dcTestDC
|
|
var dcTestConfig *Config
|
|
var dcTestDelegationCert Certificate
|
|
var dcTestCert Certificate
|
|
var dcTestNow time.Time
|
|
|
|
func init() {
|
|
// Parse the PEM block containing the test DCs.
|
|
block, _ := pem.Decode([]byte(dcTestDCsPEM))
|
|
if block == nil {
|
|
panic("failed to decode DC tests PEM block")
|
|
}
|
|
|
|
// Parse the DER-encoded test DCs.
|
|
_, err := asn1.Unmarshal(block.Bytes, &dcTestDCs)
|
|
if err != nil {
|
|
panic("failed to unmarshal DC test ASN.1 data")
|
|
}
|
|
|
|
// Use a static time for testing. This is the point at which the test DCs
|
|
// were generated.
|
|
dcTestNow = time.Date(2018, 07, 03, 18, 0, 0, 234234, time.UTC)
|
|
|
|
// The base configuration for the client and server.
|
|
dcTestConfig = &Config{
|
|
Time: func() time.Time {
|
|
return dcTestNow
|
|
},
|
|
Rand: zeroSource{},
|
|
Certificates: nil,
|
|
MinVersion: VersionTLS10,
|
|
MaxVersion: VersionTLS13Draft22,
|
|
CipherSuites: allCipherSuites(),
|
|
}
|
|
|
|
// The delegation certificate.
|
|
dcTestDelegationCert, err = X509KeyPair([]byte(dcDelegationCertPEM), []byte(dcDelegationKeyPEM))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
dcTestDelegationCert.Leaf, err = x509.ParseCertificate(dcTestDelegationCert.Certificate[0])
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// A certificate without the the DelegationUsage extension for X.509.
|
|
dcTestCert, err = X509KeyPair([]byte(dcCertPEM), []byte(dcKeyPEM))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
dcTestCert.Leaf, err = x509.ParseCertificate(dcTestCert.Certificate[0])
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Make these roots of these certificates the client's trusted CAs.
|
|
dcTestConfig.RootCAs = x509.NewCertPool()
|
|
|
|
raw := dcTestDelegationCert.Certificate[len(dcTestDelegationCert.Certificate)-1]
|
|
root, err := x509.ParseCertificate(raw)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
dcTestConfig.RootCAs.AddCert(root)
|
|
|
|
raw = dcTestCert.Certificate[len(dcTestCert.Certificate)-1]
|
|
root, err = x509.ParseCertificate(raw)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
dcTestConfig.RootCAs.AddCert(root)
|
|
}
|
|
|
|
// Executes the handshake with the given configuration and returns true if the
|
|
// delegated credential extension was successfully negotiated.
|
|
func testConnWithDC(t *testing.T, clientConfig, serverConfig *Config) (bool, error) {
|
|
ln := newLocalListener(t)
|
|
defer ln.Close()
|
|
|
|
// Listen for and serve a single client connection.
|
|
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
|
|
}()
|
|
|
|
// Dial the server.
|
|
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
|
|
}
|
|
|
|
// Return true if the client's conn.dc structure was instantiated.
|
|
st := cli.ConnectionState()
|
|
return (st.DelegatedCredential != 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 &dcTestDelegationCert, nil
|
|
}
|
|
return &dcTestCert, nil
|
|
}
|
|
|
|
// Various test cases for handshakes involving DCs.
|
|
var dcTesters = []struct {
|
|
clientDC bool
|
|
serverDC bool
|
|
clientSkipVerify bool
|
|
clientMaxVers uint16
|
|
serverMaxVers uint16
|
|
nowOffset time.Duration
|
|
dcTestName string
|
|
expectSuccess bool
|
|
expectDC bool
|
|
name string
|
|
}{
|
|
{true, true, false, VersionTLS12, VersionTLS12, 0, "tls12", true, true, "tls12"},
|
|
{true, true, false, VersionTLS13, VersionTLS13, 0, "tls13", true, true, "tls13"},
|
|
{true, true, false, VersionTLS12, VersionTLS12, 0, "malformed12", false, false, "tls12, malformed dc"},
|
|
{true, true, false, VersionTLS13, VersionTLS13, 0, "malformed13", false, false, "tls13, malformed dc"},
|
|
{true, true, true, VersionTLS12, VersionTLS12, 0, "invalid", true, true, "tls12, invalid dc, skip verify"},
|
|
{true, true, true, VersionTLS13, VersionTLS13, 0, "invalid", true, true, "tls13, invalid dc, skip verify"},
|
|
{false, true, false, VersionTLS12, VersionTLS12, 0, "tls12", true, false, "client no dc"},
|
|
{true, false, false, VersionTLS12, VersionTLS12, 0, "tls12", true, false, "server no dc"},
|
|
{true, true, false, VersionTLS11, VersionTLS12, 0, "tls12", true, false, "client old"},
|
|
{true, true, false, VersionTLS12, VersionTLS11, 0, "tls12", true, false, "server old"},
|
|
{true, true, false, VersionTLS13, VersionTLS13, dcMaxTTL, "tls13", false, false, "expired dc"},
|
|
}
|
|
|
|
// Tests the handshake with the delegated credential extension for each test
|
|
// case in dcTests.
|
|
func TestDCHandshake(t *testing.T) {
|
|
clientConfig := dcTestConfig.Clone()
|
|
serverConfig := dcTestConfig.Clone()
|
|
serverConfig.GetCertificate = testServerGetCertificate
|
|
|
|
for i, tester := range dcTesters {
|
|
clientConfig.MaxVersion = tester.clientMaxVers
|
|
serverConfig.MaxVersion = tester.serverMaxVers
|
|
clientConfig.InsecureSkipVerify = tester.clientSkipVerify
|
|
clientConfig.AcceptDelegatedCredential = tester.clientDC
|
|
clientConfig.Time = func() time.Time {
|
|
return dcTestNow.Add(time.Duration(tester.nowOffset))
|
|
}
|
|
|
|
if tester.serverDC {
|
|
serverConfig.GetDelegatedCredential = func(
|
|
ch *ClientHelloInfo, vers uint16) ([]byte, crypto.PrivateKey, error) {
|
|
for _, test := range dcTestDCs {
|
|
if test.Name == tester.dcTestName {
|
|
sk, err := x509.ParseECPrivateKey(test.PrivateKey)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return test.DC, sk, nil
|
|
}
|
|
}
|
|
return nil, nil, fmt.Errorf("Test DC with name '%s' not found", tester.dcTestName)
|
|
}
|
|
} else {
|
|
serverConfig.GetDelegatedCredential = nil
|
|
}
|
|
|
|
usedDC, err := testConnWithDC(t, clientConfig, serverConfig)
|
|
if err != nil && tester.expectSuccess {
|
|
t.Errorf("test #%d (%s) fails: %s", i+1, tester.name, err)
|
|
} else if err == nil && !tester.expectSuccess {
|
|
t.Errorf("test #%d (%s) succeeds; expected failure", i+1, tester.name)
|
|
}
|
|
|
|
if usedDC != tester.expectDC {
|
|
t.Errorf("test #%d (%s) usedDC = %v; expected %v", i+1, tester.name, usedDC, tester.expectDC)
|
|
}
|
|
}
|
|
}
|