From aa9189a05fa548bbad80af42a84027a6e9c40737 Mon Sep 17 00:00:00 2001 From: Rasmus Dahlberg Date: Fri, 27 Nov 2020 19:49:24 +0100 Subject: added buildChainFromDerList tests --- crypto.go | 4 +- crypto_test.go | 99 ++++++++++++++++++++++++++++++++++++++++++++++- instance.go | 2 +- instance_test.go | 2 +- x509util/testdata/data.go | 27 +++++++++++++ 5 files changed, 129 insertions(+), 5 deletions(-) diff --git a/crypto.go b/crypto.go index 47c16bb..34451cc 100644 --- a/crypto.go +++ b/crypto.go @@ -30,8 +30,8 @@ func (lp *LogParameters) buildChainFromDerList(derChain [][]byte) ([]*x509.Certi if err != nil { return nil, fmt.Errorf("chain verification failed: %v", err) } - if len(chains) == 0 { - return nil, fmt.Errorf("bad certificate chain length: empty") + if len(chains) == 0 { // better safe than sorry + return nil, fmt.Errorf("chain verification failed: no path") } // there might be several valid chains diff --git a/crypto_test.go b/crypto_test.go index 577244a..b7179f3 100644 --- a/crypto_test.go +++ b/crypto_test.go @@ -7,14 +7,97 @@ import ( "testing" cttestdata "github.com/google/certificate-transparency-go/trillian/testdata" + "github.com/system-transparency/stfe/x509util" + "github.com/system-transparency/stfe/x509util/testdata" ) var ( testLeaf = make([]byte, 64) ) -// TODO: TestBuildChainFromDerList func TestBuildChainFromDerList(t *testing.T) { + for _, table := range []struct { + description string + maxChain int64 // including trust anchor + anchors []byte // pem block + chain [][]byte // der list + wantErr bool + }{ + { + description: "bad chain: cannot be parsed because empty", + maxChain: 3, + anchors: testdata.RootCertificate, + wantErr: true, + }, + { + description: "bad chain: no path from end-entity to intermediate", + maxChain: 3, + anchors: testdata.RootCertificate2, + chain: mustMakeDerList(t, testdata.ChainBadIntermediate)[:2], + wantErr: true, + }, + { + description: "bad chain: no path from intermediate to root", + maxChain: 3, + anchors: testdata.RootCertificate2, + chain: mustMakeDerList(t, testdata.IntermediateChain), + wantErr: true, + }, + { + description: "bad chain: end-entity certificate expired", + maxChain: 3, + anchors: testdata.RootCertificate, + chain: mustMakeDerList(t, testdata.ExpiredChain), + }, + { + description: "bad chain: too large", + maxChain: 2, + anchors: testdata.RootCertificate, + chain: mustMakeDerList(t, testdata.IntermediateChain), + wantErr: true, + }, + { + description: "ok chain: one explicit trust anchor", + maxChain: 3, + anchors: testdata.RootCertificate, + chain: mustMakeDerList(t, testdata.RootChain), + }, + { + description: "ok chain: unnecessary certificates are ignored", + maxChain: 3, + anchors: testdata.RootCertificate, + chain: append(mustMakeDerList(t, testdata.IntermediateChain), mustMakeDerList(t, testdata.IntermediateChain2)...), + }, + { + description: "ok chain: multiple anchors but one valid path", + maxChain: 3, + anchors: testdata.TrustAnchors, + chain: mustMakeDerList(t, testdata.IntermediateChain), + }, + // Note that the underlying verify function also checks name constraints + // and extended key usages. Not relied upon atm, so not tested. + } { + anchorList, err := x509util.NewCertificateList(table.anchors) + if err != nil { + t.Fatalf("must parse trust anchors: %v", err) + } + lp := &LogParameters{ + LogId: testLogId, + TreeId: testTreeId, + Prefix: testPrefix, + MaxRange: testMaxRange, + MaxChain: table.maxChain, + AnchorPool: x509util.NewCertPool(anchorList), + AnchorList: anchorList, + KeyUsage: testExtKeyUsage, + Signer: nil, + HashType: testHashType, + } + _, err = lp.buildChainFromDerList(table.chain) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error=%v but wanted %v in test %q: %v", got, want, table.description, err) + } + } } // TODO: TestVerifySignature @@ -133,3 +216,17 @@ func TestGenV1Sth(t *testing.T) { } // TODO: test that metrics are updated correctly? + +// mustMakeDerList must parse a PEM-encoded list of certificates to DER +func mustMakeDerList(t *testing.T, pem []byte) [][]byte { + certs, err := x509util.NewCertificateList(pem) + if err != nil { + t.Fatalf("must parse pem-encoded certificates: %v", err) + } + + list := make([][]byte, 0, len(certs)) + for _, cert := range certs { + list = append(list, cert.Raw) + } + return list +} diff --git a/instance.go b/instance.go index 6732698..510c7ae 100644 --- a/instance.go +++ b/instance.go @@ -92,7 +92,7 @@ func NewLogParameters(treeId int64, prefix string, anchorPath, keyPath string, m MaxChain: maxChain, AnchorPool: anchorPool, AnchorList: anchorList, - KeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, + KeyUsage: []x509.ExtKeyUsage{}, // placeholder, must be tested if used Signer: key, HashType: crypto.SHA256, }, nil diff --git a/instance_test.go b/instance_test.go index 40a0c57..f4a8fea 100644 --- a/instance_test.go +++ b/instance_test.go @@ -17,7 +17,7 @@ var ( testTreeId = int64(0) testPrefix = "/test" testHashType = crypto.SHA256 - testExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageAny} + testExtKeyUsage = []x509.ExtKeyUsage{} ) func makeTestLogParameters(t *testing.T, signer crypto.Signer) *LogParameters { diff --git a/x509util/testdata/data.go b/x509util/testdata/data.go index a1febdc..46f4ab5 100644 --- a/x509util/testdata/data.go +++ b/x509util/testdata/data.go @@ -154,6 +154,33 @@ MC4CAQAwBQYDK2VwBCIEIKQd3B84w9pB6zJLGljuDyGKfz9uPP6QBeLiFcw0EME4 IntermediateCertificate2, RootCertificate2, }, []byte("\n")) + + // TrustAnchors is composed of two PEM-encoded trust anchors, namely, + // RootCertificate and RootCertificate2. + TrustAnchors = bytes.Join([][]byte{ + RootCertificate, + RootCertificate2, + }, []byte("\n")) + + // ExpiredCertificate is a PEM-encoded certificate that is always expired, + // i.e., `Not Before`=`Not After`. It is signed by IntermediateCertificate. + ExpiredCertificate = []byte(` +-----BEGIN CERTIFICATE----- +MIIBbDCCAR4CFDfeuu6XURfn7AE4WShuwZBHEaLIMAUGAytlcDBsMQswCQYDVQQG +EwJOQTELMAkGA1UECAwCTkExCzAJBgNVBAcMAk5BMQswCQYDVQQKDAJOQTELMAkG +A1UECwwCTkExFjAUBgNVBAMMDXN0ZmUgdGVzdGRhdGExETAPBgkqhkiG9w0BCQEW +Ak5BMB4XDTIwMTEwMzE4MzI0MFoXDTMyMDEyMTE4MzI0MFowRTELMAkGA1UEBhMC +QVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdp +dHMgUHR5IEx0ZDAqMAUGAytlcAMhAJvk390ZvwULplBri03Od4LLz+Sf/OUHu+20 +wik+T9y5MAUGAytlcANBANekliXq4ttoClBJDZoktIQxyHHNcWyXFrj1HlOaT5bC +I3GIqqZ60Ua3jKytnEsKsD2rLMPItDwmG6wYSecy2ws= +-----END CERTIFICATE-----`) + // ExpiredChain is an expired PEM-encoded certificate chain. It is composed + // of two certificates: ExpiredCertificate and IntermediateCertificate. + ExpiredChain = bytes.Join([][]byte{ + ExpiredCertificate, + IntermediateCertificate, + }, []byte("\n")) // ChainBadIntermediate is a PEM-encoded certificate chain that contains // an end-entity certificate, an intermediate certificate, and a root -- cgit v1.2.3