| // 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/ecdsa" |
| "crypto/elliptic" |
| "crypto/rand" |
| "reflect" |
| "testing" |
| "time" |
| |
| "v.io/v23/context" |
| "v.io/v23/vom" |
| ) |
| |
| func newSigner() Signer { |
| key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) |
| if err != nil { |
| panic(err) |
| } |
| return NewInMemoryECDSASigner(key) |
| } |
| |
| // Log the "on-the-wire" sizes for blessings (which are shipped during the |
| // authentication protocol). |
| // As of February 27, 2015, the numbers were: |
| // Marshaled P256 ECDSA key : 91 bytes |
| // Major components of an ECDSA signature : 64 bytes |
| // VOM type information overhead for blessings: 354 bytes |
| // Blessing with 1 certificates : 536 bytes (a) |
| // Blessing with 2 certificates : 741 bytes (a:a) |
| // Blessing with 3 certificates : 945 bytes (a:a:a) |
| // Blessing with 4 certificates : 1149 bytes (a:a:a:a) |
| // Marshaled caveat : 55 bytes (0xa64c2d0119fba3348071feeb2f308000(time.Time=0001-01-01 00:00:00 +0000 UTC)) |
| // Marshaled caveat : 6 bytes (0x54a676398137187ecdb26d2d69ba0003([]string=[m])) |
| func TestByteSize(t *testing.T) { |
| blessingsize := func(b Blessings) int { |
| buf, err := vom.Encode(b) |
| if err != nil { |
| t.Fatal(err) |
| } |
| return len(buf) |
| } |
| key, err := newSigner().PublicKey().MarshalBinary() |
| if err != nil { |
| t.Fatal(err) |
| } |
| var sigbytes int |
| if sig, err := newSigner().Sign([]byte("purpose"), []byte("message")); err != nil { |
| t.Fatal(err) |
| } else { |
| sigbytes = len(sig.R) + len(sig.S) |
| } |
| t.Logf("Marshaled P256 ECDSA key : %4d bytes", len(key)) |
| t.Logf("Major components of an ECDSA signature : %4d bytes", sigbytes) |
| // Byte sizes of blessings (with no caveats in any certificates). |
| t.Logf("VOM type information overhead for blessings: %4d bytes", blessingsize(Blessings{})) |
| for ncerts := 1; ncerts < 5; ncerts++ { |
| b := makeBlessings(t, ncerts) |
| t.Logf("Blessing with %d certificates : %4d bytes (%v)", ncerts, blessingsize(b), b) |
| } |
| // Byte size of framework caveats. |
| logCaveatSize := func(c Caveat, err error) { |
| if err != nil { |
| t.Fatal(err) |
| } |
| t.Logf("Marshaled caveat : %4d bytes (%v)", len(c.ParamVom), &c) |
| } |
| logCaveatSize(NewExpiryCaveat(time.Now())) |
| logCaveatSize(NewMethodCaveat("m")) |
| } |
| |
| func TestBlessingCouldHaveNames(t *testing.T) { |
| falseCaveat, err := NewCaveat(ConstCaveat, false) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| bless := func(p Principal, key PublicKey, with Blessings, extension string) Blessings { |
| b, err := p.Bless(key, with, extension, falseCaveat) |
| if err != nil { |
| t.Fatal(err) |
| } |
| return b |
| } |
| |
| var ( |
| alice = newPrincipal(t) |
| bob = newPrincipal(t) |
| |
| bbob = blessSelf(t, bob, "bob:tablet") |
| |
| balice1 = blessSelf(t, alice, "alice") |
| balice2 = blessSelf(t, alice, "alice:phone:youtube", falseCaveat) |
| balice3 = bless(bob, alice.PublicKey(), bbob, "friend") |
| balice, _ = UnionOfBlessings(balice1, balice2, balice3) |
| ) |
| |
| tests := []struct { |
| names []string |
| result bool |
| }{ |
| {[]string{"alice", "alice:phone:youtube", "bob:tablet:friend"}, true}, |
| {[]string{"alice", "alice:phone:youtube"}, true}, |
| {[]string{"alice:phone:youtube", "bob:tablet:friend"}, true}, |
| {[]string{"alice", "bob:tablet:friend"}, true}, |
| {[]string{"alice"}, true}, |
| {[]string{"alice:phone:youtube"}, true}, |
| {[]string{"bob:tablet:friend"}, true}, |
| {[]string{"alice:tablet"}, false}, |
| {[]string{"alice:phone"}, false}, |
| {[]string{"bob:tablet"}, false}, |
| {[]string{"bob:tablet:friend:spouse"}, false}, |
| {[]string{"carol:phone"}, false}, |
| } |
| for _, test := range tests { |
| if got, want := balice.CouldHaveNames(test.names), test.result; got != want { |
| t.Errorf("%v.CouldHaveNames(%v): got %v, want %v", balice, test.names, got, want) |
| } |
| } |
| } |
| |
| func TestBlessingsExpiry(t *testing.T) { |
| p, err := CreatePrincipal(newSigner(), nil, nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| now := time.Now() |
| oneHour := now.Add(time.Hour) |
| twoHour := now.Add(2 * time.Hour) |
| oneHourCav, err := NewExpiryCaveat(oneHour) |
| if err != nil { |
| t.Fatal(err) |
| } |
| twoHourCav, err := NewExpiryCaveat(twoHour) |
| if err != nil { |
| t.Fatal(err) |
| } |
| // twoHourB should expiry in two hours. |
| twoHourB, err := p.BlessSelf("self", twoHourCav) |
| if err != nil { |
| t.Fatal(err) |
| } |
| // oneHourB should expiry in one hour. |
| oneHourB, err := p.BlessSelf("self", oneHourCav, twoHourCav) |
| if err != nil { |
| t.Fatal(err) |
| } |
| // noExpiryB should never expiry. |
| noExpiryB, err := p.BlessSelf("self", UnconstrainedUse()) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if exp := noExpiryB.Expiry(); !exp.IsZero() { |
| t.Errorf("got %v, want %v", exp, time.Time{}) |
| } |
| if got, want := oneHourB.Expiry().UTC(), oneHour.UTC(); got != want { |
| t.Errorf("got %v, want %v", got, want) |
| } |
| if got, want := twoHourB.Expiry().UTC(), twoHour.UTC(); got != want { |
| t.Errorf("got %v, want %v", got, want) |
| } |
| } |
| |
| func TestBlessingsUniqueID(t *testing.T) { |
| var ( |
| palice = newPrincipal(t) |
| pbob = newPrincipal(t) |
| |
| // Create blessings using all the methods available to create |
| // them: Bless, BlessSelf, UnionOfBlessings, NamelessBlessings. |
| nameless, _ = NamelessBlessing(palice.PublicKey()) |
| alice = blessSelf(t, palice, "alice") |
| bob = blessSelf(t, pbob, "bob") |
| bobfriend, _ = pbob.Bless(alice.PublicKey(), bob, "friend", UnconstrainedUse()) |
| bobspouse, _ = pbob.Bless(alice.PublicKey(), bob, "spouse", UnconstrainedUse()) |
| |
| u1, _ = UnionOfBlessings(alice, bobfriend, bobspouse) |
| u2, _ = UnionOfBlessings(bobfriend, bobspouse, alice) |
| |
| all = []Blessings{nameless, alice, bob, bobfriend, bobspouse, u1} |
| ) |
| // Each individual blessing should have a different UniqueID, and different from u1 |
| for i := 0; i < len(all); i++ { |
| b1 := all[i] |
| for j := i + 1; j < len(all); j++ { |
| if b2 := all[j]; bytes.Equal(b1.UniqueID(), b2.UniqueID()) { |
| t.Errorf("%q and %q have the same UniqueID!", b1, b2) |
| } |
| } |
| // Each blessings object must have a unique ID (whether created |
| // by blessing self, blessed by another principal, or |
| // roundtripped through VOM) |
| if len(b1.UniqueID()) == 0 { |
| t.Errorf("%q has no UniqueID", b1) |
| } |
| serialized, err := vom.Encode(b1) |
| if err != nil { |
| t.Errorf("%q failed VOM encoding: %v", b1, err) |
| } |
| var deserialized Blessings |
| if err := vom.Decode(serialized, &deserialized); err != nil || !bytes.Equal(b1.UniqueID(), deserialized.UniqueID()) { |
| t.Errorf("%q: UniqueID mismatch after VOM round-tripping. VOM decode error: %v", b1, err) |
| } |
| } |
| // u1 and u2 should have the same UniqueID |
| if !bytes.Equal(u1.UniqueID(), u2.UniqueID()) { |
| t.Errorf("%q and %q have different UniqueIDs", u1, u2) |
| } |
| |
| // Finally, two blessings with the same name but different public keys |
| // should not have the same unique id. |
| const commonName = "alice" |
| var ( |
| alice1 = blessSelf(t, palice, commonName) |
| alice2 = blessSelf(t, pbob, commonName) |
| ) |
| if bytes.Equal(alice1.UniqueID(), alice2.UniqueID()) { |
| t.Errorf("Blessings for different public keys but the same name have the same unique id!") |
| } |
| } |
| |
| func TestRootBlessings(t *testing.T) { |
| falseCaveat, err := NewCaveat(ConstCaveat, false) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| bless := func(p Principal, key PublicKey, with Blessings, extension string) Blessings { |
| b, err := p.Bless(key, with, extension, falseCaveat) |
| if err != nil { |
| t.Fatal(err) |
| } |
| return b |
| } |
| |
| union := func(b ...Blessings) Blessings { |
| ret, err := UnionOfBlessings(b...) |
| if err != nil { |
| t.Fatal(err) |
| } |
| return ret |
| } |
| |
| var ( |
| alpha = newPrincipal(t) |
| beta = newPrincipal(t) |
| gamma = newPrincipal(t) |
| alice = newPrincipal(t) |
| |
| balpha = blessSelf(t, alpha, "alpha") |
| bbeta = blessSelf(t, beta, "beta") |
| bgamma = blessSelf(t, gamma, "gamma") |
| balice = blessSelf(t, alice, "alice") |
| |
| bAlphaFriend = bless(alpha, alice.PublicKey(), balpha, "friend") |
| bBetaEnemy = bless(beta, alice.PublicKey(), bbeta, "enemy") |
| bGammaAcquaintanceFriend = bless(alpha, alice.PublicKey(), bless(gamma, alpha.PublicKey(), bgamma, "acquaintance"), "friend") |
| |
| tests = []struct { |
| b Blessings |
| r []Blessings |
| }{ |
| {balice, []Blessings{balice}}, |
| {bAlphaFriend, []Blessings{balpha}}, |
| {union(balice, bAlphaFriend), []Blessings{balice, balpha}}, |
| {union(bAlphaFriend, bBetaEnemy, bGammaAcquaintanceFriend), []Blessings{balpha, bbeta, bgamma}}, |
| } |
| ) |
| for _, test := range tests { |
| roots := RootBlessings(test.b) |
| if got, want := roots, test.r; !reflect.DeepEqual(got, want) { |
| t.Errorf("%v: Got %#v, want %#v", test.b, got, want) |
| } |
| // Since these RootBlessings are carefully constructed, use a |
| // VOM-roundtrip to ensure they are properly so. |
| serialized, err := vom.Encode(roots) |
| if err != nil { |
| t.Errorf("%v: vom encoding error: %v", test.b, err) |
| } |
| var deserialized []Blessings |
| if err := vom.Decode(serialized, &deserialized); err != nil || !reflect.DeepEqual(roots, deserialized) { |
| t.Errorf("%v: Failed roundtripping: Got %#v want %#v", test.b, roots, deserialized) |
| } |
| } |
| } |
| |
| func TestNamelessBlessing(t *testing.T) { |
| var ( |
| alice = newPrincipal(t) |
| bob = newPrincipal(t) |
| balice, _ = NamelessBlessing(alice.PublicKey()) |
| baliceagain, _ = NamelessBlessing(alice.PublicKey()) |
| bbob, _ = NamelessBlessing(bob.PublicKey()) |
| ) |
| if got, want := balice.PublicKey(), alice.PublicKey(); !reflect.DeepEqual(got, want) { |
| t.Errorf("got %v, want %v", got, want) |
| } |
| if got, want := len(balice.ThirdPartyCaveats()), 0; got != want { |
| t.Errorf("got %v tpcavs, want %v", got, want) |
| } |
| if !balice.Equivalent(baliceagain) { |
| t.Errorf("expected blessings to be equivalent") |
| } |
| if balice.Equivalent(bbob) { |
| t.Errorf("expected blessings to differ") |
| } |
| if ua, ub := balice.UniqueID(), bbob.UniqueID(); bytes.Equal(ua, ub) { |
| t.Errorf("%q and %q should not be equal", ua, ub) |
| } |
| if ua, uaa := balice.UniqueID(), baliceagain.UniqueID(); !bytes.Equal(ua, uaa) { |
| t.Errorf("balice and baliceagain uniqueid should be equal") |
| } |
| if balice.CouldHaveNames([]string{"alice"}) { |
| t.Errorf("blessing should not have names") |
| } |
| if !balice.Expiry().IsZero() { |
| t.Errorf("blessing should not expire") |
| } |
| if got, want := balice.String(), ""; got != want { |
| t.Errorf("got %v, want %v", got, want) |
| } |
| if got, want := len(RootBlessings(balice)), 0; got != want { |
| t.Errorf("got %v, want %v", got, want) |
| } |
| if got := SigningBlessings(balice); !got.IsZero() { |
| t.Errorf("got %v, want zero blessings", got) |
| } |
| ctx, _ := context.RootContext() |
| if gotname, gotrejected := SigningBlessingNames(ctx, alice, balice); len(gotname)+len(gotrejected) > 0 { |
| t.Errorf("got %v, %v, want nil, nil", gotname, gotrejected) |
| } |
| call := NewCall(&CallParams{ |
| LocalPrincipal: alice, |
| RemoteBlessings: bbob, |
| }) |
| if gotname, gotrejected := RemoteBlessingNames(ctx, call); len(gotname)+len(gotrejected) > 0 { |
| t.Errorf("got %v, %v, want nil, nil", gotname, gotrejected) |
| } |
| call = NewCall(&CallParams{ |
| LocalPrincipal: alice, |
| LocalBlessings: balice, |
| }) |
| if got := LocalBlessingNames(ctx, call); len(got) > 0 { |
| t.Errorf("got %v, want nil", got) |
| } |
| if got := BlessingNames(alice, balice); len(got) > 0 { |
| t.Errorf("got %v, want nil", got) |
| } |
| var b Blessings |
| if err := roundTrip(balice, &b); err != nil { |
| t.Fatal(err) |
| } |
| if !balice.Equivalent(b) { |
| t.Errorf("got %#v, want %#v", b, balice) |
| } |
| if _, err := alice.Bless(bob.PublicKey(), balice, "friend", UnconstrainedUse()); err == nil { |
| t.Errorf("bless operation should have failed") |
| } |
| // Union of two nameless blessings should be nameless. |
| if got, err := UnionOfBlessings(balice, baliceagain); err != nil || !got.Equivalent(balice) { |
| t.Errorf("got %#v want %#v, (err = %v)", got, balice, err) |
| } |
| // Union of zero and nameless blessings hsoudl be nameless. |
| if got, err := UnionOfBlessings(balice, Blessings{}); err != nil || !got.Equivalent(balice) { |
| t.Errorf("got %#v want %#v, (err = %v)", got, balice, err) |
| } |
| // Union of zero, nameless, and named blessings should be the named blessings. |
| named, err := alice.BlessSelf("named") |
| if err != nil { |
| t.Fatal(err) |
| } |
| if got, err := UnionOfBlessings(balice, Blessings{}, named); err != nil || !got.Equivalent(named) { |
| t.Errorf("got %#v want %#v, (err = %v)", got, named, err) |
| } |
| } |
| |
| func BenchmarkBless(b *testing.B) { |
| p, err := CreatePrincipal(newSigner(), nil, nil) |
| if err != nil { |
| b.Fatal(err) |
| } |
| self, err := p.BlessSelf("self") |
| if err != nil { |
| b.Fatal(err) |
| } |
| // Include at least one caveat as having caveats should be the common case. |
| caveat, err := NewExpiryCaveat(time.Now().Add(time.Hour)) |
| if err != nil { |
| b.Fatal(err) |
| } |
| blessee := newSigner().PublicKey() |
| b.ResetTimer() |
| for i := 0; i < b.N; i++ { |
| if _, err := p.Bless(blessee, self, "friend", caveat); err != nil { |
| b.Fatal(err) |
| } |
| } |
| } |
| |
| func BenchmarkVerifyCertificateIntegrity(b *testing.B) { |
| native := makeBlessings(b, 1) |
| var wire WireBlessings |
| if err := WireBlessingsFromNative(&wire, native); err != nil { |
| b.Fatal(err) |
| } |
| b.ResetTimer() |
| for i := 0; i < b.N; i++ { |
| if err := WireBlessingsToNative(wire, &native); err != nil { |
| b.Fatal(err) |
| } |
| } |
| } |
| |
| func BenchmarkVerifyCertificateIntegrity_NoCaching(b *testing.B) { |
| signatureCache.disable() |
| defer signatureCache.enable() |
| BenchmarkVerifyCertificateIntegrity(b) |
| } |
| |
| func makeBlessings(t testing.TB, ncerts int) Blessings { |
| p, err := CreatePrincipal(newSigner(), nil, nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| b, err := p.BlessSelf("a") |
| if err != nil { |
| t.Fatal(err) |
| } |
| for i := 1; i < ncerts; i++ { |
| p2, err := CreatePrincipal(newSigner(), nil, nil) |
| if err != nil { |
| t.Fatalf("%d: %v", i, err) |
| } |
| b2, err := p.Bless(p2.PublicKey(), b, "a", UnconstrainedUse()) |
| if err != nil { |
| t.Fatalf("%d: %v", i, err) |
| } |
| p = p2 |
| b = b2 |
| } |
| return b |
| } |