dendrite/vendor/src/github.com/go-macaroon/macaroon/macaroon_test.go
Anant Prakash afeab7b2d4 Add token generation using go macaroon (#437)
* Add Go macaroon library

Signed-off-by: Anant Prakash <anantprakashjsr@gmail.com>

* Add macaroon generation and serialization, for login token.

Signed-off-by: Anant Prakash <anantprakashjsr@gmail.com>

* Remove copyright, trim empty lines

* Make Serialize functions private

* Fix typos
2018-05-22 10:13:58 +01:00

915 lines
27 KiB
Go

package macaroon_test
import (
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"testing"
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
"gopkg.in/macaroon.v2"
)
func TestPackage(t *testing.T) {
gc.TestingT(t)
}
type macaroonSuite struct{}
var _ = gc.Suite(&macaroonSuite{})
func never(string) error {
return fmt.Errorf("condition is never true")
}
func (*macaroonSuite) TestNoCaveats(c *gc.C) {
rootKey := []byte("secret")
m := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion)
c.Assert(m.Location(), gc.Equals, "a location")
c.Assert(m.Id(), gc.DeepEquals, []byte("some id"))
err := m.Verify(rootKey, never, nil)
c.Assert(err, gc.IsNil)
}
func (*macaroonSuite) TestFirstPartyCaveat(c *gc.C) {
rootKey := []byte("secret")
m := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion)
caveats := map[string]bool{
"a caveat": true,
"another caveat": true,
}
tested := make(map[string]bool)
for cav := range caveats {
m.AddFirstPartyCaveat([]byte(cav))
}
expectErr := fmt.Errorf("condition not met")
check := func(cav string) error {
tested[cav] = true
if caveats[cav] {
return nil
}
return expectErr
}
err := m.Verify(rootKey, check, nil)
c.Assert(err, gc.IsNil)
c.Assert(tested, gc.DeepEquals, caveats)
m.AddFirstPartyCaveat([]byte("not met"))
err = m.Verify(rootKey, check, nil)
c.Assert(err, gc.Equals, expectErr)
c.Assert(tested["not met"], gc.Equals, true)
}
func (*macaroonSuite) TestThirdPartyCaveat(c *gc.C) {
rootKey := []byte("secret")
m := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion)
dischargeRootKey := []byte("shared root key")
thirdPartyCaveatId := []byte("3rd party caveat")
err := m.AddThirdPartyCaveat(dischargeRootKey, thirdPartyCaveatId, "remote.com")
c.Assert(err, gc.IsNil)
dm := MustNew(dischargeRootKey, thirdPartyCaveatId, "remote location", macaroon.LatestVersion)
dm.Bind(m.Signature())
err = m.Verify(rootKey, never, []*macaroon.Macaroon{dm})
c.Assert(err, gc.IsNil)
}
func (*macaroonSuite) TestThirdPartyCaveatBadRandom(c *gc.C) {
rootKey := []byte("secret")
m := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion)
dischargeRootKey := []byte("shared root key")
thirdPartyCaveatId := []byte("3rd party caveat")
err := macaroon.AddThirdPartyCaveatWithRand(m, dischargeRootKey, thirdPartyCaveatId, "remote.com", &macaroon.ErrorReader{})
c.Assert(err, gc.ErrorMatches, "cannot generate random bytes: fail")
}
func (*macaroonSuite) TestSetLocation(c *gc.C) {
rootKey := []byte("secret")
m := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion)
c.Assert(m.Location(), gc.Equals, "a location")
m.SetLocation("another location")
c.Assert(m.Location(), gc.Equals, "another location")
}
type conditionTest struct {
conditions map[string]bool
expectErr string
}
var verifyTests = []struct {
about string
macaroons []macaroonSpec
conditions []conditionTest
}{{
about: "single third party caveat without discharge",
macaroons: []macaroonSpec{{
rootKey: "root-key",
id: "root-id",
caveats: []caveat{{
condition: "wonderful",
}, {
condition: "bob-is-great",
location: "bob",
rootKey: "bob-caveat-root-key",
}},
}},
conditions: []conditionTest{{
conditions: map[string]bool{
"wonderful": true,
},
expectErr: fmt.Sprintf(`cannot find discharge macaroon for caveat %x`, "bob-is-great"),
}},
}, {
about: "single third party caveat with discharge",
macaroons: []macaroonSpec{{
rootKey: "root-key",
id: "root-id",
caveats: []caveat{{
condition: "wonderful",
}, {
condition: "bob-is-great",
location: "bob",
rootKey: "bob-caveat-root-key",
}},
}, {
location: "bob",
rootKey: "bob-caveat-root-key",
id: "bob-is-great",
}},
conditions: []conditionTest{{
conditions: map[string]bool{
"wonderful": true,
},
}, {
conditions: map[string]bool{
"wonderful": false,
},
expectErr: `condition "wonderful" not met`,
}},
}, {
about: "single third party caveat with discharge with mismatching root key",
macaroons: []macaroonSpec{{
rootKey: "root-key",
id: "root-id",
caveats: []caveat{{
condition: "wonderful",
}, {
condition: "bob-is-great",
location: "bob",
rootKey: "bob-caveat-root-key",
}},
}, {
location: "bob",
rootKey: "bob-caveat-root-key-wrong",
id: "bob-is-great",
}},
conditions: []conditionTest{{
conditions: map[string]bool{
"wonderful": true,
},
expectErr: `signature mismatch after caveat verification`,
}},
}, {
about: "single third party caveat with two discharges",
macaroons: []macaroonSpec{{
rootKey: "root-key",
id: "root-id",
caveats: []caveat{{
condition: "wonderful",
}, {
condition: "bob-is-great",
location: "bob",
rootKey: "bob-caveat-root-key",
}},
}, {
location: "bob",
rootKey: "bob-caveat-root-key",
id: "bob-is-great",
caveats: []caveat{{
condition: "splendid",
}},
}, {
location: "bob",
rootKey: "bob-caveat-root-key",
id: "bob-is-great",
caveats: []caveat{{
condition: "top of the world",
}},
}},
conditions: []conditionTest{{
conditions: map[string]bool{
"wonderful": true,
},
expectErr: `condition "splendid" not met`,
}, {
conditions: map[string]bool{
"wonderful": true,
"splendid": true,
"top of the world": true,
},
expectErr: `discharge macaroon "bob-is-great" was not used`,
}, {
conditions: map[string]bool{
"wonderful": true,
"splendid": false,
"top of the world": true,
},
expectErr: `condition "splendid" not met`,
}, {
conditions: map[string]bool{
"wonderful": true,
"splendid": true,
"top of the world": false,
},
expectErr: `discharge macaroon "bob-is-great" was not used`,
}},
}, {
about: "one discharge used for two macaroons",
macaroons: []macaroonSpec{{
rootKey: "root-key",
id: "root-id",
caveats: []caveat{{
condition: "somewhere else",
location: "bob",
rootKey: "bob-caveat-root-key",
}, {
condition: "bob-is-great",
location: "charlie",
rootKey: "bob-caveat-root-key",
}},
}, {
location: "bob",
rootKey: "bob-caveat-root-key",
id: "somewhere else",
caveats: []caveat{{
condition: "bob-is-great",
location: "charlie",
rootKey: "bob-caveat-root-key",
}},
}, {
location: "bob",
rootKey: "bob-caveat-root-key",
id: "bob-is-great",
}},
conditions: []conditionTest{{
expectErr: `discharge macaroon "bob-is-great" was used more than once`,
}},
}, {
about: "recursive third party caveat",
macaroons: []macaroonSpec{{
rootKey: "root-key",
id: "root-id",
caveats: []caveat{{
condition: "bob-is-great",
location: "bob",
rootKey: "bob-caveat-root-key",
}},
}, {
location: "bob",
rootKey: "bob-caveat-root-key",
id: "bob-is-great",
caveats: []caveat{{
condition: "bob-is-great",
location: "charlie",
rootKey: "bob-caveat-root-key",
}},
}},
conditions: []conditionTest{{
expectErr: `discharge macaroon "bob-is-great" was used more than once`,
}},
}, {
about: "two third party caveats",
macaroons: []macaroonSpec{{
rootKey: "root-key",
id: "root-id",
caveats: []caveat{{
condition: "wonderful",
}, {
condition: "bob-is-great",
location: "bob",
rootKey: "bob-caveat-root-key",
}, {
condition: "charlie-is-great",
location: "charlie",
rootKey: "charlie-caveat-root-key",
}},
}, {
location: "bob",
rootKey: "bob-caveat-root-key",
id: "bob-is-great",
caveats: []caveat{{
condition: "splendid",
}},
}, {
location: "charlie",
rootKey: "charlie-caveat-root-key",
id: "charlie-is-great",
caveats: []caveat{{
condition: "top of the world",
}},
}},
conditions: []conditionTest{{
conditions: map[string]bool{
"wonderful": true,
"splendid": true,
"top of the world": true,
},
}, {
conditions: map[string]bool{
"wonderful": true,
"splendid": false,
"top of the world": true,
},
expectErr: `condition "splendid" not met`,
}, {
conditions: map[string]bool{
"wonderful": true,
"splendid": true,
"top of the world": false,
},
expectErr: `condition "top of the world" not met`,
}},
}, {
about: "third party caveat with undischarged third party caveat",
macaroons: []macaroonSpec{{
rootKey: "root-key",
id: "root-id",
caveats: []caveat{{
condition: "wonderful",
}, {
condition: "bob-is-great",
location: "bob",
rootKey: "bob-caveat-root-key",
}},
}, {
location: "bob",
rootKey: "bob-caveat-root-key",
id: "bob-is-great",
caveats: []caveat{{
condition: "splendid",
}, {
condition: "barbara-is-great",
location: "barbara",
rootKey: "barbara-caveat-root-key",
}},
}},
conditions: []conditionTest{{
conditions: map[string]bool{
"wonderful": true,
"splendid": true,
},
expectErr: fmt.Sprintf(`cannot find discharge macaroon for caveat %x`, "barbara-is-great"),
}},
}, {
about: "multilevel third party caveats",
macaroons: multilevelThirdPartyCaveatMacaroons,
conditions: []conditionTest{{
conditions: map[string]bool{
"wonderful": true,
"splendid": true,
"high-fiving": true,
"spiffing": true,
},
}, {
conditions: map[string]bool{
"wonderful": true,
"splendid": true,
"high-fiving": false,
"spiffing": true,
},
expectErr: `condition "high-fiving" not met`,
}},
}, {
about: "unused discharge",
macaroons: []macaroonSpec{{
rootKey: "root-key",
id: "root-id",
}, {
rootKey: "other-key",
id: "unused",
}},
conditions: []conditionTest{{
expectErr: `discharge macaroon "unused" was not used`,
}},
}}
var multilevelThirdPartyCaveatMacaroons = []macaroonSpec{{
rootKey: "root-key",
id: "root-id",
caveats: []caveat{{
condition: "wonderful",
}, {
condition: "bob-is-great",
location: "bob",
rootKey: "bob-caveat-root-key",
}, {
condition: "charlie-is-great",
location: "charlie",
rootKey: "charlie-caveat-root-key",
}},
}, {
location: "bob",
rootKey: "bob-caveat-root-key",
id: "bob-is-great",
caveats: []caveat{{
condition: "splendid",
}, {
condition: "barbara-is-great",
location: "barbara",
rootKey: "barbara-caveat-root-key",
}},
}, {
location: "charlie",
rootKey: "charlie-caveat-root-key",
id: "charlie-is-great",
caveats: []caveat{{
condition: "splendid",
}, {
condition: "celine-is-great",
location: "celine",
rootKey: "celine-caveat-root-key",
}},
}, {
location: "barbara",
rootKey: "barbara-caveat-root-key",
id: "barbara-is-great",
caveats: []caveat{{
condition: "spiffing",
}, {
condition: "ben-is-great",
location: "ben",
rootKey: "ben-caveat-root-key",
}},
}, {
location: "ben",
rootKey: "ben-caveat-root-key",
id: "ben-is-great",
}, {
location: "celine",
rootKey: "celine-caveat-root-key",
id: "celine-is-great",
caveats: []caveat{{
condition: "high-fiving",
}},
}}
func (*macaroonSuite) TestVerify(c *gc.C) {
for i, test := range verifyTests {
c.Logf("test %d: %s", i, test.about)
rootKey, macaroons := makeMacaroons(test.macaroons)
for _, cond := range test.conditions {
c.Logf("conditions %#v", cond.conditions)
check := func(cav string) error {
if cond.conditions[cav] {
return nil
}
return fmt.Errorf("condition %q not met", cav)
}
err := macaroons[0].Verify(
rootKey,
check,
macaroons[1:],
)
if cond.expectErr != "" {
c.Assert(err, gc.ErrorMatches, cond.expectErr)
} else {
c.Assert(err, gc.IsNil)
}
// Cloned macaroon should have same verify result.
cloneErr := macaroons[0].Clone().Verify(rootKey, check, macaroons[1:])
c.Assert(cloneErr, gc.DeepEquals, err)
}
}
}
func (*macaroonSuite) TestTraceVerify(c *gc.C) {
rootKey, macaroons := makeMacaroons(multilevelThirdPartyCaveatMacaroons)
traces, err := macaroons[0].TraceVerify(rootKey, macaroons[1:])
c.Assert(err, gc.Equals, nil)
c.Assert(traces, gc.HasLen, len(macaroons))
// Check that we can run through the resulting operations and
// arrive at the same signature.
for i, m := range macaroons {
r := traces[i].Results()
c.Assert(b64str(r[len(r)-1]), gc.Equals, b64str(m.Signature()), gc.Commentf("macaroon %d", i))
}
}
func (*macaroonSuite) TestTraceVerifyFailure(c *gc.C) {
rootKey, macaroons := makeMacaroons([]macaroonSpec{{
rootKey: "xxx",
id: "hello",
caveats: []caveat{{
condition: "cond1",
}, {
condition: "cond2",
}, {
condition: "cond3",
}},
}})
// Marshal the macaroon, corrupt a condition, then unmarshal
// it and check we see the expected trace failure.
data, err := json.Marshal(macaroons[0])
c.Assert(err, gc.Equals, nil)
var jm macaroon.MacaroonJSONV2
err = json.Unmarshal(data, &jm)
c.Assert(err, gc.Equals, nil)
jm.Caveats[1].CID = "cond2 corrupted"
data, err = json.Marshal(jm)
c.Assert(err, gc.Equals, nil)
var corruptm *macaroon.Macaroon
err = json.Unmarshal(data, &corruptm)
c.Assert(err, gc.Equals, nil)
traces, err := corruptm.TraceVerify(rootKey, nil)
c.Assert(err, gc.ErrorMatches, `signature mismatch after caveat verification`)
c.Assert(traces, gc.HasLen, 1)
var kinds []macaroon.TraceOpKind
for _, op := range traces[0].Ops {
kinds = append(kinds, op.Kind)
}
c.Assert(kinds, gc.DeepEquals, []macaroon.TraceOpKind{
macaroon.TraceMakeKey,
macaroon.TraceHash, // id
macaroon.TraceHash, // cond1
macaroon.TraceHash, // cond2
macaroon.TraceHash, // cond3
macaroon.TraceFail, // sig mismatch
})
}
func b64str(b []byte) string {
return base64.StdEncoding.EncodeToString(b)
}
func (*macaroonSuite) TestVerifySignature(c *gc.C) {
rootKey, macaroons := makeMacaroons([]macaroonSpec{{
rootKey: "xxx",
id: "hello",
caveats: []caveat{{
rootKey: "y",
condition: "something",
location: "somewhere",
}, {
condition: "cond1",
}, {
condition: "cond2",
}},
}, {
rootKey: "y",
id: "something",
caveats: []caveat{{
condition: "cond3",
}, {
condition: "cond4",
}},
}})
conds, err := macaroons[0].VerifySignature(rootKey, macaroons[1:])
c.Assert(err, gc.IsNil)
c.Assert(conds, jc.DeepEquals, []string{"cond3", "cond4", "cond1", "cond2"})
conds, err = macaroons[0].VerifySignature(nil, macaroons[1:])
c.Assert(err, gc.ErrorMatches, `failed to decrypt caveat 0 signature: decryption failure`)
c.Assert(conds, gc.IsNil)
}
// TODO(rog) move the following JSON-marshal tests into marshal_test.go.
// jsonTestVersions holds the various possible ways of marshaling a macaroon
// to JSON.
var jsonTestVersions = []macaroon.Version{
macaroon.V1,
macaroon.V2,
}
func (s *macaroonSuite) TestMarshalJSON(c *gc.C) {
for i, vers := range jsonTestVersions {
c.Logf("test %d: %v", i, vers)
s.testMarshalJSONWithVersion(c, vers)
}
}
func (*macaroonSuite) testMarshalJSONWithVersion(c *gc.C, vers macaroon.Version) {
rootKey := []byte("secret")
m0 := MustNew(rootKey, []byte("some id"), "a location", vers)
m0.AddFirstPartyCaveat([]byte("account = 3735928559"))
m0JSON, err := json.Marshal(m0)
c.Assert(err, gc.IsNil)
var m1 macaroon.Macaroon
err = json.Unmarshal(m0JSON, &m1)
c.Assert(err, gc.IsNil)
c.Assert(m0.Location(), gc.Equals, m1.Location())
c.Assert(string(m0.Id()), gc.Equals, string(m1.Id()))
c.Assert(
hex.EncodeToString(m0.Signature()),
gc.Equals,
hex.EncodeToString(m1.Signature()))
c.Assert(m1.Version(), gc.Equals, vers)
}
var jsonRoundTripTests = []struct {
about string
// data holds the marshaled data. All the data values hold
// different encodings of the same macaroon - the same as produced
// from the second example in libmacaroons
// example README with the following libmacaroons code:
//
// secret = 'this is a different super-secret key; never use the same secret twice'
// public = 'we used our other secret key'
// location = 'http://mybank/'
// M = macaroons.create(location, secret, public)
// M = M.add_first_party_caveat('account = 3735928559')
// caveat_key = '4; guaranteed random by a fair toss of the dice'
// predicate = 'user = Alice'
// identifier = 'this was how we remind auth of key/pred'
// M = M.add_third_party_caveat('http://auth.mybank/', caveat_key, identifier)
// m.serialize_json()
data string
expectExactRoundTrip bool
expectVers macaroon.Version
}{{
about: "exact JSON as produced by libmacaroons",
data: `{"caveats":[{"cid":"account = 3735928559"},{"cid":"this was how we remind auth of key\/pred","vid":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA027FAuBYhtHwJ58FX6UlVNFtFsGxQHS7uD_w_dedwv4Jjw7UorCREw5rXbRqIKhr","cl":"http:\/\/auth.mybank\/"}],"location":"http:\/\/mybank\/","identifier":"we used our other secret key","signature":"d27db2fd1f22760e4c3dae8137e2d8fc1df6c0741c18aed4b97256bf78d1f55c"}`,
expectVers: macaroon.V1,
expectExactRoundTrip: true,
}, {
about: "V2 object with std base-64 binary values",
data: `{"c":[{"i64":"YWNjb3VudCA9IDM3MzU5Mjg1NTk="},{"i64":"dGhpcyB3YXMgaG93IHdlIHJlbWluZCBhdXRoIG9mIGtleS9wcmVk","v64":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA027FAuBYhtHwJ58FX6UlVNFtFsGxQHS7uD/w/dedwv4Jjw7UorCREw5rXbRqIKhr","l":"http://auth.mybank/"}],"l":"http://mybank/","i64":"d2UgdXNlZCBvdXIgb3RoZXIgc2VjcmV0IGtleQ==","s64":"0n2y/R8idg5MPa6BN+LY/B32wHQcGK7UuXJWv3jR9Vw="}`,
expectVers: macaroon.V2,
}, {
about: "V2 object with URL base-64 binary values",
data: `{"c":[{"i64":"YWNjb3VudCA9IDM3MzU5Mjg1NTk"},{"i64":"dGhpcyB3YXMgaG93IHdlIHJlbWluZCBhdXRoIG9mIGtleS9wcmVk","v64":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA027FAuBYhtHwJ58FX6UlVNFtFsGxQHS7uD_w_dedwv4Jjw7UorCREw5rXbRqIKhr","l":"http://auth.mybank/"}],"l":"http://mybank/","i64":"d2UgdXNlZCBvdXIgb3RoZXIgc2VjcmV0IGtleQ","s64":"0n2y_R8idg5MPa6BN-LY_B32wHQcGK7UuXJWv3jR9Vw"}`,
expectVers: macaroon.V2,
}, {
about: "V2 object with URL base-64 binary values and strings for ASCII",
data: `{"c":[{"i":"account = 3735928559"},{"i":"this was how we remind auth of key/pred","v64":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA027FAuBYhtHwJ58FX6UlVNFtFsGxQHS7uD_w_dedwv4Jjw7UorCREw5rXbRqIKhr","l":"http://auth.mybank/"}],"l":"http://mybank/","i":"we used our other secret key","s64":"0n2y_R8idg5MPa6BN-LY_B32wHQcGK7UuXJWv3jR9Vw"}`,
expectVers: macaroon.V2,
expectExactRoundTrip: true,
}, {
about: "V2 base64 encoded binary",
data: `"` +
base64.StdEncoding.EncodeToString([]byte(
"\x02"+
"\x01\x0ehttp://mybank/"+
"\x02\x1cwe used our other secret key"+
"\x00"+
"\x02\x14account = 3735928559"+
"\x00"+
"\x01\x13http://auth.mybank/"+
"\x02'this was how we remind auth of key/pred"+
"\x04\x48\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\x6e\xc5\x02\xe0\x58\x86\xd1\xf0\x27\x9f\x05\x5f\xa5\x25\x54\xd1\x6d\x16\xc1\xb1\x40\x74\xbb\xb8\x3f\xf0\xfd\xd7\x9d\xc2\xfe\x09\x8f\x0e\xd4\xa2\xb0\x91\x13\x0e\x6b\x5d\xb4\x6a\x20\xa8\x6b"+
"\x00"+
"\x00"+
"\x06\x20\xd2\x7d\xb2\xfd\x1f\x22\x76\x0e\x4c\x3d\xae\x81\x37\xe2\xd8\xfc\x1d\xf6\xc0\x74\x1c\x18\xae\xd4\xb9\x72\x56\xbf\x78\xd1\xf5\x5c",
)) + `"`,
expectVers: macaroon.V2,
}}
func (s *macaroonSuite) TestJSONRoundTrip(c *gc.C) {
for i, test := range jsonRoundTripTests {
c.Logf("test %d (%v) %s", i, test.expectVers, test.about)
s.testJSONRoundTripWithVersion(c, test.data, test.expectVers, test.expectExactRoundTrip)
}
}
func (*macaroonSuite) testJSONRoundTripWithVersion(c *gc.C, jsonData string, vers macaroon.Version, expectExactRoundTrip bool) {
var m macaroon.Macaroon
err := json.Unmarshal([]byte(jsonData), &m)
c.Assert(err, gc.IsNil)
assertLibMacaroonsMacaroon(c, &m)
c.Assert(m.Version(), gc.Equals, vers)
data, err := m.MarshalJSON()
c.Assert(err, gc.IsNil)
if expectExactRoundTrip {
// The data is in canonical form, so we can check that
// the round-tripped data is the same as the original
// data when unmarshalled into an interface{}.
var got interface{}
err = json.Unmarshal(data, &got)
c.Assert(err, gc.IsNil)
var original interface{}
err = json.Unmarshal([]byte(jsonData), &original)
c.Assert(err, gc.IsNil)
c.Assert(got, jc.DeepEquals, original, gc.Commentf("data: %s", data))
}
// Check that we can unmarshal the marshaled data anyway
// and the macaroon still looks the same.
var m1 macaroon.Macaroon
err = m1.UnmarshalJSON(data)
c.Assert(err, gc.IsNil)
assertLibMacaroonsMacaroon(c, &m1)
c.Assert(m.Version(), gc.Equals, vers)
}
// assertLibMacaroonsMacaroon asserts that m looks like the macaroon
// created in the README of the libmacaroons documentation.
// In particular, the signature is the same one reported there.
func assertLibMacaroonsMacaroon(c *gc.C, m *macaroon.Macaroon) {
c.Assert(fmt.Sprintf("%x", m.Signature()), gc.Equals,
"d27db2fd1f22760e4c3dae8137e2d8fc1df6c0741c18aed4b97256bf78d1f55c")
c.Assert(m.Location(), gc.Equals, "http://mybank/")
c.Assert(string(m.Id()), gc.Equals, "we used our other secret key")
c.Assert(m.Caveats(), jc.DeepEquals, []macaroon.Caveat{{
Id: []byte("account = 3735928559"),
}, {
Id: []byte("this was how we remind auth of key/pred"),
VerificationId: decodeB64("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA027FAuBYhtHwJ58FX6UlVNFtFsGxQHS7uD_w_dedwv4Jjw7UorCREw5rXbRqIKhr"),
Location: "http://auth.mybank/",
}})
}
var jsonDecodeErrorTests = []struct {
about string
data string
expectError string
}{{
about: "ambiguous id #1",
data: `{"i": "hello", "i64": "abcd", "s64": "ZDI3ZGIyZmQxZjIyNzYwZTRjM2RhZTgxMzdlMmQ4ZmMK"}`,
expectError: "invalid identifier: ambiguous field encoding",
}, {
about: "ambiguous signature",
data: `{"i": "hello", "s": "345", "s64": "543467"}`,
expectError: "invalid signature: ambiguous field encoding",
}, {
about: "signature too short",
data: `{"i": "hello", "s64": "0n2y/R8idg5MPa6BN+LY/B32wHQcGK7UuXJWv3jR9Q"}`,
expectError: "signature has unexpected length 31",
}, {
about: "signature too long",
data: `{"i": "hello", "s64": "0n2y/R8idg5MPa6BN+LY/B32wHQcGK7UuXJWv3jR9dP1"}`,
expectError: "signature has unexpected length 33",
}, {
about: "invalid caveat id",
data: `{"i": "hello", "s64": "0n2y/R8idg5MPa6BN+LY/B32wHQcGK7UuXJWv3jR9Vw", "c": [{"i": "hello", "i64": "00"}]}`,
expectError: "invalid cid in caveat: ambiguous field encoding",
}, {
about: "invalid caveat vid",
data: `{"i": "hello", "s64": "0n2y/R8idg5MPa6BN+LY/B32wHQcGK7UuXJWv3jR9Vw", "c": [{"i": "hello", "v": "hello", "v64": "00"}]}`,
expectError: "invalid vid in caveat: ambiguous field encoding",
}}
func (*macaroonSuite) TestJSONDecodeError(c *gc.C) {
for i, test := range jsonDecodeErrorTests {
c.Logf("test %d: %v", i, test.about)
var m macaroon.Macaroon
err := json.Unmarshal([]byte(test.data), &m)
c.Assert(err, gc.ErrorMatches, test.expectError)
}
}
func (*macaroonSuite) TestFirstPartyCaveatWithInvalidUTF8(c *gc.C) {
rootKey := []byte("secret")
badString := "foo\xff"
m0 := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion)
err := m0.AddFirstPartyCaveat([]byte(badString))
c.Assert(err, gc.Equals, nil)
}
func decodeB64(s string) []byte {
data, err := base64.RawURLEncoding.DecodeString(s)
if err != nil {
panic(err)
}
return data
}
type caveat struct {
rootKey string
location string
condition string
}
type macaroonSpec struct {
rootKey string
id string
caveats []caveat
location string
}
func makeMacaroons(mspecs []macaroonSpec) (rootKey []byte, macaroons macaroon.Slice) {
for _, mspec := range mspecs {
macaroons = append(macaroons, makeMacaroon(mspec))
}
primary := macaroons[0]
for _, m := range macaroons[1:] {
m.Bind(primary.Signature())
}
return []byte(mspecs[0].rootKey), macaroons
}
func makeMacaroon(mspec macaroonSpec) *macaroon.Macaroon {
m := MustNew([]byte(mspec.rootKey), []byte(mspec.id), mspec.location, macaroon.LatestVersion)
for _, cav := range mspec.caveats {
if cav.location != "" {
err := m.AddThirdPartyCaveat([]byte(cav.rootKey), []byte(cav.condition), cav.location)
if err != nil {
panic(err)
}
} else {
m.AddFirstPartyCaveat([]byte(cav.condition))
}
}
return m
}
func assertEqualMacaroons(c *gc.C, m0, m1 *macaroon.Macaroon) {
m0json, err := m0.MarshalJSON()
c.Assert(err, gc.IsNil)
m1json, err := m1.MarshalJSON()
var m0val, m1val interface{}
err = json.Unmarshal(m0json, &m0val)
c.Assert(err, gc.IsNil)
err = json.Unmarshal(m1json, &m1val)
c.Assert(err, gc.IsNil)
c.Assert(m0val, gc.DeepEquals, m1val)
}
func (*macaroonSuite) TestBinaryRoundTrip(c *gc.C) {
// Test the binary marshalling and unmarshalling of a macaroon with
// first and third party caveats.
rootKey := []byte("secret")
m0 := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion)
err := m0.AddFirstPartyCaveat([]byte("first caveat"))
c.Assert(err, gc.IsNil)
err = m0.AddFirstPartyCaveat([]byte("second caveat"))
c.Assert(err, gc.IsNil)
err = m0.AddThirdPartyCaveat([]byte("shared root key"), []byte("3rd party caveat"), "remote.com")
c.Assert(err, gc.IsNil)
data, err := m0.MarshalBinary()
c.Assert(err, gc.IsNil)
var m1 macaroon.Macaroon
err = m1.UnmarshalBinary(data)
c.Assert(err, gc.IsNil)
assertEqualMacaroons(c, m0, &m1)
}
func (*macaroonSuite) TestBinaryMarshalingAgainstLibmacaroon(c *gc.C) {
// Test that a libmacaroon marshalled macaroon can be correctly unmarshaled
data, err := base64.RawURLEncoding.DecodeString(
"MDAxY2xvY2F0aW9uIGh0dHA6Ly9teWJhbmsvCjAwMmNpZGVudGlmaWVyIHdlIHVzZWQgb3VyIG90aGVyIHNlY3JldCBrZXkKMDAxZGNpZCBhY2NvdW50ID0gMzczNTkyODU1OQowMDMwY2lkIHRoaXMgd2FzIGhvdyB3ZSByZW1pbmQgYXV0aCBvZiBrZXkvcHJlZAowMDUxdmlkIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANNuxQLgWIbR8CefBV-lJVTRbRbBsUB0u7g_8P3XncL-CY8O1KKwkRMOa120aiCoawowMDFiY2wgaHR0cDovL2F1dGgubXliYW5rLwowMDJmc2lnbmF0dXJlINJ9sv0fInYOTD2ugTfi2Pwd9sB0HBiu1LlyVr940fVcCg")
c.Assert(err, gc.IsNil)
var m0 macaroon.Macaroon
err = m0.UnmarshalBinary(data)
c.Assert(err, gc.IsNil)
jsonData := []byte(`{"caveats":[{"cid":"account = 3735928559"},{"cid":"this was how we remind auth of key\/pred","vid":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA027FAuBYhtHwJ58FX6UlVNFtFsGxQHS7uD_w_dedwv4Jjw7UorCREw5rXbRqIKhr","cl":"http:\/\/auth.mybank\/"}],"location":"http:\/\/mybank\/","identifier":"we used our other secret key","signature":"d27db2fd1f22760e4c3dae8137e2d8fc1df6c0741c18aed4b97256bf78d1f55c"}`)
var m1 macaroon.Macaroon
err = m1.UnmarshalJSON(jsonData)
c.Assert(err, gc.IsNil)
assertEqualMacaroons(c, &m0, &m1)
}
var binaryFieldBase64ChoiceTests = []struct {
id string
expectBase64 bool
}{
{"x", false},
{"\x00", true},
{"\x03\x00", true},
{"a longer id with more stuff", false},
{"a longer id with more stuff and one invalid \xff", true},
{"a longer id with more stuff and one encoded \x00", false},
}
func (*macaroonSuite) TestBinaryFieldBase64Choice(c *gc.C) {
for i, test := range binaryFieldBase64ChoiceTests {
c.Logf("test %d: %q", i, test.id)
m := MustNew([]byte{0}, []byte(test.id), "", macaroon.LatestVersion)
data, err := json.Marshal(m)
c.Assert(err, gc.Equals, nil)
var x struct {
Id *string `json:"i"`
Id64 *string `json:"i64"`
}
err = json.Unmarshal(data, &x)
c.Assert(err, gc.Equals, nil)
if test.expectBase64 {
c.Assert(x.Id64, gc.NotNil)
c.Assert(x.Id, gc.IsNil)
idDec, err := base64.RawURLEncoding.DecodeString(*x.Id64)
c.Assert(err, gc.Equals, nil)
c.Assert(string(idDec), gc.Equals, test.id)
} else {
c.Assert(x.Id64, gc.IsNil)
c.Assert(x.Id, gc.NotNil)
c.Assert(*x.Id, gc.Equals, test.id)
}
}
}