// 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 trs import ( "crypto" "crypto/x509" "encoding/asn1" "encoding/pem" "errors" "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-02. const DcCertWithDelegationUsage = `-----BEGIN CERTIFICATE----- MIIBejCCASGgAwIBAgIQXXtl0v50W2OadoW0QwLUlzAKBggqhkjOPQQDAjAUMRIw EAYDVQQKEwlBY21lIEluYy4wHhcNMTgwNzMwMjAxMTE5WhcNMTgwODA2MjAxMTE5 WjAUMRIwEAYDVQQKEwlBY21lIEluYy4wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC AATcQuuaUNJ3kqKGs4DBdJVd7zWzyGANT4uBNGVkZ2cgaDsdFnx99fGibfgoWer8 HLt9Z+S6Hs+8bDPBHNgTR/Lfo1UwUzAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAww CgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAPBgNVHREECDAGhwR/AAABMA0GCSsG AQQBgtpLLAQAMAoGCCqGSM49BAMCA0cAMEQCIEMdIkwwmzQAJ6RSDT3wcrsySx2B 5Lvx5HGzc43Fgu9eAiAi4sFXnizFBVUL43qXZBq4ARw17o0JW3/7eec1xttQhw== -----END CERTIFICATE----- ` // The PEM-encoded "delegation key", the secret key associated with the // delegation certificate. This is a key for ECDSA with P256 and SHA256. const DcKeyWithDelegationUsage = `-----BEGIN EC PRIVATE KEY----- MHcCAQEEIAS/pGktmxK1hlt3gF4N2nkMrJnoZihvOO63nnNcxXQroAoGCCqGSM49 AwEHoUQDQgAE3ELrmlDSd5KihrOAwXSVXe81s8hgDU+LgTRlZGdnIGg7HRZ8ffXx om34KFnq/By7fWfkuh7PvGwzwRzYE0fy3w== -----END EC PRIVATE KEY----- ` // A certificate without the DelegationUsage extension. const DcCertWithoutDelegationUsage = `-----BEGIN CERTIFICATE----- MIIBajCCAQ+gAwIBAgIRAMUg/VFqJaWWJwZ9iHoMjqIwCgYIKoZIzj0EAwIwEjEQ MA4GA1UEChMHQWNtZSBDbzAeFw0xODA3MzAyMDExMTlaFw0xOTA3MzAyMDExMTla MBIxEDAOBgNVBAoTB0FjbWUgQ28wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATA n+oeWSvSNHhEskSRgkkerCQDoV/NA+r3S5AtCOFT5AYLt8xltSTWerFI/YlZLIcL xlJPT7T+XpBnfS6xaAuxo0YwRDAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYI KwYBBQUHAwEwDAYDVR0TAQH/BAIwADAPBgNVHREECDAGhwR/AAABMAoGCCqGSM49 BAMCA0kAMEYCIQCFGWnoJmwH1rxNCKBJWVDBKDTSsYhySRk4h9RPyR8bUwIhAJxc KFyrowMTan791RJnyANH/4uYhmvkfhfrFGSTXUli -----END CERTIFICATE----- ` // The secret key associatted with DcCertWithoutDelegationUsage. const DcKeyWithoutDelegationUsage = `-----BEGIN EC PRIVATE KEY----- MHcCAQEEIEP82pOhzx0tKkky9t0OmUo9MHgmfdAHxDN2cHmWGqOhoAoGCCqGSM49 AwEHoUQDQgAEwJ/qHlkr0jR4RLJEkYJJHqwkA6FfzQPq90uQLQjhU+QGC7fMZbUk 1nqxSP2JWSyHC8ZST0+0/l6QZ30usWgLsQ== -----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 } // Use with maxVersion == VersionTLS13. const DcTestDataTLS13PEM = `-----BEGIN DC TEST DATA----- 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 jreml8HfVQ== -----END DC TEST DATA-----` // Parses the input PEM block containing the test DCs. func dcLoadTestData(in []byte, out *[]dcTestDC) error { block, _ := pem.Decode(in) if block == nil { return errors.New("failed to decode DC tests PEM block") } // Parse the DER-encoded test DCs. _, err := asn1.Unmarshal(block.Bytes, out) if err != nil { return errors.New("failed to unmarshal DC test ASN.1 data") } // Check that the test data is for the right version. This should be // maxVersion, defined in common.go. for _, test := range *out { dc, err := unmarshalDelegatedCredential(test.DC) if err != nil { return err } // Sanity check that test version matches the version encoded by the DC. testVersion := uint16(test.Version) if dc.cred.expectedVersion != testVersion { return fmt.Errorf( "test version doesn't match credential version: got: 0x0%04x; want: 0x%04x", testVersion, dc.cred.expectedVersion) } // With the exception of "badvers" and "tsl12", all test DCs should have // the expected verison. if test.Name != "badvers" && test.Name != "tls12" && testVersion != maxVersion { return fmt.Errorf( "encountered test with wrong version: got: 0x0%04x; want: 0x%04x", test.Version, maxVersion) } } return nil } var dcTestDCs []dcTestDC var dcTestConfig *Config var dcTestDelegationCert Certificate var dcTestCert Certificate var dcTestNow time.Time func init() { // Load the DC test data. var testData []byte if maxVersion != 0x0304 { panic(fmt.Errorf("no test data for version %04x", maxVersion)) } testData = []byte(DcTestDataTLS13PEM) err := dcLoadTestData(testData, &dcTestDCs) if err != nil { panic(err) } // The base configuration for the client and server. dcTestConfig = &Config{ Time: func() time.Time { return dcTestNow }, Rand: zeroSource{}, Certificates: nil, MinVersion: VersionTLS10, MaxVersion: VersionTLS13, CipherSuites: allCipherSuites(), } // The delegation certificate. dcTestDelegationCert, err = X509KeyPair([]byte(DcCertWithDelegationUsage), []byte(DcKeyWithDelegationUsage)) 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(DcCertWithoutDelegationUsage), []byte(DcKeyWithoutDelegationUsage)) if err != nil { panic(err) } dcTestCert.Leaf, err = x509.ParseCertificate(dcTestCert.Certificate[0]) if err != nil { panic(err) } // For testing purposes, use the point at which the test DCs were generated // as the current time. This is the same as the time at which the // delegation certificate was generated. dcTestNow = dcTestDelegationCert.Leaf.NotBefore // 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, VersionTLS13, VersionTLS13, 0, "tls13p256", true, true, "tls13"}, {true, true, false, VersionTLS13, VersionTLS13, 0, "tls13p521", true, true, "tls13"}, {true, false, false, VersionTLS13, VersionTLS13, 0, "tls13p256", true, false, "server no dc"}, {true, true, false, VersionTLS12, VersionTLS13, 0, "tls13p256", true, false, "client old"}, {true, true, false, VersionTLS13, VersionTLS12, 0, "tls13p256", true, false, "server old"}, {true, true, false, VersionTLS13, VersionTLS13, 0, "badkey", false, false, "bad key"}, {true, true, true, VersionTLS13, VersionTLS13, 0, "badsig", true, true, "bad key, skip verify"}, {true, true, false, VersionTLS13, VersionTLS13, dcMaxTTL, "tls13", false, false, "expired dc"}, {true, true, false, VersionTLS13, VersionTLS13, 0, "badvers", false, false, "dc wrong version"}, {true, true, false, VersionTLS12, VersionTLS12, 0, "tls12", true, false, "tls12"}, } // 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) { if vers < VersionTLS13 { return nil, nil, nil } 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) } } }