blob: 1dae49ce4c52cbd12ee63447fcaeda0f68f9ff45 [file] [log] [blame]
package security
import (
"bytes"
"crypto/elliptic"
"fmt"
"io"
"math/big"
"reflect"
"sort"
"testing"
"time"
"veyron.io/veyron/veyron2/security"
"veyron.io/veyron/veyron2/security/wire"
"veyron.io/veyron/veyron2/vlog"
"veyron.io/veyron/veyron2/vom"
"veyron.io/veyron/veyron/lib/expect"
"veyron.io/veyron/veyron/lib/modules"
)
type S []string
func TestNewPrivateID(t *testing.T) {
testdata := []struct {
name, err string
}{
{"alice", ""},
{"alice#google", ""},
{"alice@google", ""},
{"bob.smith", ""},
{"", "invalid blessing name"},
{"/", "invalid blessing name"},
{"/alice", "invalid blessing name"},
{"alice/", "invalid blessing name"},
{"google/alice", "invalid blessing name"},
{"google/alice/bob", "invalid blessing name"},
}
for _, d := range testdata {
if _, err := NewPrivateID(d.name, nil); !matchesErrorPattern(err, d.err) {
t.Errorf("NewPrivateID(%q): got: %s, want to match: %s", d.name, err, d.err)
}
}
}
func TestNameAndAuth(t *testing.T) {
var (
cUnknownAlice = newChain("alice").PublicID()
cTrustedAlice = bless(cUnknownAlice, veyronChain, "alice")
cMistrustedAlice = bless(cUnknownAlice, newChain("veyron"), "alice")
cGoogleAlice = bless(cUnknownAlice, googleChain, "alice")
sAlice = newSetPublicID(cTrustedAlice, cGoogleAlice)
sBadAlice = newSetPublicID(cUnknownAlice, cMistrustedAlice)
sGoogleAlice = newSetPublicID(cMistrustedAlice, cGoogleAlice)
)
testdata := []struct {
id security.PublicID
names []string
}{
{id: cUnknownAlice},
{id: cTrustedAlice, names: S{"veyron/alice"}},
{id: cMistrustedAlice},
{id: sAlice, names: S{"veyron/alice", "google/alice"}},
{id: sBadAlice},
{id: sGoogleAlice, names: S{"google/alice"}},
}
for _, d := range testdata {
if got, want := d.id.Names(), d.names; !reflect.DeepEqual(got, want) {
t.Errorf("%q(%T).Names(): got: %q, want: %q", d.id, d.id, got, want)
}
authID, err := d.id.Authorize(NewContext(ContextArgs{}))
if (authID != nil) == (err != nil) {
t.Errorf("%q.Authorize returned: (%v, %v), exactly one return value must be nil", d.id, authID, err)
continue
}
if err := verifyAuthorizedID(d.id, authID, d.names); err != nil {
t.Error(err)
}
}
}
func TestMatch(t *testing.T) {
alice := newChain("alice")
type matchInstance struct {
pattern security.BlessingPattern
want bool
}
testdata := []struct {
id security.PublicID
matchData []matchInstance
}{
{
// self-signed alice chain, not a trusted identity provider so should only match "..."
id: alice.PublicID(),
matchData: []matchInstance{
{pattern: "...", want: true},
{pattern: "alice", want: false},
{pattern: "alice/...", want: false},
},
},
{
// veyron/alice: rooted in the trusted "veyron" identity provider
id: bless(newChain("immaterial").PublicID(), veyronChain, "alice"),
matchData: []matchInstance{
{pattern: "...", want: true},
{pattern: "veyron/...", want: true},
{pattern: "veyron/alice", want: true},
{pattern: "veyron/alice/...", want: true},
{pattern: "veyron/alice/TV", want: true},
{pattern: "veyron", want: false},
{pattern: "veyron/ali", want: false},
{pattern: "veyron/aliced", want: false},
{pattern: "veyron/bob", want: false},
{pattern: "google/alice", want: false},
},
},
{
// alice#veyron/alice#google/alice: two trusted identity providers
id: newSetPublicID(alice.PublicID(), bless(alice.PublicID(), veyronChain, "alice"), bless(alice.PublicID(), googleChain, "alice")),
matchData: []matchInstance{
{pattern: "...", want: true},
// Since alice is not a trusted identity provider, the self-blessed identity
// should not match "alice/..."
{pattern: "alice", want: false},
{pattern: "alice/...", want: false},
{pattern: "veyron/...", want: true},
{pattern: "veyron/alice", want: true},
{pattern: "veyron/alice/TV", want: true},
{pattern: "veyron/alice/...", want: true},
{pattern: "ali", want: false},
{pattern: "aliced", want: false},
{pattern: "veyron", want: false},
{pattern: "veyron/ali", want: false},
{pattern: "veyron/aliced", want: false},
{pattern: "veyron/bob", want: false},
{pattern: "google/alice", want: true},
{pattern: "google/alice/TV", want: true},
{pattern: "google/alice/...", want: true},
},
},
}
for _, d := range testdata {
for _, m := range d.matchData {
if got := m.pattern.MatchedBy(d.id.Names()...); got != m.want {
t.Errorf("%q.Match(%s), Got %t, want %t", d.id, m.pattern, got, m.want)
}
}
}
}
func TestExpiredIdentityChain(t *testing.T) {
id, err := veyronChain.Bless(newChain("immaterial").PublicID(), "alice", time.Millisecond, nil)
if err != nil {
t.Fatal(err)
}
time.Sleep(time.Millisecond)
if authid, _ := id.Authorize(NewContext(ContextArgs{})); authid != nil && len(authid.Names()) != 0 {
t.Errorf("%q.Authorize returned %v, wanted empty slice", id, authid)
}
}
func TestExpiredIdentityInSet(t *testing.T) {
var (
alice = newChain("alice").PublicID()
googleAlice = bless(alice, googleChain, "googler")
)
veyronAlice, err := veyronChain.Bless(alice, "veyroner", time.Millisecond, nil)
if err != nil {
t.Fatal(err)
}
time.Sleep(time.Millisecond)
setid := newSetPublicID(alice, googleAlice, veyronAlice)
authid, _ := setid.Authorize(NewContext(ContextArgs{}))
if authid == nil {
t.Fatalf("%q.Authorize returned nil, expected google/alice", setid)
}
if got, want := authid.Names(), "google/googler"; len(got) != 1 || got[0] != want {
t.Errorf("Got %v want [%v]", got, want)
}
}
func TestTamperedIdentityChain(t *testing.T) {
alice := newChain("alice").PublicID().(*chainPublicID)
nCerts := len(alice.certificates)
// Tamper with alice's public key, in a way that it can be decoded into a valid key,
// just different from what was originally encoded.
xy := alice.certificates[nCerts-1].PublicKey.XY
x, y := elliptic.Unmarshal(elliptic.P256(), xy)
y = y.Add(y, big.NewInt(1))
alice.certificates[nCerts-1].PublicKey.XY = elliptic.Marshal(elliptic.P256(), x, y)
if _, err := alice.certificates[nCerts-1].PublicKey.Decode(); err != nil {
t.Fatal(err)
}
if _, err := roundTrip(alice); err != wire.ErrNoIntegrity {
t.Errorf("Got %v want %v from roundTrip(%v)", err, wire.ErrNoIntegrity, alice)
}
}
func TestBless(t *testing.T) {
var (
cAlice = newChain("alice")
cBob = newChain("bob").PublicID()
cVeyronAlice = derive(bless(cAlice.PublicID(), veyronChain, "alice"), cAlice)
cGoogleAlice = derive(bless(cAlice.PublicID(), googleChain, "alice"), cAlice)
cVeyronBob = bless(cBob, veyronChain, "bob")
sVeyronAlice = newSetPrivateID(cAlice, cVeyronAlice, cGoogleAlice)
sVeyronBob = newSetPublicID(cBob, cVeyronBob)
)
testdata := []struct {
blessor security.PrivateID
blessee security.PublicID
blessing string // name provided to security.PublicID.Bless
blessed []string // names of the blessed identity. Empty if the Bless operation should have failed
err string
}{
// Blessings from veyron/alice (chain implementation)
{
blessor: cVeyronAlice,
blessee: cBob,
err: `invalid blessing name:""`,
},
{
blessor: cVeyronAlice,
blessee: cBob,
blessing: "alice/bob",
err: `invalid blessing name:"alice/bob"`,
},
{
blessor: cVeyronAlice,
blessee: cBob,
blessing: "friend_bob",
blessed: S{"veyron/alice/friend_bob"},
},
{
blessor: cVeyronAlice,
blessee: sVeyronBob,
blessing: "friend_bob",
blessed: S{"veyron/alice/friend_bob"},
},
// Blessings from alice#google/alice#veyron/alice
{
blessor: sVeyronAlice,
blessee: cBob,
err: `invalid blessing name:""`,
},
{
blessor: sVeyronAlice,
blessee: cBob,
blessing: "alice/bob",
err: `invalid blessing name:"alice/bob"`,
},
{
blessor: sVeyronAlice,
blessee: cBob,
blessing: "friend_bob",
blessed: S{"veyron/alice/friend_bob", "google/alice/friend_bob"},
},
{
blessor: sVeyronAlice,
blessee: sVeyronBob,
blessing: "friend_bob",
blessed: S{"veyron/alice/friend_bob", "google/alice/friend_bob"},
},
}
for _, d := range testdata {
if (len(d.blessed) == 0) == (len(d.err) == 0) {
t.Fatalf("Bad testdata. Exactly one of blessed and err must be non-empty: %+v", d)
}
blessed, err := d.blessor.Bless(d.blessee, d.blessing, 1*time.Minute, nil)
// Exactly one of (blessed, err) should be nil
if (blessed != nil) == (err != nil) {
t.Errorf("%q.Bless(%q, %q, ...) returned: (%v, %v): exactly one return value should be nil", d.blessor, d.blessee, d.blessing, blessed, err)
continue
}
// err should match d.err
if !matchesErrorPattern(err, d.err) {
t.Errorf("%q.Bless(%q, %q, ...) returned error: %v, want to match: %q", d.blessor, d.blessee, d.blessing, err, d.err)
}
if err != nil {
continue
}
// Compare names
if got, want := blessed.Names(), d.blessed; !reflect.DeepEqual(got, want) {
t.Errorf("%q.Names(): got: %q, want: %q", blessed, got, want)
}
// Public keys should match for blessed and blessee
if !reflect.DeepEqual(blessed.PublicKey(), d.blessee.PublicKey()) {
t.Errorf("PublicKey mismatch in %q.Bless(%q, %q, ...)", d.blessor, d.blessee, d.blessing)
}
// Verify wire encoding of the blessed
if _, err := roundTrip(blessed); err != nil {
t.Errorf("roundTrip(%q) failed: %v (from %q.Bless(%q, %q, ...))", blessed, err, d.blessor, d.blessee, d.blessing)
}
}
}
type unregisteredCaveat struct {
// TODO(ataly, ashankar): The int is embedded in order to
// distinguish the signature of this type from other Caveat types
// and therefore preventing VOM from decoding values of this type
// into another type. This is still not foolproof, we need to figure
// out a better way of preventing the accidental decoding into another
// type.
UniqueField int
}
func (unregisteredCaveat) Validate(security.Context) error {
return nil
}
func encodeUnregisteredCaveat(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
cav, err := security.NewCaveat(unregisteredCaveat{1})
if err != nil {
vlog.Fatalf("security.NewCaveat failed: %s", err)
}
var buf bytes.Buffer
if err := vom.NewEncoder(&buf).Encode(cav); err != nil {
vlog.Fatalf("could not VOM-encode caveat: %s", err)
}
fmt.Fprintln(stdout, string(buf.Bytes()))
modules.WaitForEOF(stdin)
return nil
}
func TestHelperProcess(t *testing.T) {
modules.DispatchInTest()
}
func TestAuthorizeWithCaveats(t *testing.T) {
var (
// alice
pcAlice = newChain("alice")
cAlice = pcAlice.PublicID().(*chainPublicID)
// Some random server called bob
bob = newChain("bob").PublicID()
// Caveats
// Can only call "Play" at the Google service
cavOnlyPlay = mkCaveat(security.MethodCaveat("Play"))
)
// We create a Caveat from the CaveatValidator "unregisteredCaveat".
// Since "unregisteredCaveat" is not registered with VOM, decoding
// this caveat as part of a PublicID should fail.
//
// The Caveat is created within a child process as VOM automaitcally
// registers all values encoded within the same process.
sh := modules.NewShell(".*")
defer sh.Cleanup(nil, nil)
h, err := sh.Start("encodeUnregisteredCaveat", nil)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
s := expect.NewSession(t, h.Stdout(), time.Minute)
encodedCaveat := s.ReadLine()
if s.Failed() {
t.Fatalf("ReadLineFromChild failed: %v", s.Error())
}
h.CloseStdin()
var cavUnregistered security.Caveat
if err := vom.NewDecoder(bytes.NewReader([]byte(encodedCaveat))).Decode(&cavUnregistered); err != nil {
t.Fatalf("Failed to decode bytes obtained from ReadLineFromChild: %v", err)
}
type rpc struct {
server security.PublicID
method string
// Expected output: exactly one should be non-empty
authErr string
authNames []string
}
testdata := []struct {
client security.PublicID
tests []rpc
}{
// client has a chain identity
{
client: bless(cAlice, veyronChain, "alice", cavOnlyPlay),
tests: []rpc{
{server: bob, method: "Play", authNames: S{"veyron/alice"}},
{server: bob, method: "Hello", authErr: `security.methodCaveat=[Play] fails validation for method "Hello"`},
{server: googleChain.PublicID(), method: "Play", authNames: S{"veyron/alice"}},
{server: googleChain.PublicID(), method: "Hello", authErr: `security.methodCaveat=[Play] fails validation for method "Hello"`},
},
},
/* TODO(ashankar): Test case disabled since PeerBlessingsCaveat is disabled.
{
client: bless(cAlice, veyronChain, "alice", cavOnlyGoogle),
tests: []rpc{
{server: bob, method: "Hello", authErr: `security.peerBlessingsCaveat=[google] fails validation for peer with blessings []`},
{server: googleChain.PublicID(), method: "Hello", authNames: S{"veyron/alice"}},
{server: googleChain.PublicID(), method: "Play", authNames: S{"veyron/alice"}},
},
},
*/
{
client: bless(cAlice, veyronChain, "alice", cavUnregistered),
tests: []rpc{
{server: bob, method: "Play", authErr: "caveat bytes could not be VOM-decoded"},
{server: bob, method: "Hello", authErr: "caveat bytes could not be VOM-decoded"},
{server: googleChain.PublicID(), method: "Play", authErr: "caveat bytes could not be VOM-decoded"},
{server: googleChain.PublicID(), method: "Hello", authErr: "caveat bytes could not be VOM-decoded"},
},
},
/* TODO(ashankar): Test case disabled since PeerBlessingsCaveat is disabled.
{
client: bless(cAlice, veyronChain, "alice", cavOnlyGoogle, cavOnlyPlay),
tests: []rpc{
{server: bob, method: "Hello", authErr: `security.peerBlessingsCaveat=[google] fails validation for peer with blessings []`},
{server: bob, method: "Play", authErr: `security.peerBlessingsCaveat=[google] fails validation for peer with blessings []`},
{server: googleChain.PublicID(), method: "Hello", authErr: `security.methodCaveat=[Play] fails validation for method "Hello"`},
{server: googleChain.PublicID(), method: "Play", authNames: S{"veyron/alice"}},
},
},
*/
{
client: bless(cAlice, veyronChain, "alice", cavUnregistered),
tests: []rpc{
{server: googleChain.PublicID(), method: "Play", authErr: "caveat bytes could not be VOM-decoded"},
{server: googleChain.PublicID(), method: "Hello", authErr: "caveat bytes could not be VOM-decoded"},
},
},
/* TODO(ashankar): Test case disabled since PeerBlessingsCaveat is disabled.
// client has multiple blessings
{
client: newSetPublicID(bless(cAlice, veyronChain, "valice", cavOnlyPlay, cavUnregistered), bless(cAlice, googleChain, "galice", cavOnlyGoogle)),
tests: []rpc{
{server: bob, method: "Hello", authErr: "none of the blessings in the set are authorized"},
{server: bob, method: "Play", authErr: "none of the blessings in the set are authorized"},
{server: googleChain.PublicID(), method: "Hello", authNames: S{"google/galice"}},
{server: googleChain.PublicID(), method: "Play", authNames: S{"google/galice"}},
},
},
*/
}
for _, d := range testdata {
// Validate that the client identity (with all its blessings) is valid for wire transmission.
if _, err := roundTrip(d.client); err != nil {
t.Errorf("roundTrip(%q): %v", d.client, err)
continue
}
for _, test := range d.tests {
if (len(test.authNames) == 0) == (len(test.authErr) == 0) {
t.Errorf("Bad testdata. Exactly one of authNames and authErr must be non-empty: %+q/%+v", d.client, test)
continue
}
ctx := NewContext(ContextArgs{LocalID: test.server, RemoteID: d.client, Method: test.method})
authID, err := d.client.Authorize(ctx)
if !matchesErrorPattern(err, test.authErr) {
t.Errorf("%q.Authorize(%v) returned error: %v, want to match: %q", d.client, ctx, err, test.authErr)
continue
}
if err := verifyAuthorizedID(d.client, authID, test.authNames); err != nil {
t.Errorf("%q.Authorize(%v) returned identity: %v want identity with names: %q [%v]", d.client, ctx, authID, test.authNames, err)
continue
}
}
}
}
type alwaysValidCaveat struct{}
func (alwaysValidCaveat) Validate(security.Context) error {
return nil
}
// proximityCaveat abuses the Method field to store proximity info
// TODO(andreser): create a context that can hold proximity data by design
type proximityCaveat struct{}
func (proximityCaveat) Validate(ctx security.Context) error {
if ctx != nil && ctx.Method() == "proximity: close enough" {
return nil
} else {
return fmt.Errorf("proximityCaveat: not close enough")
}
}
func TestThirdPartyCaveatMinting(t *testing.T) {
minter := newChain("minter")
cav, err := security.NewPublicKeyCaveat(minter.PublicID().PublicKey(), "location", security.ThirdPartyRequirements{}, newCaveat(proximityCaveat{}))
if err != nil {
t.Fatalf("security.NewPublicKeyCaveat failed: %s", err)
}
discharge, err := minter.MintDischarge(cav, NewContext(ContextArgs{}), time.Hour, nil)
if discharge != nil || !matchesErrorPattern(err, "not close enough") {
t.Errorf("Discharge was minted while minting caveats were not met")
}
discharge, err = minter.MintDischarge(cav, NewContext(ContextArgs{Method: "proximity: close enough"}), time.Hour, nil)
if err != nil {
t.Errorf("Discharge was NOT minted even though minting caveats were met")
}
ctxValidateMinting := NewContext(ContextArgs{
Discharges: dischargeMap{discharge.ID(): discharge},
Debug: "ctxValidateMinting",
})
if err = cav.Validate(ctxValidateMinting); err != nil {
t.Errorf("Failed %q.Validate(%q): %s", cav, ctxValidateMinting, err)
}
}
/*
TODO(ashankar): Test disabled because PeerBlessingCaveat has been disabled.
In any case, we expect this whole package to be deleted (it is the "old" security
model), so won't bother with trying to fix up the tests
func TestAuthorizeWithThirdPartyCaveats(t *testing.T) {
mkveyron := func(id security.PrivateID, name string) security.PrivateID {
return derive(bless(id.PublicID(), veyronChain, name), id)
}
mkgoogle := func(id security.PrivateID, name string) security.PrivateID {
return derive(bless(id.PublicID(), googleChain, name), id)
}
mkTPCaveat := func(id security.PrivateID) security.ThirdPartyCaveat {
c, err := security.NewPublicKeyCaveat(id.PublicID().PublicKey(), fmt.Sprintf("%v location", id.PublicID()), security.ThirdPartyRequirements{}, newCaveat(alwaysValidCaveat{}))
if err != nil {
t.Fatalf("NewPublicKeyCaveat with PublicKey of: %v failed: %s", id, err)
}
return c
}
var (
alice = newChain("alice")
bob = newChain("bob")
carol = newChain("carol").PublicID()
// aliceProximityCaveat is a caveat whose discharge can only be minted by alice.
aliceProximityCaveat = mkTPCaveat(alice)
bobProximityCaveat = mkTPCaveat(bob)
)
mintDischarge := func(id security.PrivateID, duration time.Duration, caveats ...security.Caveat) security.Discharge {
d, err := id.MintDischarge(aliceProximityCaveat.(security.ThirdPartyCaveat), nil, duration, caveats)
if err != nil {
t.Fatalf("%q.MintDischarge failed: %v", id, err)
}
return d
}
var (
// Discharges
dAlice = mintDischarge(alice, time.Minute)
dGoogle = mintDischarge(alice, time.Minute, mkCaveat(security.PeerBlessingsCaveat("google")))
dExpired = mintDischarge(alice, 0)
dInvalid = mintDischarge(bob, time.Minute) // Invalid because bob cannot mint valid discharges for aliceProximityCaveat
// Contexts
ctxEmpty = NewContext(ContextArgs{Method: "Play", Debug: "ctxEmpty"})
ctxAlice = NewContext(ContextArgs{
Method: "Play",
Discharges: dischargeMap{dAlice.ID(): dAlice},
Debug: "ctxAlice",
})
// Context containing the discharge dGoogle but the server is not a Google server, so
// the service caveat is not satisfied
ctxGoogleAtOther = NewContext(ContextArgs{
Method: "Play",
Discharges: dischargeMap{dGoogle.ID(): dGoogle},
Debug: "ctxGoogleAtOther",
})
// Context containing the discharge dGoogle at a google server.
ctxGoogleAtGoogle = NewContext(ContextArgs{
Method: "Play",
Discharges: dischargeMap{dGoogle.ID(): dGoogle},
LocalID: googleChain.PublicID(),
Debug: "ctxGoogleAtGoogle",
})
ctxExpired = NewContext(ContextArgs{
Method: "Play",
Discharges: dischargeMap{dExpired.ID(): dExpired},
Debug: "ctxExpired",
})
ctxInvalid = NewContext(ContextArgs{
Method: "Play",
Discharges: dischargeMap{dInvalid.ID(): dInvalid},
Debug: "ctxInvalid",
})
// Contexts that should always end in authorization errors
errtests = map[security.Context]string{
ctxEmpty: "missing discharge",
ctxGoogleAtOther: "security.peerBlessingsCaveat=[google] fails validation",
ctxExpired: "security.unixTimeExpiryCaveat",
ctxInvalid: "signature verification on discharge",
}
)
testdata := []struct {
id security.PublicID
authNames S // For ctxAlice and ctxGoogleAtGoogle
}{
// carol blessed by bob with the third-party caveat should be authorized when the context contains a valid discharge
{
id: bless(carol, mkveyron(bob, "bob"), "friend", newCaveat(aliceProximityCaveat)),
authNames: S{"veyron/bob/friend"},
},
// veyron/vbob/vfriend with bobProximityCaveat and google/gbob/gfriend with aliceProximityCaveat
// Only google/gbob/gfriend should be authorized since the discharge for the former is missing
{
id: newSetPublicID(
bless(carol, mkveyron(bob, "vbob"), "vfriend", newCaveat(bobProximityCaveat)),
bless(carol, mkgoogle(bob, "gbob"), "gfriend", newCaveat(aliceProximityCaveat))),
authNames: S{"google/gbob/gfriend"},
},
// veyron/vbob/friend#google/gbob/friend both have the same caveat and both are satisfied
{
id: bless(carol, newSetPrivateID(mkveyron(bob, "vbob"), mkgoogle(bob, "gbob")), "friend", newCaveat(aliceProximityCaveat)),
authNames: S{"veyron/vbob/friend", "google/gbob/friend"},
},
}
for _, test := range testdata {
if _, err := roundTrip(test.id); err != nil {
t.Errorf("%q is not round-trippable: %v", test.id, test.id, err)
}
for _, ctx := range []security.Context{ctxAlice, ctxGoogleAtGoogle} {
authID, _ := test.id.Authorize(ctx)
if err := verifyAuthorizedID(test.id, authID, test.authNames); err != nil {
t.Errorf("%q.Authorize(%v): %v", test.id, ctx, err)
}
}
for ctx, want := range errtests {
authID, err := test.id.Authorize(ctx)
if authID != nil {
t.Errorf("%q.Authorize(%v) returned %v, should have returned nil", test.id, ctx, authID)
}
if !matchesErrorPattern(err, want) {
t.Errorf("%q.Authorize(%v) returned error: %v, want to match: %q", test.id, ctx, err, want)
}
}
}
}
*/
type SortedThirdPartyCaveats []security.ThirdPartyCaveat
func (s SortedThirdPartyCaveats) Len() int { return len(s) }
func (s SortedThirdPartyCaveats) Less(i, j int) bool {
return s[i].ID() < s[j].ID()
}
func (s SortedThirdPartyCaveats) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func TestThirdPartyCaveatAccessors(t *testing.T) {
mkTPCaveat := func(id security.PublicID) security.ThirdPartyCaveat {
tpCav, err := security.NewPublicKeyCaveat(id.PublicKey(), "someLocation", security.ThirdPartyRequirements{}, newCaveat(alwaysValidCaveat{}))
if err != nil {
t.Fatalf("NewPublicKeyCaveat with PublicKey of: %v failed: %s", id, err)
}
return tpCav
}
mintDischarge := func(caveat security.ThirdPartyCaveat, id security.PrivateID, caveats ...security.Caveat) security.Discharge {
d, err := id.MintDischarge(caveat, nil, time.Minute, caveats)
if err != nil {
t.Fatalf("%q.MintDischarge failed: %v", id, err)
}
return d
}
sortTPCaveats := func(caveats []security.ThirdPartyCaveat) []security.ThirdPartyCaveat {
sort.Stable(SortedThirdPartyCaveats(caveats))
return caveats
}
var (
// Principals (type conversions just to protect against accidentally
// calling the wrong factory function)
alice = newChain("alice").(*chainPrivateID)
cBob = newChain("bob").(*chainPrivateID)
cBobBuilder = derive(bless(cBob.PublicID(), cBob, "builder"), cBob) // Bob also calls himself bob/builder
sBob = newSetPrivateID(cBob, cBobBuilder).(setPrivateID)
// Caveats
tpCavAlice = mkTPCaveat(alice.PublicID())
tpCavBob = mkTPCaveat(alice.PublicID())
cav = mkCaveat(security.MethodCaveat(""))
)
caveats := []struct {
firstparty *security.Caveat
thirdparty []security.ThirdPartyCaveat
}{
{firstparty: nil, thirdparty: nil},
{firstparty: &cav},
{thirdparty: []security.ThirdPartyCaveat{tpCavAlice}},
{thirdparty: []security.ThirdPartyCaveat{tpCavAlice, tpCavBob}},
{firstparty: &cav, thirdparty: []security.ThirdPartyCaveat{tpCavAlice, tpCavBob}},
}
testdata := []struct {
privID security.PrivateID
pubID security.PublicID
}{
{privID: veyronChain, pubID: cBob.PublicID()}, // Chain blessing a chain
{privID: veyronChain, pubID: sBob.PublicID()}, // Chain blessing a set
{privID: sBob, pubID: cBob.PublicID()}, // Set blessing a chain
//{privID: veyronTree, pubID: tBob.PublicID()},
}
for _, d := range testdata {
for _, c := range caveats {
var all []security.Caveat
if c.firstparty != nil {
all = append(all, *c.firstparty)
}
for _, tpc := range c.thirdparty {
all = append(all, newCaveat(tpc))
}
// Test ThirdPartyCaveat accessors on security.PublicIDs.
id := bless(d.pubID, d.privID, "irrelevant", all...)
want := sortTPCaveats(c.thirdparty)
if got := sortTPCaveats(id.ThirdPartyCaveats()); !reflect.DeepEqual(got, want) {
t.Errorf("%q(%T) got ThirdPartyCaveats() = %+v, want %+v", id, id, got, want)
}
// Test ThirdPartyCaveat accessors on security.ThirdPartyCaveat discharges.
dis := mintDischarge(mkTPCaveat(alice.PublicID()), d.privID, all...)
if got := sortTPCaveats(dis.ThirdPartyCaveats()); !reflect.DeepEqual(got, want) {
t.Errorf("%q got ThirdPartyCaveats() = %+v, want %+v", dis, got, want)
}
}
}
}
func TestBlessingChainAmplification(t *testing.T) {
var (
// alice has blessings from trusted identity providers google and veyron
alice = newChain("alice")
googleAlice = derive(bless(alice.PublicID(), googleChain, "alice"), alice)
veyronAlice = derive(bless(alice.PublicID(), veyronChain, "alice"), alice)
bob = newChain("bob").PublicID()
)
// veyron/alice blesses bob for 5 minutes
veyronAliceBob, err := veyronAlice.Bless(bob, "bob@veyron@alice", 5*time.Minute, nil)
if err != nil {
t.Fatal(err)
}
authID, _ := veyronAliceBob.Authorize(NewContext(ContextArgs{}))
if err := verifyAuthorizedID(veyronAliceBob, authID, S{"veyron/alice/bob@veyron@alice"}); err != nil {
t.Fatal(err)
}
// google/alice blesses bob for 1 millisecond
googleAliceBob, err := googleAlice.Bless(bob, "bob@google@alice", 1*time.Millisecond, nil)
if err != nil {
t.Fatal(err)
}
// Wait for 1ms so that the blessing expires
time.Sleep(time.Millisecond)
authID, _ = googleAliceBob.Authorize(NewContext(ContextArgs{}))
if authID != nil {
t.Fatal("%q.Authorized returned: %q, want nil", authID)
}
// At this point, Bob has a valid blessing from veyron/alice and an
// expired blessing from google/alice. Bob should not be able to
// construct a valid blessing from google/alice by combining certificates.
veyronBob := veyronAliceBob.(*chainPublicID)
googleBob := googleAliceBob.(*chainPublicID)
// googleBob should be a valid identity before any modifications
if _, err := roundTrip(googleBob); err != nil {
t.Fatal(err)
}
// Keep the "google/alice" certificate and replace "alice/bob" from
// "google/alice/bob" with the one from "veyron/alice/bob"
cert := googleBob.certificates[2]
googleBob.certificates[2] = veyronBob.certificates[2]
// This hacked up identity should fail integrity tests
if _, err := roundTrip(googleBob); err != wire.ErrNoIntegrity {
t.Fatalf("roundTrip(%q) returned: %v want %v", googleBob, err, wire.ErrNoIntegrity)
}
// Restoring the certificate should restore validity.
googleBob.certificates[2] = cert
if _, err := roundTrip(googleBob); err != nil {
t.Fatal(err)
}
// Replacing the "google/alice" certificate with the "veyron/alice"
// certificate should also cause the identity to be invalid.
googleBob.certificates[1] = veyronBob.certificates[1]
if _, err := roundTrip(googleBob); err != wire.ErrNoIntegrity {
t.Fatalf("roundTrip(%q) returned: %v want %v", googleBob, err, wire.ErrNoIntegrity)
}
}
func TestDerive(t *testing.T) {
var (
cAlice = newChain("alice")
cVeyronAlice = bless(cAlice.PublicID(), veyronChain, "alice")
cBob = newChain("bob").PublicID()
sVeyronAlice = newSetPrivateID(cAlice, derive(cVeyronAlice, cAlice))
tChain = reflect.TypeOf(cAlice)
tSet = reflect.TypeOf(sVeyronAlice)
tErr = reflect.TypeOf(nil)
)
testdata := []struct {
priv security.PrivateID
pub security.PublicID
typ reflect.Type
}{
{priv: cAlice, pub: cVeyronAlice, typ: tChain}, // chain.Derive(chain) = chain
{priv: cAlice, pub: sVeyronAlice.PublicID(), typ: tSet}, // chain.Derive(set) = set
{priv: cAlice, pub: cBob, typ: tErr},
{priv: sVeyronAlice, pub: cAlice.PublicID(), typ: tChain}, // set.Derive(chain) = chain
{priv: sVeyronAlice, pub: sVeyronAlice.PublicID(), typ: tSet}, // set.Derive(set) = set
{priv: sVeyronAlice, pub: cBob, typ: tErr},
}
for _, d := range testdata {
derivedID, err := d.priv.Derive(d.pub)
if reflect.TypeOf(derivedID) != d.typ {
t.Errorf("%T=%q.Derive(%T=%q) yielded (%T, %v), want %v", d.priv, d.priv, d.pub, d.pub, derivedID, err, d.typ)
continue
}
if err != nil {
// If it was not supposed to be, the previous check
// would have registered the error.
continue
}
if !reflect.DeepEqual(derivedID.PublicID(), d.pub) {
t.Errorf("%q.Derive(%q) returned: %q. PublicID mismatch", d.priv, d.pub, derivedID)
}
if _, err := roundTrip(derivedID.PublicID()); err != nil {
t.Errorf("roundTrip(%q=%q.Derive(%q)) failed: %v", derivedID, d.priv, d.pub, err)
}
}
}
func TestNewSetFailures(t *testing.T) {
var (
alice = newChain("alice")
bob = newChain("bob")
)
if s, err := NewSetPrivateID(alice, bob); err == nil {
t.Errorf("Got %v, want error since PrivateKeys do not match", s)
}
if s, err := NewSetPublicID(alice.PublicID(), bob.PublicID()); err == nil {
t.Errorf("Got %v, want error since PublicKeys do not match", s)
}
}
func TestSetIdentityAmplification(t *testing.T) {
var (
alice = newChain("alice").PublicID()
bob = newChain("bob").PublicID()
sAlice = newSetPublicID(bless(alice, veyronChain, "valice"), bless(alice, googleChain, "galice"))
)
// Manipulate sAlice before writing it out to the wire so that it has Bob's authorizations.
reflect.ValueOf(sAlice).Elem().Index(1).Set(reflect.ValueOf(bob))
// Encode/decode the identity.
var buf bytes.Buffer
if err := vom.NewEncoder(&buf).Encode(sAlice); err != nil {
t.Fatal(err)
}
var decoded security.PublicID
if err := vom.NewDecoder(&buf).Decode(&decoded); err == nil || decoded != nil {
t.Fatalf("Got (%v, %v), want wire decode of manipulated identity to fail", decoded, err)
}
}
func init() {
modules.RegisterChild("encodeUnregisteredCaveat", "", encodeUnregisteredCaveat)
vom.Register(alwaysValidCaveat{})
vom.Register(proximityCaveat{})
}