7
"github.com/juju/testing"
8
jc "github.com/juju/testing/checkers"
10
"gopkg.in/macaroon.v1"
12
"gopkg.in/macaroon-bakery.v1/bakery"
13
"gopkg.in/macaroon-bakery.v1/bakery/checkers"
16
type ServiceSuite struct {
20
var _ = gc.Suite(&ServiceSuite{})
22
// TestSingleServiceFirstParty creates a single service
23
// with a macaroon with one first party caveat.
24
// It creates a request with this macaroon and checks that the service
25
// can verify this macaroon as valid.
26
func (s *ServiceSuite) TestSingleServiceFirstParty(c *gc.C) {
27
p := bakery.NewServiceParams{
33
service, err := bakery.NewService(p)
34
c.Assert(err, gc.IsNil)
36
primary, err := service.NewMacaroon("", nil, nil)
37
c.Assert(err, gc.IsNil)
38
c.Assert(primary.Location(), gc.Equals, "loc")
39
cav := checkers.Caveat{
41
Condition: "something",
43
err = service.AddCaveat(primary, cav)
44
c.Assert(err, gc.IsNil)
46
err = service.Check(macaroon.Slice{primary}, strcmpChecker("something"))
47
c.Assert(err, gc.IsNil)
50
// TestMacaroonPaperFig6 implements an example flow as described in the macaroons paper:
51
// http://theory.stanford.edu/~ataly/Papers/macaroons.pdf
52
// There are three services, ts, fs, as:
53
// ts is a storage service which has deligated authority to a forum service fs.
54
// The forum service wants to require its users to be logged into to an authentication service as.
56
// The client obtains a macaroon from fs (minted by ts, with a third party caveat addressed to as).
57
// The client obtains a discharge macaroon from as to satisfy this caveat.
58
// The target service verifies the original macaroon it delegated to fs
59
// No direct contact between as and ts is required
60
func (s *ServiceSuite) TestMacaroonPaperFig6(c *gc.C) {
61
locator := make(bakery.PublicKeyLocatorMap)
62
as := newService(c, "as-loc", locator)
63
ts := newService(c, "ts-loc", locator)
64
fs := newService(c, "fs-loc", locator)
66
// ts creates a macaroon.
67
tsMacaroon, err := ts.NewMacaroon("", nil, nil)
68
c.Assert(err, gc.IsNil)
70
// ts somehow sends the macaroon to fs which adds a third party caveat to be discharged by as.
71
err = fs.AddCaveat(tsMacaroon, checkers.Caveat{Location: "as-loc", Condition: "user==bob"})
72
c.Assert(err, gc.IsNil)
74
// client asks for a discharge macaroon for each third party caveat
75
d, err := bakery.DischargeAll(tsMacaroon, func(firstPartyLocation string, cav macaroon.Caveat) (*macaroon.Macaroon, error) {
76
c.Assert(firstPartyLocation, gc.Equals, "ts-loc")
77
c.Assert(cav.Location, gc.Equals, "as-loc")
78
mac, err := as.Discharge(strcmpChecker("user==bob"), cav.Id)
79
c.Assert(err, gc.IsNil)
82
c.Assert(err, gc.IsNil)
84
err = ts.Check(d, strcmpChecker(""))
85
c.Assert(err, gc.IsNil)
88
func macStr(m *macaroon.Macaroon) string {
89
data, err := json.MarshalIndent(m, "\t", "\t")
96
// TestMacaroonPaperFig6FailsWithoutDischarges runs a similar test as TestMacaroonPaperFig6
97
// without the client discharging the third party caveats.
98
func (s *ServiceSuite) TestMacaroonPaperFig6FailsWithoutDischarges(c *gc.C) {
99
locator := make(bakery.PublicKeyLocatorMap)
100
ts := newService(c, "ts-loc", locator)
101
fs := newService(c, "fs-loc", locator)
102
_ = newService(c, "as-loc", locator)
104
// ts creates a macaroon.
105
tsMacaroon, err := ts.NewMacaroon("", nil, nil)
106
c.Assert(err, gc.IsNil)
108
// ts somehow sends the macaroon to fs which adds a third party caveat to be discharged by as.
109
err = fs.AddCaveat(tsMacaroon, checkers.Caveat{Location: "as-loc", Condition: "user==bob"})
110
c.Assert(err, gc.IsNil)
112
// client makes request to ts
113
err = ts.Check(macaroon.Slice{tsMacaroon}, strcmpChecker(""))
114
c.Assert(err, gc.ErrorMatches, `verification failed: cannot find discharge macaroon for caveat ".*"`)
117
// TestMacaroonPaperFig6FailsWithBindingOnTamperedSignature runs a similar test as TestMacaroonPaperFig6
118
// with the discharge macaroon binding being done on a tampered signature.
119
func (s *ServiceSuite) TestMacaroonPaperFig6FailsWithBindingOnTamperedSignature(c *gc.C) {
120
locator := make(bakery.PublicKeyLocatorMap)
121
as := newService(c, "as-loc", locator)
122
ts := newService(c, "ts-loc", locator)
123
fs := newService(c, "fs-loc", locator)
125
// ts creates a macaroon.
126
tsMacaroon, err := ts.NewMacaroon("", nil, nil)
127
c.Assert(err, gc.IsNil)
129
// ts somehow sends the macaroon to fs which adds a third party caveat to be discharged by as.
130
err = fs.AddCaveat(tsMacaroon, checkers.Caveat{Location: "as-loc", Condition: "user==bob"})
131
c.Assert(err, gc.IsNil)
133
// client asks for a discharge macaroon for each third party caveat
134
d, err := bakery.DischargeAll(tsMacaroon, func(firstPartyLocation string, cav macaroon.Caveat) (*macaroon.Macaroon, error) {
135
c.Assert(firstPartyLocation, gc.Equals, "ts-loc")
136
c.Assert(cav.Location, gc.Equals, "as-loc")
137
mac, err := as.Discharge(strcmpChecker("user==bob"), cav.Id)
138
c.Assert(err, gc.IsNil)
141
c.Assert(err, gc.IsNil)
143
// client has all the discharge macaroons. For each discharge macaroon bind it to our tsMacaroon
144
// and add it to our request.
145
for _, dm := range d[1:] {
146
dm.Bind([]byte("tampered-signature")) // Bind against an incorrect signature.
149
// client makes request to ts.
150
err = ts.Check(d, strcmpChecker(""))
151
c.Assert(err, gc.ErrorMatches, "verification failed: signature mismatch after caveat verification")
154
func (s *ServiceSuite) TestNeedDeclared(c *gc.C) {
155
locator := make(bakery.PublicKeyLocatorMap)
156
firstParty := newService(c, "first", locator)
157
thirdParty := newService(c, "third", locator)
159
// firstParty mints a macaroon with a third-party caveat addressed
160
// to thirdParty with a need-declared caveat.
161
m, err := firstParty.NewMacaroon("", nil, []checkers.Caveat{
162
checkers.NeedDeclaredCaveat(checkers.Caveat{
164
Condition: "something",
167
c.Assert(err, gc.IsNil)
169
// The client asks for a discharge macaroon for each third party caveat.
170
d, err := bakery.DischargeAll(m, func(_ string, cav macaroon.Caveat) (*macaroon.Macaroon, error) {
171
return thirdParty.Discharge(strcmpChecker("something"), cav.Id)
173
c.Assert(err, gc.IsNil)
175
// The required declared attributes should have been added
176
// to the discharge macaroons.
177
declared := checkers.InferDeclared(d)
178
c.Assert(declared, gc.DeepEquals, checkers.Declared{
183
// Make sure the macaroons actually check out correctly
184
// when provided with the declared checker.
185
err = firstParty.Check(d, checkers.New(declared))
186
c.Assert(err, gc.IsNil)
188
// Try again when the third party does add a required declaration.
190
// The client asks for a discharge macaroon for each third party caveat.
191
d, err = bakery.DischargeAll(m, func(_ string, cav macaroon.Caveat) (*macaroon.Macaroon, error) {
192
checker := thirdPartyCheckerWithCaveats{
193
checkers.DeclaredCaveat("foo", "a"),
194
checkers.DeclaredCaveat("arble", "b"),
196
return thirdParty.Discharge(checker, cav.Id)
198
c.Assert(err, gc.IsNil)
200
// One attribute should have been added, the other was already there.
201
declared = checkers.InferDeclared(d)
202
c.Assert(declared, gc.DeepEquals, checkers.Declared{
208
err = firstParty.Check(d, checkers.New(declared))
209
c.Assert(err, gc.IsNil)
211
// Try again, but this time pretend a client is sneakily trying
212
// to add another "declared" attribute to alter the declarations.
213
d, err = bakery.DischargeAll(m, func(_ string, cav macaroon.Caveat) (*macaroon.Macaroon, error) {
214
checker := thirdPartyCheckerWithCaveats{
215
checkers.DeclaredCaveat("foo", "a"),
216
checkers.DeclaredCaveat("arble", "b"),
218
m, err := thirdParty.Discharge(checker, cav.Id)
219
c.Assert(err, gc.IsNil)
221
// Sneaky client adds a first party caveat.
222
m.AddFirstPartyCaveat(checkers.DeclaredCaveat("foo", "c").Condition)
225
c.Assert(err, gc.IsNil)
227
declared = checkers.InferDeclared(d)
228
c.Assert(declared, gc.DeepEquals, checkers.Declared{
233
err = firstParty.Check(d, checkers.New(declared))
234
c.Assert(err, gc.ErrorMatches, `verification failed: caveat "declared foo a" not satisfied: got foo=null, expected "a"`)
237
func (s *ServiceSuite) TestDischargeTwoNeedDeclared(c *gc.C) {
238
locator := make(bakery.PublicKeyLocatorMap)
239
firstParty := newService(c, "first", locator)
240
thirdParty := newService(c, "third", locator)
242
// firstParty mints a macaroon with two third party caveats
243
// with overlapping attributes.
244
m, err := firstParty.NewMacaroon("", nil, []checkers.Caveat{
245
checkers.NeedDeclaredCaveat(checkers.Caveat{
249
checkers.NeedDeclaredCaveat(checkers.Caveat{
254
c.Assert(err, gc.IsNil)
256
// The client asks for a discharge macaroon for each third party caveat.
257
// Since no declarations are added by the discharger,
258
d, err := bakery.DischargeAll(m, func(_ string, cav macaroon.Caveat) (*macaroon.Macaroon, error) {
259
return thirdParty.Discharge(bakery.ThirdPartyCheckerFunc(func(_, caveat string) ([]checkers.Caveat, error) {
263
c.Assert(err, gc.IsNil)
264
declared := checkers.InferDeclared(d)
265
c.Assert(declared, gc.DeepEquals, checkers.Declared{
270
err = firstParty.Check(d, checkers.New(declared))
271
c.Assert(err, gc.IsNil)
273
// If they return conflicting values, the discharge fails.
274
// The client asks for a discharge macaroon for each third party caveat.
275
// Since no declarations are added by the discharger,
276
d, err = bakery.DischargeAll(m, func(_ string, cav macaroon.Caveat) (*macaroon.Macaroon, error) {
277
return thirdParty.Discharge(bakery.ThirdPartyCheckerFunc(func(_, caveat string) ([]checkers.Caveat, error) {
280
return []checkers.Caveat{
281
checkers.DeclaredCaveat("foo", "fooval1"),
284
return []checkers.Caveat{
285
checkers.DeclaredCaveat("foo", "fooval2"),
286
checkers.DeclaredCaveat("baz", "bazval"),
289
return nil, fmt.Errorf("not matched")
292
c.Assert(err, gc.IsNil)
293
declared = checkers.InferDeclared(d)
294
c.Assert(declared, gc.DeepEquals, checkers.Declared{
298
err = firstParty.Check(d, checkers.New(declared))
299
c.Assert(err, gc.ErrorMatches, `verification failed: caveat "declared foo fooval1" not satisfied: got foo=null, expected "fooval1"`)
302
func (s *ServiceSuite) TestDischargeMacaroonCannotBeUsedAsNormalMacaroon(c *gc.C) {
303
locator := make(bakery.PublicKeyLocatorMap)
304
firstParty := newService(c, "first", locator)
305
thirdParty := newService(c, "third", locator)
307
// First party mints a macaroon with a 3rd party caveat.
308
m, err := firstParty.NewMacaroon("", nil, []checkers.Caveat{{
312
c.Assert(err, gc.IsNil)
314
// Acquire the discharge macaroon, but don't bind it to the original.
315
d, err := thirdParty.Discharge(bakery.ThirdPartyCheckerFunc(func(_, caveat string) ([]checkers.Caveat, error) {
317
}), m.Caveats()[0].Id)
318
c.Assert(err, gc.IsNil)
320
// Make sure it cannot be used as a normal macaroon in the third party.
321
err = thirdParty.Check(macaroon.Slice{d}, checkers.New())
322
c.Assert(err, gc.ErrorMatches, `verification failed: macaroon not found in storage`)
325
func (*ServiceSuite) TestCheckAny(c *gc.C) {
326
svc := newService(c, "somewhere", nil)
327
newMacaroons := func(id string, caveats ...checkers.Caveat) macaroon.Slice {
328
m, err := svc.NewMacaroon(id, nil, caveats)
329
c.Assert(err, gc.IsNil)
330
return macaroon.Slice{m}
334
macaroons []macaroon.Slice
335
assert map[string]string
336
checker checkers.Checker
337
expectDeclared map[string]string
341
about: "no macaroons",
342
expectError: "verification failed: no macaroons",
344
about: "one macaroon, no caveats",
345
macaroons: []macaroon.Slice{
350
about: "one macaroon, one unrecognized caveat",
351
macaroons: []macaroon.Slice{
352
newMacaroons("x2", checkers.Caveat{
356
expectError: `verification failed: caveat "bad" not satisfied: caveat not recognized`,
358
about: "two macaroons, only one ok",
359
macaroons: []macaroon.Slice{
360
newMacaroons("x3", checkers.Caveat{
367
about: "macaroon with declared caveats",
368
macaroons: []macaroon.Slice{
370
checkers.DeclaredCaveat("key1", "value1"),
371
checkers.DeclaredCaveat("key2", "value2"),
374
expectDeclared: map[string]string{
380
about: "macaroon with declared values and asserted keys with wrong value",
381
macaroons: []macaroon.Slice{
383
checkers.DeclaredCaveat("key1", "value1"),
384
checkers.DeclaredCaveat("key2", "value2"),
387
assert: map[string]string{
391
expectError: `verification failed: caveat "declared key1 value1" not satisfied: got key1="valuex", expected "value1"`,
393
about: "macaroon with declared values and asserted keys with correct value",
394
macaroons: []macaroon.Slice{
396
checkers.DeclaredCaveat("key1", "value1"),
397
checkers.DeclaredCaveat("key2", "value2"),
400
assert: map[string]string{
403
expectDeclared: map[string]string{
409
for i, test := range tests {
410
c.Logf("test %d: %s", i, test.about)
411
if test.expectDeclared == nil {
412
test.expectDeclared = make(map[string]string)
414
if test.checker == nil {
415
test.checker = checkers.New()
418
decl, ms, err := svc.CheckAnyM(test.macaroons, test.assert, test.checker)
419
if test.expectError != "" {
420
c.Assert(err, gc.ErrorMatches, test.expectError)
421
c.Assert(decl, gc.HasLen, 0)
422
c.Assert(ms, gc.IsNil)
425
c.Assert(err, gc.IsNil)
426
c.Assert(decl, jc.DeepEquals, test.expectDeclared)
427
c.Assert(ms[0].Id(), gc.Equals, test.expectId)
431
func (s *ServiceSuite) TestNewMacaroonWithRootKeyStorage(c *gc.C) {
432
svc, err := bakery.NewService(bakery.NewServiceParams{
433
Location: "somewhere",
435
c.Assert(err, gc.IsNil)
437
store := bakery.NewMemRootKeyStorage()
438
key, id, err := store.RootKey()
439
c.Assert(err, gc.IsNil)
441
svc = svc.WithRootKeyStore(store)
443
m, err := svc.NewMacaroon("", nil, []checkers.Caveat{{
445
Condition: "something",
447
c.Assert(err, gc.IsNil)
448
c.Assert(m.Location(), gc.Equals, "somewhere")
450
c.Assert(id1, gc.Matches, id+"-[0-9a-f]{32}")
452
err = svc.Check(macaroon.Slice{m}, strcmpChecker("something"))
453
c.Assert(err, gc.IsNil)
455
// Check that it's really using the root key returned from
457
err = m.Verify(key, func(string) error {
460
c.Assert(err, gc.IsNil)
462
// Create another one and check that it re-uses the
463
// same key but has a different id.
464
m, err = svc.NewMacaroon("", nil, []checkers.Caveat{{
466
Condition: "something",
468
c.Assert(err, gc.IsNil)
469
c.Assert(m.Location(), gc.Equals, "somewhere")
471
c.Assert(id2, gc.Matches, id+"-[0-9a-f]{32}")
472
c.Assert(id2, gc.Not(gc.Equals), id1)
473
err = m.Verify(key, func(string) error { return nil }, nil)
474
c.Assert(err, gc.IsNil)
477
func (s *ServiceSuite) TestNewMacaroonWithRootKeyStorageInParams(c *gc.C) {
478
store := bakery.NewMemRootKeyStorage()
479
_, id, err := store.RootKey()
480
c.Assert(err, gc.IsNil)
482
// Check that we can create a bakery with the root key store
483
// in its parameters too.
484
svc, err := bakery.NewService(bakery.NewServiceParams{
485
Location: "elsewhere",
488
c.Assert(err, gc.IsNil)
490
m, err := svc.NewMacaroon("", nil, nil)
491
c.Assert(err, gc.IsNil)
492
c.Assert(m.Id(), gc.Matches, id+"-[0-9a-f]{32}")
494
err = svc.Check(macaroon.Slice{m}, checkers.New())
495
c.Assert(err, gc.IsNil)
498
func (s *ServiceSuite) TestNewMacaroonWithExplicitIdAndRootKeyStorage(c *gc.C) {
499
store := bakery.NewMemRootKeyStorage()
501
// Check that we can create a bakery with the root key store
502
// in its parameters too.
503
svc, err := bakery.NewService(bakery.NewServiceParams{
504
Location: "somewhere",
507
c.Assert(err, gc.IsNil)
509
m, err := svc.NewMacaroon("someid", nil, nil)
510
c.Assert(err, gc.ErrorMatches, `cannot choose root key or id when using RootKeyStore`)
511
c.Assert(m, gc.IsNil)
513
m, err = svc.NewMacaroon("", []byte{1}, nil)
514
c.Assert(err, gc.ErrorMatches, `cannot choose root key or id when using RootKeyStore`)
515
c.Assert(m, gc.IsNil)
518
func newService(c *gc.C, location string, locator bakery.PublicKeyLocatorMap) *bakery.Service {
519
keyPair, err := bakery.GenerateKey()
520
c.Assert(err, gc.IsNil)
522
svc, err := bakery.NewService(bakery.NewServiceParams{
527
c.Assert(err, gc.IsNil)
529
locator[location] = &keyPair.Public
534
type strcmpChecker string
536
func (c strcmpChecker) CheckFirstPartyCaveat(caveat string) error {
537
if caveat != string(c) {
538
return fmt.Errorf("%v doesn't match %s", caveat, c)
543
func (c strcmpChecker) CheckThirdPartyCaveat(caveatId string, caveat string) ([]checkers.Caveat, error) {
544
if caveat != string(c) {
545
return nil, fmt.Errorf("%v doesn't match %s", caveat, c)
550
type thirdPartyCheckerWithCaveats []checkers.Caveat
552
func (c thirdPartyCheckerWithCaveats) CheckThirdPartyCaveat(caveatId string, caveat string) ([]checkers.Caveat, error) {