blob: fd70b8ce78a096e6c7982af9ef70ed5ca742bdf2 [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 signing_test implements a test for the package
// v.io/x/ref/services/syncbase/signing
package signing_test
import "crypto/sha256"
import "testing"
import "time"
import "v.io/x/ref/services/syncbase/signing"
import "v.io/x/ref/services/syncbase/signing/krl"
import "v.io/v23/naming"
import "v.io/v23/security"
import "v.io/v23/vdl"
import "v.io/v23/vom"
import "v.io/v23/verror"
import "v.io/x/ref/test"
import lib_security "v.io/x/ref/lib/security"
import _ "v.io/x/ref/runtime/factories/generic"
// --------------------------------------
// The following implements a fake security.Call.
type fakeCall struct {
localPrincipal security.Principal
localBlessings security.Blessings
remoteBlessings security.Blessings
}
func (fc *fakeCall) Timestamp() time.Time { return time.Now() }
func (fc *fakeCall) Method() string { return "the_method_name" }
func (fc *fakeCall) MethodTags() []*vdl.Value { return nil }
func (fc *fakeCall) Suffix() string { return "the_suffix" }
func (fc *fakeCall) LocalDischarges() map[string]security.Discharge { return nil }
func (fc *fakeCall) RemoteDischarges() map[string]security.Discharge { return nil }
func (fc *fakeCall) LocalPrincipal() security.Principal { return fc.localPrincipal }
func (fc *fakeCall) LocalBlessings() security.Blessings { return fc.localBlessings }
func (fc *fakeCall) RemoteBlessings() security.Blessings { return fc.remoteBlessings }
func (fc *fakeCall) LocalEndpoint() naming.Endpoint { return naming.Endpoint{} }
func (fc *fakeCall) RemoteEndpoint() naming.Endpoint { return naming.Endpoint{} }
// --------------------------------------
// A principalDesc holds the local state of a single principal in the tests below.
type principalDesc struct {
name string
principal security.Principal
blessings security.Blessings
krl *krl.KRL
authorBlessingsData *signing.BlessingsData
names []string
marshalledBlessings []byte
blessingsHash []byte
validatorData *signing.ValidatorData
validatorHash []byte
cache *signing.ValidationCache
data *signing.DataWithSignature
}
// makePrincipal() returns a pointer to a newly-initialized principalDesc,
// with a unique key, and a single blessing named with its own name.
func makePrincipal(t testing.TB, name string) (desc *principalDesc) {
var err error
desc = new(principalDesc)
desc.name = name
desc.principal, err = lib_security.NewPrincipal()
if err != nil {
t.Fatalf("security.CreatePrincipal %q failed: %v", desc.name, err)
}
desc.blessings, err = desc.principal.BlessSelf(desc.name)
if err != nil {
t.Fatalf("principal.BlessSelf %q failed: %v", desc.name, err)
}
desc.krl = krl.New()
desc.cache = signing.NewValidationCache(5 * time.Second)
return desc
}
// makePrincipals() creares one principal per name, and adds
// the blessings of each to the roots of all.
func makePrincipals(t testing.TB, names ...string) (principals []*principalDesc) {
for i := range names {
principals = append(principals, makePrincipal(t, names[i]))
}
for i := range principals {
for j := range principals {
security.AddToRoots(principals[j].principal, principals[i].blessings)
}
}
return principals
}
// BenchmarkHashData() measures the time taken to do a cryptogrqaphic hash of
// 1kBytes.
func BenchmarkHashData(b *testing.B) {
var block [1024]byte
hasher := sha256.New()
b.ResetTimer()
for i := 0; i < b.N; i++ {
hasher.Write(block[:])
}
}
// BenchmarkSignData() measures the time taken to sign something with
// signing.SignData().
func BenchmarkSignData(b *testing.B) {
ctx, shutdown := test.V23Init()
defer shutdown()
var err error
author := makePrincipal(b, "author")
dataToSign := []signing.Item{signing.ItemData{Value: []byte("hello")}}
b.ResetTimer()
for i := 0; i < b.N; i++ {
author.data, author.authorBlessingsData, err =
signing.SignData(ctx, author.cache, author.principal, author.blessings, dataToSign)
}
if err != nil {
panic(err)
}
}
// BenchmarkSign1000Data() measures the time taken to sign 1000 small data
// items with signing.SignData().
func BenchmarkSign1000Data(b *testing.B) {
ctx, shutdown := test.V23Init()
defer shutdown()
var err error
author := makePrincipal(b, "author")
var dataToSign []signing.Item
for i := 0; i != 1000; i++ {
dataToSign = append(dataToSign, signing.ItemData{Value: []byte("hello")})
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
author.data, author.authorBlessingsData, err =
signing.SignData(ctx, author.cache, author.principal, author.blessings, dataToSign)
}
if err != nil {
panic(err)
}
}
// BenchmarkSignData() measures the time taken to check a validated signature
// with DataWithSignature.Check().
func BenchmarkCheckData(b *testing.B) {
ctx, shutdown := test.V23Init()
defer shutdown()
var err error
principals := makePrincipals(b, "author", "validator", "checker")
author := principals[0]
validator := principals[1]
checker := principals[2]
dataToSign := []signing.Item{signing.ItemData{Value: []byte("hello")}}
author.data, author.authorBlessingsData, err =
signing.SignData(ctx, author.cache, author.principal, author.blessings, dataToSign)
if err != nil {
panic(err)
}
callToValidator := fakeCall{
localPrincipal: validator.principal,
localBlessings: validator.blessings,
remoteBlessings: author.blessings,
}
validator.names, err = author.data.Check(ctx, author.cache, &callToValidator, validator.krl, 24*30*time.Hour)
if err != nil {
panic(err)
}
callToChecker := fakeCall{
localPrincipal: checker.principal,
localBlessings: checker.blessings,
remoteBlessings: validator.blessings,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
checker.names, err = author.data.Check(ctx, author.cache, &callToChecker, checker.krl, 24*30*time.Hour)
}
}
// BenchmarkSign1000Data() measures the time taken to check a validated
// signature over 1000 small data items with DataWithSignature.Check().
func BenchmarkCheck1000Data(b *testing.B) {
ctx, shutdown := test.V23Init()
defer shutdown()
var err error
principals := makePrincipals(b, "author", "validator", "checker")
author := principals[0]
validator := principals[1]
checker := principals[2]
var dataToSign []signing.Item
for i := 0; i != 1000; i++ {
dataToSign = append(dataToSign, signing.ItemData{Value: []byte("hello")})
}
author.data, author.authorBlessingsData, err =
signing.SignData(ctx, author.cache, author.principal, author.blessings, dataToSign)
if err != nil {
panic(err)
}
callToValidator := fakeCall{
localPrincipal: validator.principal,
localBlessings: validator.blessings,
remoteBlessings: author.blessings,
}
validator.names, err = author.data.Check(ctx, author.cache, &callToValidator, validator.krl, 24*30*time.Hour)
if err != nil {
panic(err)
}
callToChecker := fakeCall{
localPrincipal: checker.principal,
localBlessings: checker.blessings,
remoteBlessings: validator.blessings,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
checker.names, err = author.data.Check(ctx, author.cache, &callToChecker, checker.krl, 24*30*time.Hour)
}
}
// BenchmarkMarshallBlessings() measures the time taken to marshal a Blessings.
func BenchmarkMarshallBlessings(b *testing.B) {
var err error
author := makePrincipal(b, "author")
b.ResetTimer()
for i := 0; i < b.N; i++ {
author.marshalledBlessings, err = vom.Encode(author.blessings)
}
if err != nil {
b.Fatalf("vom.Encode failed: %v", err)
}
}
// BenchmarkUnmarshallBlessings() measures the time taken to unmashal a Blessings.
func BenchmarkUnmarshallBlessings(b *testing.B) {
var err error
author := makePrincipal(b, "author")
author.marshalledBlessings, err = vom.Encode(author.blessings)
if err != nil {
b.Fatalf("vom.Encode failed: %v", err)
}
var blessings security.Blessings
b.ResetTimer()
for i := 0; i < b.N; i++ {
err = vom.Decode(author.marshalledBlessings, &blessings)
}
if err != nil {
b.Fatalf("vom.Encode failed: %v", err)
}
}
// BenchmarkMarshallPublicKey() measures the time taken to marshal a PublicKey.
func BenchmarkMarshallPublicKey(b *testing.B) {
var err error
author := makePrincipal(b, "author")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err = author.principal.PublicKey().MarshalBinary()
}
if err != nil {
b.Fatalf("MarshalBinary() failed: %v", err)
}
}
// BenchmarkUnmarshallPublicKey() measures the time taken to unmarshal a PublicKey.
func BenchmarkUnmarshallPublicKey(b *testing.B) {
var err error
author := makePrincipal(b, "author")
var marshalledKey []byte
marshalledKey, err = author.principal.PublicKey().MarshalBinary()
if err != nil {
b.Fatalf("MarshalBinary() failed: %v", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err = security.UnmarshalPublicKey(marshalledKey)
}
if err != nil {
b.Fatalf("MarshalBinary() failed: %v", err)
}
}
// TestSignData() tests that a complete flow of signing, validating, and
// checking works on a DataWithSignature.
func TestSignData(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
var err error
principals := makePrincipals(t, "author", "validator", "checker")
author := principals[0]
validator := principals[1]
checker := principals[2]
// Add each princpipal's blessings to each principal's roots.
pdList := []*principalDesc{author, validator, checker}
for i := 0; i != len(pdList); i++ {
for j := 0; j != len(pdList); j++ {
security.AddToRoots(pdList[j].principal, pdList[i].blessings)
}
}
// --------------------------------------
// Author
// Sign some data.
dataToSign := []signing.Item{
signing.ItemData{Value: []byte("hello")},
signing.ItemData{Value: []byte("world")},
signing.ItemData{Value: []byte("!")},
}
author.data, author.authorBlessingsData, err =
signing.SignData(ctx, author.cache, author.principal, author.blessings, dataToSign)
if err != nil {
t.Fatalf("signing.SignData failed: %v", err)
}
if author.data.IsValidated {
t.Fatalf("signing.SignData generated data with IsValidated set")
}
// --------------------------------------
// Validator
callToValidator := fakeCall{
localPrincipal: validator.principal,
localBlessings: validator.blessings,
remoteBlessings: author.blessings,
}
// The validator receives author.data from the author.
validator.data = new(signing.DataWithSignature)
*validator.data = *author.data
// Initially the validator doesn't have the author BlessingsData.
validator.authorBlessingsData = validator.cache.LookupBlessingsData(ctx, validator.data.BlessingsHash)
if validator.authorBlessingsData != nil {
t.Errorf("found non-nil BlessingsData for validator.data.BlessingsHash in validator's ValidationCache")
}
validator.names, err = validator.data.Check(ctx, validator.cache, &callToValidator, validator.krl, 24*30*time.Hour)
if verror.ErrorID(err) != signing.ErrNeedAuthorBlessingsForHash.ID {
t.Fatalf("validator.data.Check got err %v, want %s", err, signing.ErrNeedAuthorBlessingsForHash.ID)
}
// The validator receives the author's marshalled blessings from the author.
validator.marshalledBlessings = author.authorBlessingsData.MarshalledBlessings
validator.blessingsHash, validator.authorBlessingsData, err = validator.cache.AddWireBlessings(ctx, validator.marshalledBlessings)
if err != nil {
t.Fatalf("validator can't add author's marshalled belssings to its ValidationCache: %v", err)
}
validator.names, err = validator.data.Check(ctx, validator.cache, &callToValidator, validator.krl, 24*30*time.Hour)
if err != nil {
t.Fatalf("validator error calling Check() on data: %v", err)
}
if !validator.data.IsValidated {
t.Fatalf("signing.Check didn't set IsValidated")
}
// Validator's cache should now have the author's BlessingData, and the validator's ValidatorData.
validator.authorBlessingsData = validator.cache.LookupBlessingsData(ctx, validator.data.BlessingsHash)
if validator.authorBlessingsData == nil {
t.Errorf("didn't finf BlessingsData for validator.data.BlessingsHash in validator's ValidationCache")
}
validator.validatorData = validator.cache.LookupValidatorData(ctx, validator.data.ValidatorDataHash)
// --------------------------------------
// Checker
callToChecker := fakeCall{
localPrincipal: checker.principal,
localBlessings: checker.blessings,
remoteBlessings: validator.blessings,
}
// The checker recieves validator.data from the validator, except that
// data item 1 is replaced by its hash, because (for example) the
// check is not allowed to see it.
checker.data = new(signing.DataWithSignature)
*checker.data = *validator.data
checker.data.Data[1] = signing.ItemHash{Value: signing.SumByteVectorWithLength(checker.data.Data[1].(signing.ItemData).Value)}
// Initially the checker doesn't have the author BlessingsData, or the validator ValidatorData.
checker.authorBlessingsData = checker.cache.LookupBlessingsData(ctx, checker.data.BlessingsHash)
if checker.authorBlessingsData != nil {
t.Errorf("found non-nil blessings data for checker.data.BlessingsHash hash in checker's ValidationCache")
}
checker.names, err = checker.data.Check(ctx, checker.cache, &callToChecker, checker.krl, 24*30*time.Hour)
if verror.ErrorID(err) != signing.ErrNeedAuthorBlessingsAndValidatorDataForHash.ID {
t.Fatalf("checker.data.Check got err %v, want %s", err, signing.ErrNeedAuthorBlessingsAndValidatorDataForHash.ID)
}
// The checker receives the author's marshalled blessings from the validator.
checker.marshalledBlessings = validator.marshalledBlessings
checker.blessingsHash, checker.authorBlessingsData, err = checker.cache.AddWireBlessings(ctx, checker.marshalledBlessings)
if err != nil {
t.Fatalf("checker can't add author's marshalled belssings to its ValidationCache: %v", err)
}
checker.names, err = checker.data.Check(ctx, checker.cache, &callToChecker, checker.krl, 24*30*time.Hour)
if verror.ErrorID(err) != signing.ErrNeedValidatorDataForHash.ID {
t.Fatalf("checker.data.Check got err %v, want %s", err, signing.ErrNeedValidatorDataForHash.ID)
}
// The checker receives the validator's data from the validator, passing through the wire format.
wvd := signing.ToWireValidatorData(validator.validatorData)
var vd signing.ValidatorData
vd, err = signing.FromWireValidatorData(&wvd)
if err != nil {
t.Fatalf("signing.FromWireValidatorData got error: %v", err)
}
checker.validatorData = &vd
// The checker adds the ValidatorData to its cache.
checker.validatorHash = checker.cache.AddValidatorData(ctx, checker.validatorData)
// And now the Check() operation should work.
checker.names, err = checker.data.Check(ctx, checker.cache, &callToChecker, checker.krl, 24*30*time.Hour)
if err != nil {
t.Fatalf("checker.data.Check got unexpected err %v", err)
}
}