blob: ade2a8e97bbc2b1a4f1febf42eecffbd9b868207 [file] [log] [blame]
// Copyright 2015 The Vanadium 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 security
import (
"bytes"
"crypto/elliptic"
"fmt"
"reflect"
"testing"
"time"
"v.io/v23/verror"
)
func TestCertificateDigest(t *testing.T) {
// This test generates a bunch of Certificates and Signatures using the reflect package
// to ensure that ever single field of these two is excercised.
//
// Then with this "comprehensive" set of certificates and signatures, it ensures that:
// (1) No two certificates with different fields have the same message digest
// (2) No two certificates when hashed with distinct parent signatures have the same message digest.
// (3) Except, the "Signature" field in the certificates should not be included in the message digest.
var (
// Array of Certificate and Signature where the i-th element differs from the (i-1)th in exactly one field.
certificates = make([]Certificate, 1)
signatures = make([]Signature, 1)
numtested = 0
v = func(item interface{}) reflect.Value { return reflect.ValueOf(item) }
// type of field in Certificate/Signature to a set of values to test against.
type2values = map[reflect.Type][]reflect.Value{
reflect.TypeOf(""): []reflect.Value{v("a"), v("b")},
reflect.TypeOf(Hash("")): []reflect.Value{v(SHA256Hash), v(SHA384Hash)},
reflect.TypeOf([]byte{}): []reflect.Value{v([]byte{1}), v([]byte{2})},
reflect.TypeOf([]Caveat{}): []reflect.Value{v([]Caveat{newCaveat(NewMethodCaveat("Method"))}), v([]Caveat{newCaveat(NewExpiryCaveat(time.Now()))})},
}
hashfn = SHA256Hash // hash function used to compute the message digest in tests.
)
defer func() {
// Paranoia: Most of the tests are gated by loops on the size of "certificates" and "signatures",
// so a small bug might cause many loops to be skipped. This sanity check tries to detect such test
// bugs by counting the expected number of digests that were generated and tested.
// - len(certificates) = 3 fields * 2 values + empty cert = 7
// Thus, number of certificate pairs = 7C2 = 21
// - len(signatures) = 4 fields * 2 values each + empty = 9
// Thus, number of signature pairs = 9C2 = 36
//
// Tests:
// - digests should be different for each Certificate: 21 hash comparisons
// - digests should depend on the chaining of certificates: 21 hash comparisons
// - content digests should not depend on the Signature: 8 hash comparisons
// - digests should depend on the Signature: 36 hash comparisons
if got, want := numtested, 21+21+36+8; got != want {
t.Fatalf("Executed %d tests, expected %d", got, want)
}
}()
// Generate a bunch of certificates (adding them to certs), each with one field
// different from the previous one. No two certificates should have the same
// digest (since they differ in content). Exclude the Signature field since
// that does not affect the digest.
for typ, idx := reflect.TypeOf(Certificate{}), 0; idx < typ.NumField(); idx++ {
field := typ.Field(idx)
if field.Name == "Signature" {
continue
}
values := type2values[field.Type]
if len(values) == 0 {
t.Fatalf("No sample values for field %q of type %v", field.Name, field.Type)
}
cert := certificates[len(certificates)-1] // copy of the last certificate
for _, v := range values {
reflect.ValueOf(&cert).Elem().Field(idx).Set(v)
certificates = append(certificates, cert)
}
}
// Similarly, generate a bunch of signatures.
for typ, idx := reflect.TypeOf(Signature{}), 0; idx < typ.NumField(); idx++ {
field := typ.Field(idx)
values := type2values[field.Type]
if len(values) == 0 {
t.Fatalf("No sample values for field %q of type %v", field.Name, field.Type)
}
sig := signatures[len(signatures)-1]
for _, v := range values {
reflect.ValueOf(&sig).Elem().Field(idx).Set(v)
signatures = append(signatures, sig)
}
}
// We have generated a bunch of test data: Certificates with all fields.
// TEST: No two certificates should have the same digests.
digests, contentDigests := make([][]byte, len(certificates)), make([][]byte, len(certificates))
for i, cert := range certificates {
digests[i], contentDigests[i] = cert.chainedDigests(hashfn, nil)
}
for i := 0; i < len(digests); i++ {
for j := i + 1; j < len(digests); j++ {
numtested++
if bytes.Equal(digests[i], digests[j]) {
t.Errorf("Certificates:{%+v} and {%+v} have the same message digest", certificates[i], certificates[j])
}
if bytes.Equal(contentDigests[i], contentDigests[j]) {
t.Errorf("Certificates:{%+v} and {%+v} have the same content digest", certificates[i], certificates[j])
}
}
}
// TEST: The digests should change with chaining.
certDigests := digests
digests, contentDigests = make([][]byte, len(certificates)), make([][]byte, len(certificates))
cert := certificates[len(certificates)-1] // The last certificate
for i := 0; i < len(digests); i++ {
digests[i], contentDigests[i] = cert.chainedDigests(hashfn, certDigests[i])
}
for i := 0; i < len(digests); i++ {
for j := i + 1; j < len(digests); j++ {
numtested++
if bytes.Equal(digests[i], digests[j]) {
t.Errorf("Certificate digest is the same for two different certificate chains - {%v} chained to {%v} and {%v}", cert, certificates[i], certificates[j])
}
if bytes.Equal(contentDigests[i], contentDigests[j]) {
t.Errorf("Content digest is the same for two different certificate chains - {%v} chained to {%v} and {%v}", cert, certificates[i], certificates[j])
}
}
}
// TEST: The Signature field within a certificate itself should not
// affect the content digest but will affect the full digest.
digests, contentDigests = make([][]byte, len(signatures)), make([][]byte, len(signatures))
for i, sig := range signatures {
cert := Certificate{Signature: sig}
digests[i], contentDigests[i] = cert.chainedDigests(hashfn, nil)
}
for i := 1; i < len(contentDigests); i++ {
numtested++
if !bytes.Equal(contentDigests[i], contentDigests[i-1]) {
cert1 := Certificate{Signature: signatures[i]}
cert2 := Certificate{Signature: signatures[i-1]}
t.Errorf("Certificate{%v} and {%v} which differ only in the signature field have different content digests", cert1, cert2)
}
}
for i := 0; i < len(digests); i++ {
for j := i + 1; j < len(digests); j++ {
numtested++
if bytes.Equal(digests[i], digests[j]) {
cert1 := Certificate{Signature: signatures[i]}
cert2 := Certificate{Signature: signatures[j]}
t.Errorf("Certificate{%v} and {%v} have different signatures but the same digests", cert1, cert2)
}
}
}
}
func TestChainSignatureUsesDigestWithStrengthComparableToSigningKey(t *testing.T) {
tests := []struct {
curve elliptic.Curve
hash Hash
nBytes int
}{
{elliptic.P224(), SHA256Hash, 32},
{elliptic.P256(), SHA256Hash, 32},
{elliptic.P384(), SHA384Hash, 48},
{elliptic.P521(), SHA512Hash, 64},
}
for idx, test := range tests {
var cert Certificate
digest, contentDigest := cert.chainedDigests(test.hash, nil)
if got, want := len(digest), test.nBytes; got != want {
t.Errorf("Got digest of %d bytes, want %d for hash function %q", got, want, test.hash)
continue
}
if got, want := len(contentDigest), test.nBytes; got != want {
t.Errorf("Got content digest of %d bytes, want %d for hash function %q", got, want, test.hash)
continue
}
signer := newECDSASigner(t, test.curve)
chain, _, err := chainCertificate(signer, nil, cert)
if err != nil {
t.Errorf("chainCertificate for test #%d (hash:%q) failed: %v", idx, test.hash, err)
continue
}
cert = chain[0]
if !cert.Signature.Verify(signer.PublicKey(), contentDigest) {
t.Errorf("Incorrect hash function used by sign. Test #%d, expected hash:%q", idx, test.hash)
continue
}
}
}
func TestChainMixing(t *testing.T) {
var (
// Private and public keys
sRoot = newECDSASigner(t, elliptic.P256())
pRoot, _ = sRoot.PublicKey().MarshalBinary()
sUser = newECDSASigner(t, elliptic.P256())
pUser, _ = sUser.PublicKey().MarshalBinary()
pDelegate, _ = newECDSASigner(t, elliptic.P256()).PublicKey().MarshalBinary()
// Individual certificates
cRoot1 = Certificate{Extension: "alpha", PublicKey: pRoot}
cRoot2 = Certificate{Extension: "beta", PublicKey: pRoot}
cUser = Certificate{Extension: "user", PublicKey: pUser}
cDelegate = Certificate{Extension: "delegate", PublicKey: pDelegate}
// Certificate chains
C1, _, _ = chainCertificate(sRoot, nil, cRoot1) // alpha
C2, _, _ = chainCertificate(sRoot, nil, cRoot2) // beta
C3, _, _ = chainCertificate(sRoot, C1, cUser) // alpha:user
C4, _, _ = chainCertificate(sUser, C3, cDelegate) // alpha:user:delegate
Cbad, _, _ = chainCertificate(sUser, []Certificate{C2[0], C3[1]}, cDelegate) // malformed beta:user:delegate
validate = func(chain []Certificate, expectedKeyBytes []byte, expectedError verror.ID) error {
var expectedKey PublicKey
var err error
if len(expectedKeyBytes) > 0 {
if expectedKey, err = UnmarshalPublicKey(expectedKeyBytes); err != nil {
return err
}
}
// Run all validations twice to account for caching of certificate verifications.
for i := 1; i <= 2; i++ {
p, digest, err := validateCertificateChain(chain)
if !reflect.DeepEqual(expectedKey, p) {
return fmt.Errorf("Got (%v, %v) wanted (%v, %q) on call #%d to validateCertificateChain", p, err, expectedKey, expectedError, i)
}
if got, want := verror.ErrorID(err), expectedError; got != want {
return fmt.Errorf("Got error %v (id=%q) want error id=%q on call #%d to validateCertificateChain", err, got, want, i)
}
if got, want := (err == nil), (len(digest) != 0); got != want {
return fmt.Errorf("Validation error:%v, Digest:%v on call #%d to validateCertificateChain", got, want, i)
}
}
return nil
}
tests = []struct {
Chain []Certificate
PublicKey []byte
Error verror.ID
}{
{C1, pRoot, ""},
{C2, pRoot, ""},
{C3, pUser, ""},
{C4, pDelegate, ""},
{[]Certificate{C1[0], C3[1]}, pUser, ""}, // Same as C3
{Cbad, nil, errBadCertSignature.ID},
}
)
for idx, test := range tests {
if err := validate(test.Chain, test.PublicKey, test.Error); err != nil {
t.Errorf("Test #%d: %v", idx, err)
}
}
// And repeat the tests clearing the cache between every test case.
for idx, test := range tests {
signatureCache.disable() // clears the cache too
signatureCache.enable()
if err := validate(test.Chain, test.PublicKey, test.Error); err != nil {
t.Errorf("Test #%d: %v", idx, err)
}
}
}
func benchmarkDigestsForCertificateChain(b *testing.B, ncerts int) {
chain := makeBlessings(b, ncerts).chains[0]
b.ResetTimer()
for i := 0; i < b.N; i++ {
digestsForCertificateChain(chain)
}
}
func BenchmarkDigestsForCertificateChain_1Cert(b *testing.B) {
benchmarkDigestsForCertificateChain(b, 1)
}
func BenchmarkDigestsForCertificateChain_3Certs(b *testing.B) {
benchmarkDigestsForCertificateChain(b, 3)
}
func BenchmarkDigestsForCertificateChain_4Certs(b *testing.B) {
benchmarkDigestsForCertificateChain(b, 4)
}