diff --git a/bundle/bundle_test.go b/bundle/bundle_test.go index 8c32c83..3a88d8b 100644 --- a/bundle/bundle_test.go +++ b/bundle/bundle_test.go @@ -418,6 +418,33 @@ func TestAttenuate(t *testing.T) { assert.True(t, hasCav(toks[2])) } +func TestAdd3P(t *testing.T) { + t.Parallel() + + toks := append(macOpts{}.tokens(t), macOpts{}.tokens(t)...) + bun, err := ParseBundle(permLoc, toks.String()) + assert.NoError(t, err) + + cav, err := macaroon.NewCaveat3P(tpKey, tpLoc) + assert.NoError(t, err) + assert.NoError(t, bun.Attenuate(cav)) + assert.Zero(t, cav.VerifierKey) + + vk1 := bun.ts[0].(*UnverifiedMacaroon).UnsafeMac.UnsafeCaveats.Caveats[0].(*macaroon.Caveat3P).VerifierKey + assert.NotZero(t, vk1) + + vk2 := bun.ts[1].(*UnverifiedMacaroon).UnsafeMac.UnsafeCaveats.Caveats[0].(*macaroon.Caveat3P).VerifierKey + assert.NotZero(t, vk2) + + assert.NotEqual(t, vk1, vk2) + + discharger := func(c []macaroon.Caveat) ([]macaroon.Caveat, error) { return nil, nil } + assert.NoError(t, bun.Discharge(tpLoc, tpKey, discharger)) + + _, err = bun.Verify(context.Background(), WithKey(permKID, permKey, nil)) + assert.NoError(t, err) +} + func hasCaveat(c macaroon.Caveat) Predicate { return MacaroonPredicate(func(m Macaroon) bool { if !cavsHasCaveat(m.UnsafeCaveats().Caveats, c) { diff --git a/caveats.go b/caveats.go index 7bbdf6b..f2a7bf7 100644 --- a/caveats.go +++ b/caveats.go @@ -18,6 +18,33 @@ type Caveat3P struct { rn []byte `msgpack:"-"` } +func NewCaveat3P(ka EncryptionKey, loc string, cs ...Caveat) (*Caveat3P, error) { + if len(ka) != EncryptionKeySize { + return nil, fmt.Errorf("bad key size: have %d, need %d", len(ka), EncryptionKeySize) + } + + // make a new root hmac key for the 3p discharge macaroon + rn := NewSigningKey() + + // make the ticket, which is consumed by the 3p service; then + // encode and encrypt it + ticket := &wireTicket{ + DischargeKey: rn, + Caveats: *NewCaveatSet(cs...), + } + + ticketBytes, err := encode(ticket) + if err != nil { + return nil, fmt.Errorf("encoding ticket: %w", err) + } + + return &Caveat3P{ + Location: loc, + Ticket: seal(ka, ticketBytes), + rn: rn, + }, nil +} + func init() { RegisterCaveatType(&Caveat3P{}) } func (c *Caveat3P) CaveatType() CaveatType { return Cav3P } func (c *Caveat3P) Name() string { return "3P" } diff --git a/macaroon.go b/macaroon.go index 4652d27..ad58a37 100644 --- a/macaroon.go +++ b/macaroon.go @@ -225,6 +225,11 @@ func (m *Macaroon) Add(caveats ...Caveat) error { } if c3p, ok := caveat.(*Caveat3P); ok { + // make a copy since we have to modify it. in case the caveat is + // added to multiple macaroons + c3p := *c3p + caveat = &c3p + // encrypt RN under the tail hmac so we can recover it during verification c3p.VerifierKey = seal(EncryptionKey(m.Tail), c3p.rn) @@ -521,32 +526,12 @@ func (m *Macaroon) BindToParentMacaroon(parent *Macaroon) error { // to use to check which caveats. The location is normally a URL. The // authentication service has an authentication location URL. func (m *Macaroon) Add3P(ka EncryptionKey, loc string, cs ...Caveat) error { - if len(ka) != EncryptionKeySize { - return fmt.Errorf("bad key size: have %d, need %d", len(ka), EncryptionKeySize) - } - - // make a new root hmac key for the 3p discharge macaroon - rn := NewSigningKey() - - // make the ticket, which is consumed by the 3p service; then - // encode and encrypt it - ticket := &wireTicket{ - DischargeKey: rn, - Caveats: *NewCaveatSet(cs...), - } - - ticketBytes, err := encode(ticket) + cav, err := NewCaveat3P(ka, loc, cs...) if err != nil { - return fmt.Errorf("encoding ticket: %w", err) + return err } - m.Add(&Caveat3P{ - Location: loc, - Ticket: seal(ka, ticketBytes), - rn: rn, - }) - - return nil + return m.Add(cav) } // AllThirdPartyTickets extracts the encrypted tickets from a token's third party