pax_global_header00006660000000000000000000000064133372342070014516gustar00rootroot0000000000000052 comment=1679699b0b723e05f9f16c45b4c6cbd46adb2c78 macaroon-2.1.0/000077500000000000000000000000001333723420700133155ustar00rootroot00000000000000macaroon-2.1.0/.gitignore000066400000000000000000000000071333723420700153020ustar00rootroot00000000000000*.test macaroon-2.1.0/.travis.yml000066400000000000000000000004141333723420700154250ustar00rootroot00000000000000language: go go_import_path: "gopkg.in/macaroon.v2" go: - "1.7" - "1.10" before_install: - "go get github.com/rogpeppe/godeps" install: - "go get -d gopkg.in/macaroon.v2" - "godeps -u $GOPATH/src/gopkg.in/macaroon.v2/dependencies.tsv" script: go test ./... macaroon-2.1.0/LICENSE000066400000000000000000000027451333723420700143320ustar00rootroot00000000000000Copyright © 2014, Roger Peppe All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of this project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. macaroon-2.1.0/README.md000066400000000000000000000222741333723420700146030ustar00rootroot00000000000000# macaroon -- import "gopkg.in/macaroon.v2" The macaroon package implements macaroons as described in the paper "Macaroons: Cookies with Contextual Caveats for Decentralized Authorization in the Cloud" (http://theory.stanford.edu/~ataly/Papers/macaroons.pdf) See the macaroon bakery packages at http://godoc.org/gopkg.in/macaroon-bakery.v2 for higher level services and operations that use macaroons. ## Usage ```go const ( TraceInvalid = TraceOpKind(iota) // TraceMakeKey represents the operation of calculating a // fixed length root key from the variable length input key. TraceMakeKey // TraceHash represents a keyed hash operation with one // or two values. If there is only one value, it will be in Data1. TraceHash // TraceBind represents the operation of binding a discharge macaroon // to its primary macaroon. Data1 holds the signature of the primary // macaroon. TraceBind // TraceFail represents a verification failure. If present, this will always // be the last operation in a trace. TraceFail ) ``` #### func Base64Decode ```go func Base64Decode(data []byte) ([]byte, error) ``` Base64Decode base64-decodes the given data. It accepts both standard and URL encodings, both padded and unpadded. #### type Caveat ```go type Caveat struct { // Id holds the id of the caveat. For first // party caveats this holds the condition; // for third party caveats this holds the encrypted // third party caveat. Id []byte // VerificationId holds the verification id. If this is // non-empty, it's a third party caveat. VerificationId []byte // For third-party caveats, Location holds the // ocation hint. Note that this is not signature checked // as part of the caveat, so should only // be used as a hint. Location string } ``` Caveat holds a first person or third party caveat. #### type Macaroon ```go type Macaroon struct { } ``` Macaroon holds a macaroon. See Fig. 7 of http://theory.stanford.edu/~ataly/Papers/macaroons.pdf for a description of the data contained within. Macaroons are mutable objects - use Clone as appropriate to avoid unwanted mutation. #### func New ```go func New(rootKey, id []byte, loc string, version Version) (*Macaroon, error) ``` New returns a new macaroon with the given root key, identifier, location and version. #### func (*Macaroon) AddFirstPartyCaveat ```go func (m *Macaroon) AddFirstPartyCaveat(condition []byte) error ``` AddFirstPartyCaveat adds a caveat that will be verified by the target service. #### func (*Macaroon) AddThirdPartyCaveat ```go func (m *Macaroon) AddThirdPartyCaveat(rootKey, caveatId []byte, loc string) error ``` AddThirdPartyCaveat adds a third-party caveat to the macaroon, using the given shared root key, caveat id and location hint. The caveat id should encode the root key in some way, either by encrypting it with a key known to the third party or by holding a reference to it stored in the third party's storage. #### func (*Macaroon) Bind ```go func (m *Macaroon) Bind(sig []byte) ``` Bind prepares the macaroon for being used to discharge the macaroon with the given signature sig. This must be used before it is used in the discharges argument to Verify. #### func (*Macaroon) Caveats ```go func (m *Macaroon) Caveats() []Caveat ``` Caveats returns the macaroon's caveats. This method will probably change, and it's important not to change the returned caveat. #### func (*Macaroon) Clone ```go func (m *Macaroon) Clone() *Macaroon ``` Clone returns a copy of the receiving macaroon. #### func (*Macaroon) Id ```go func (m *Macaroon) Id() []byte ``` Id returns the id of the macaroon. This can hold arbitrary information. #### func (*Macaroon) Location ```go func (m *Macaroon) Location() string ``` Location returns the macaroon's location hint. This is not verified as part of the macaroon. #### func (*Macaroon) MarshalBinary ```go func (m *Macaroon) MarshalBinary() ([]byte, error) ``` MarshalBinary implements encoding.BinaryMarshaler by formatting the macaroon according to the version specified by MarshalAs. #### func (*Macaroon) MarshalJSON ```go func (m *Macaroon) MarshalJSON() ([]byte, error) ``` MarshalJSON implements json.Marshaler by marshaling the macaroon in JSON format. The serialisation format is determined by the macaroon's version. #### func (*Macaroon) SetLocation ```go func (m *Macaroon) SetLocation(loc string) ``` SetLocation sets the location associated with the macaroon. Note that the location is not included in the macaroon's hash chain, so this does not change the signature. #### func (*Macaroon) Signature ```go func (m *Macaroon) Signature() []byte ``` Signature returns the macaroon's signature. #### func (*Macaroon) TraceVerify ```go func (m *Macaroon) TraceVerify(rootKey []byte, discharges []*Macaroon) ([]Trace, error) ``` TraceVerify verifies the signature of the macaroon without checking any of the first party caveats, and returns a slice of Traces holding the operations used when verifying the macaroons. Each element in the returned slice corresponds to the operation for one of the argument macaroons, with m at index 0, and discharges at 1 onwards. #### func (*Macaroon) UnmarshalBinary ```go func (m *Macaroon) UnmarshalBinary(data []byte) error ``` UnmarshalBinary implements encoding.BinaryUnmarshaler. It accepts both V1 and V2 binary encodings. #### func (*Macaroon) UnmarshalJSON ```go func (m *Macaroon) UnmarshalJSON(data []byte) error ``` UnmarshalJSON implements json.Unmarshaller by unmarshaling the given macaroon in JSON format. It accepts both V1 and V2 forms encoded forms, and also a base64-encoded JSON string containing the binary-marshaled macaroon. After unmarshaling, the macaroon's version will reflect the version that it was unmarshaled as. #### func (*Macaroon) Verify ```go func (m *Macaroon) Verify(rootKey []byte, check func(caveat string) error, discharges []*Macaroon) error ``` Verify verifies that the receiving macaroon is valid. The root key must be the same that the macaroon was originally minted with. The check function is called to verify each first-party caveat - it should return an error if the condition is not met. The discharge macaroons should be provided in discharges. Verify returns nil if the verification succeeds. #### func (*Macaroon) VerifySignature ```go func (m *Macaroon) VerifySignature(rootKey []byte, discharges []*Macaroon) ([]string, error) ``` VerifySignature verifies the signature of the given macaroon with respect to the root key, but it does not validate any first-party caveats. Instead it returns all the applicable first party caveats on success. The caller is responsible for checking the returned first party caveat conditions. #### func (*Macaroon) Version ```go func (m *Macaroon) Version() Version ``` Version returns the version of the macaroon. #### type Slice ```go type Slice []*Macaroon ``` Slice defines a collection of macaroons. By convention, the first macaroon in the slice is a primary macaroon and the rest are discharges for its third party caveats. #### func (Slice) MarshalBinary ```go func (s Slice) MarshalBinary() ([]byte, error) ``` MarshalBinary implements encoding.BinaryMarshaler. #### func (*Slice) UnmarshalBinary ```go func (s *Slice) UnmarshalBinary(data []byte) error ``` UnmarshalBinary implements encoding.BinaryUnmarshaler. It accepts all known binary encodings for the data - all the embedded macaroons need not be encoded in the same format. #### type Trace ```go type Trace struct { RootKey []byte Ops []TraceOp } ``` Trace holds all toperations involved in verifying a macaroon, and the root key used as the initial verification key. This can be useful for debugging macaroon implementations. #### func (Trace) Results ```go func (t Trace) Results() [][]byte ``` Results returns the output from all operations in the Trace. The result from ts.Ops[i] will be in the i'th element of the returned slice. When a trace has resulted in a failure, the last element will be nil. #### type TraceOp ```go type TraceOp struct { Kind TraceOpKind `json:"kind"` Data1 []byte `json:"data1,omitempty"` Data2 []byte `json:"data2,omitempty"` } ``` TraceOp holds one possible operation when verifying a macaroon. #### func (TraceOp) Result ```go func (op TraceOp) Result(input []byte) []byte ``` Result returns the result of computing the given operation with the given input data. If op is TraceFail, it returns nil. #### type TraceOpKind ```go type TraceOpKind int ``` TraceOpKind represents the kind of a macaroon verification operation. #### func (TraceOpKind) String ```go func (k TraceOpKind) String() string ``` String returns a string representation of the operation. #### type Version ```go type Version uint16 ``` Version specifies the version of a macaroon. In version 1, the macaroon id and all caveats must be UTF-8-compatible strings, and the size of any part of the macaroon may not exceed approximately 64K. In version 2, all field may be arbitrary binary blobs. ```go const ( // V1 specifies version 1 macaroons. V1 Version = 1 // V2 specifies version 2 macaroons. V2 Version = 2 // LatestVersion holds the latest supported version. LatestVersion = V2 ) ``` #### func (Version) String ```go func (v Version) String() string ``` String returns a string representation of the version; for example V1 formats as "v1". macaroon-2.1.0/TODO000066400000000000000000000001371333723420700140060ustar00rootroot00000000000000macaroon: - verify that all signature calculations to correspond exactly with libmacaroons. macaroon-2.1.0/bench_test.go000066400000000000000000000051111333723420700157600ustar00rootroot00000000000000package macaroon_test import ( "crypto/rand" "encoding/base64" "testing" "gopkg.in/macaroon.v2" ) func randomBytes(n int) []byte { b := make([]byte, n) _, err := rand.Read(b) if err != nil { panic(err) } return b } func BenchmarkNew(b *testing.B) { rootKey := randomBytes(24) id := []byte(base64.StdEncoding.EncodeToString(randomBytes(100))) loc := base64.StdEncoding.EncodeToString(randomBytes(40)) b.ResetTimer() for i := b.N - 1; i >= 0; i-- { MustNew(rootKey, id, loc, macaroon.LatestVersion) } } func BenchmarkAddCaveat(b *testing.B) { rootKey := randomBytes(24) id := []byte(base64.StdEncoding.EncodeToString(randomBytes(100))) loc := base64.StdEncoding.EncodeToString(randomBytes(40)) b.ResetTimer() for i := b.N - 1; i >= 0; i-- { b.StopTimer() m := MustNew(rootKey, id, loc, macaroon.LatestVersion) b.StartTimer() m.AddFirstPartyCaveat([]byte("some caveat stuff")) } } func benchmarkVerify(b *testing.B, mspecs []macaroonSpec) { rootKey, macaroons := makeMacaroons(mspecs) check := func(string) error { return nil } b.ResetTimer() for i := b.N - 1; i >= 0; i-- { err := macaroons[0].Verify(rootKey, check, macaroons[1:]) if err != nil { b.Fatalf("verification failed: %v", err) } } } func BenchmarkVerifyLarge(b *testing.B) { benchmarkVerify(b, multilevelThirdPartyCaveatMacaroons) } func BenchmarkVerifySmall(b *testing.B) { benchmarkVerify(b, []macaroonSpec{{ rootKey: "root-key", id: "root-id", caveats: []caveat{{ condition: "wonderful", }}, }}) } func BenchmarkMarshalJSON(b *testing.B) { rootKey := randomBytes(24) id := []byte(base64.StdEncoding.EncodeToString(randomBytes(100))) loc := base64.StdEncoding.EncodeToString(randomBytes(40)) m := MustNew(rootKey, id, loc, macaroon.LatestVersion) b.ResetTimer() for i := b.N - 1; i >= 0; i-- { _, err := m.MarshalJSON() if err != nil { b.Fatalf("cannot marshal JSON: %v", err) } } } func MustNew(rootKey, id []byte, loc string, vers macaroon.Version) *macaroon.Macaroon { m, err := macaroon.New(rootKey, id, loc, vers) if err != nil { panic(err) } return m } func BenchmarkUnmarshalJSON(b *testing.B) { rootKey := randomBytes(24) id := []byte(base64.StdEncoding.EncodeToString(randomBytes(100))) loc := base64.StdEncoding.EncodeToString(randomBytes(40)) m := MustNew(rootKey, id, loc, macaroon.LatestVersion) data, err := m.MarshalJSON() if err != nil { b.Fatalf("cannot marshal JSON: %v", err) } for i := b.N - 1; i >= 0; i-- { var m macaroon.Macaroon err := m.UnmarshalJSON(data) if err != nil { b.Fatalf("cannot unmarshal JSON: %v", err) } } } macaroon-2.1.0/crypto.go000066400000000000000000000041241333723420700151650ustar00rootroot00000000000000package macaroon import ( "crypto/hmac" "crypto/sha256" "fmt" "hash" "io" "golang.org/x/crypto/nacl/secretbox" ) func keyedHash(key *[hashLen]byte, text []byte) *[hashLen]byte { h := keyedHasher(key) h.Write([]byte(text)) var sum [hashLen]byte hashSum(h, &sum) return &sum } func keyedHasher(key *[hashLen]byte) hash.Hash { return hmac.New(sha256.New, key[:]) } var keyGen = []byte("macaroons-key-generator") // makeKey derives a fixed length key from a variable // length key. The keyGen constant is the same // as that used in libmacaroons. func makeKey(variableKey []byte) *[keyLen]byte { h := hmac.New(sha256.New, keyGen) h.Write(variableKey) var key [keyLen]byte hashSum(h, &key) return &key } // hashSum calls h.Sum to put the sum into // the given destination. It also sanity // checks that the result really is the expected // size. func hashSum(h hash.Hash, dest *[hashLen]byte) { r := h.Sum(dest[:0]) if len(r) != len(dest) { panic("hash size inconsistency") } } const ( keyLen = 32 nonceLen = 24 hashLen = sha256.Size ) func newNonce(r io.Reader) (*[nonceLen]byte, error) { var nonce [nonceLen]byte _, err := r.Read(nonce[:]) if err != nil { return nil, fmt.Errorf("cannot generate random bytes: %v", err) } return &nonce, nil } func encrypt(key *[keyLen]byte, text *[hashLen]byte, r io.Reader) ([]byte, error) { nonce, err := newNonce(r) if err != nil { return nil, err } out := make([]byte, 0, len(nonce)+secretbox.Overhead+len(text)) out = append(out, nonce[:]...) return secretbox.Seal(out, text[:], nonce, key), nil } func decrypt(key *[keyLen]byte, ciphertext []byte) (*[hashLen]byte, error) { if len(ciphertext) < nonceLen+secretbox.Overhead { return nil, fmt.Errorf("message too short") } var nonce [nonceLen]byte copy(nonce[:], ciphertext) ciphertext = ciphertext[nonceLen:] text, ok := secretbox.Open(nil, ciphertext, &nonce, key) if !ok { return nil, fmt.Errorf("decryption failure") } if len(text) != hashLen { return nil, fmt.Errorf("decrypted text is wrong length") } var rtext [hashLen]byte copy(rtext[:], text) return &rtext, nil } macaroon-2.1.0/crypto_test.go000066400000000000000000000032231333723420700162230ustar00rootroot00000000000000package macaroon import ( "crypto/rand" "fmt" "testing" qt "github.com/frankban/quicktest" "golang.org/x/crypto/nacl/secretbox" ) var testCryptKey = &[hashLen]byte{'k', 'e', 'y'} var testCryptText = &[hashLen]byte{'t', 'e', 'x', 't'} func TestEncDec(t *testing.T) { c := qt.New(t) b, err := encrypt(testCryptKey, testCryptText, rand.Reader) c.Assert(err, qt.Equals, nil) p, err := decrypt(testCryptKey, b) c.Assert(err, qt.Equals, nil) c.Assert(string(p[:]), qt.Equals, string(testCryptText[:])) } func TestUniqueNonces(t *testing.T) { c := qt.New(t) nonces := make(map[string]struct{}) for i := 0; i < 100; i++ { nonce, err := newNonce(rand.Reader) c.Assert(err, qt.Equals, nil) nonces[string(nonce[:])] = struct{}{} } c.Assert(nonces, qt.HasLen, 100, qt.Commentf("duplicate nonce detected")) } type ErrorReader struct{} func (*ErrorReader) Read([]byte) (int, error) { return 0, fmt.Errorf("fail") } func TestBadRandom(t *testing.T) { c := qt.New(t) _, err := newNonce(&ErrorReader{}) c.Assert(err, qt.ErrorMatches, "^cannot generate random bytes:.*") _, err = encrypt(testCryptKey, testCryptText, &ErrorReader{}) c.Assert(err, qt.ErrorMatches, "^cannot generate random bytes:.*") } func TestBadCiphertext(t *testing.T) { c := qt.New(t) buf := randomBytes(nonceLen + secretbox.Overhead) for i := range buf { _, err := decrypt(testCryptKey, buf[0:i]) c.Assert(err, qt.ErrorMatches, "message too short") } _, err := decrypt(testCryptKey, buf) c.Assert(err, qt.ErrorMatches, "decryption failure") } func randomBytes(n int) []byte { buf := make([]byte, n) if _, err := rand.Reader.Read(buf); err != nil { panic(err) } return buf } macaroon-2.1.0/dependencies.tsv000066400000000000000000000006751333723420700165110ustar00rootroot00000000000000github.com/frankban/quicktest git 2c6a0d60c05cd2d970f356eee0623ddf1cd0d62d 2018-02-06T12:35:47Z github.com/google/go-cmp git 5411ab924f9ffa6566244a9e504bc347edacffd3 2018-03-28T20:15:12Z github.com/kr/pretty git cfb55aafdaf3ec08f0db22699ab822c50091b1c4 2016-08-23T17:07:15Z github.com/kr/text git 7cafcd837844e784b526369c9bce262804aebc60 2016-05-04T23:40:17Z golang.org/x/crypto git 96846453c37f0876340a66a47f3f75b1f3a6cd2d 2017-04-21T04:31:20Z macaroon-2.1.0/export_test.go000066400000000000000000000005331333723420700162250ustar00rootroot00000000000000package macaroon var ( AddThirdPartyCaveatWithRand = (*Macaroon).addThirdPartyCaveatWithRand ) type MacaroonJSONV2 macaroonJSONV2 // SetVersion sets the version field of m to v; // usually so that we can compare it for deep equality with // another differently unmarshaled macaroon. func (m *Macaroon) SetVersion(v Version) { m.version = v } macaroon-2.1.0/go.mod000066400000000000000000000002501333723420700144200ustar00rootroot00000000000000module gopkg.in/macaroon.v2 require ( github.com/frankban/quicktest v1.0.0 github.com/google/go-cmp v0.2.0 golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb ) macaroon-2.1.0/go.sum000066400000000000000000000015471333723420700144570ustar00rootroot00000000000000github.com/frankban/quicktest v1.0.0 h1:QgmxFbprE29UG4oL88tGiiL/7VuiBl5xCcz+wJcJhc0= github.com/frankban/quicktest v1.0.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb h1:Ah9YqXLj6fEgeKqcmBuLCbAsrF3ScD7dJ/bYM0C6tXI= golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= macaroon-2.1.0/macaroon.go000066400000000000000000000302541333723420700154470ustar00rootroot00000000000000// The macaroon package implements macaroons as described in // the paper "Macaroons: Cookies with Contextual Caveats for // Decentralized Authorization in the Cloud" // (http://theory.stanford.edu/~ataly/Papers/macaroons.pdf) // // See the macaroon bakery packages at http://godoc.org/gopkg.in/macaroon-bakery.v2 // for higher level services and operations that use macaroons. package macaroon import ( "bytes" "crypto/hmac" "crypto/rand" "fmt" "io" "unicode/utf8" ) // Macaroon holds a macaroon. // See Fig. 7 of http://theory.stanford.edu/~ataly/Papers/macaroons.pdf // for a description of the data contained within. // Macaroons are mutable objects - use Clone as appropriate // to avoid unwanted mutation. type Macaroon struct { location string id []byte caveats []Caveat sig [hashLen]byte version Version } // Equal reports whether m has exactly the same content as m1. func (m *Macaroon) Equal(m1 *Macaroon) bool { if m == m1 || m == nil || m1 == nil { return m == m1 } if m.location != m1.location || !bytes.Equal(m.id, m1.id) || m.sig != m1.sig || m.version != m1.version || len(m.caveats) != len(m1.caveats) { return false } for i, c := range m.caveats { if !c.Equal(m1.caveats[i]) { return false } } return true } // Caveat holds a first party or third party caveat. type Caveat struct { // Id holds the id of the caveat. For first // party caveats this holds the condition; // for third party caveats this holds the encrypted // third party caveat. Id []byte // VerificationId holds the verification id. If this is // non-empty, it's a third party caveat. VerificationId []byte // For third-party caveats, Location holds the // ocation hint. Note that this is not signature checked // as part of the caveat, so should only // be used as a hint. Location string } // Equal reports whether c is equal to c1. func (c Caveat) Equal(c1 Caveat) bool { return bytes.Equal(c.Id, c1.Id) && bytes.Equal(c.VerificationId, c1.VerificationId) && c.Location == c1.Location } // isThirdParty reports whether the caveat must be satisfied // by some third party (if not, it's a first person caveat). func (cav *Caveat) isThirdParty() bool { return len(cav.VerificationId) > 0 } // New returns a new macaroon with the given root key, // identifier, location and version. func New(rootKey, id []byte, loc string, version Version) (*Macaroon, error) { var m Macaroon if version < V2 { if !utf8.Valid(id) { return nil, fmt.Errorf("invalid id for %v macaroon", id) } // TODO check id length too. } if version < V1 || version > LatestVersion { return nil, fmt.Errorf("invalid version %v", version) } m.version = version m.init(append([]byte(nil), id...), loc, version) derivedKey := makeKey(rootKey) m.sig = *keyedHash(derivedKey, m.id) return &m, nil } // init initializes the macaroon. It retains a reference to id. func (m *Macaroon) init(id []byte, loc string, vers Version) { m.location = loc m.id = append([]byte(nil), id...) m.version = vers } // SetLocation sets the location associated with the macaroon. // Note that the location is not included in the macaroon's // hash chain, so this does not change the signature. func (m *Macaroon) SetLocation(loc string) { m.location = loc } // Clone returns a copy of the receiving macaroon. func (m *Macaroon) Clone() *Macaroon { m1 := *m // Ensure that if any caveats are appended to the new // macaroon, it will copy the caveats. m1.caveats = m1.caveats[0:len(m1.caveats):len(m1.caveats)] return &m1 } // Location returns the macaroon's location hint. This is // not verified as part of the macaroon. func (m *Macaroon) Location() string { return m.location } // Id returns the id of the macaroon. This can hold // arbitrary information. func (m *Macaroon) Id() []byte { return append([]byte(nil), m.id...) } // Signature returns the macaroon's signature. func (m *Macaroon) Signature() []byte { // sig := m.sig // return sig[:] // Work around https://github.com/golang/go/issues/9537 sig := new([hashLen]byte) *sig = m.sig return sig[:] } // Caveats returns the macaroon's caveats. // This method will probably change, and it's important not to change the returned caveat. func (m *Macaroon) Caveats() []Caveat { return m.caveats[0:len(m.caveats):len(m.caveats)] } // appendCaveat appends a caveat without modifying the macaroon's signature. func (m *Macaroon) appendCaveat(caveatId, verificationId []byte, loc string) { if len(verificationId) == 0 { // Ensure that an empty vid is always represented by nil, // so that marshalers don't procuce spurious zero-length // vid fields which can confuse some verifiers. verificationId = nil } m.caveats = append(m.caveats, Caveat{ Id: caveatId, VerificationId: verificationId, Location: loc, }) } func (m *Macaroon) addCaveat(caveatId, verificationId []byte, loc string) error { if m.version < V2 { if !utf8.Valid(caveatId) { return fmt.Errorf("invalid caveat id for %v macaroon", m.version) } // TODO check caveat length too. } m.appendCaveat(caveatId, verificationId, loc) if len(verificationId) == 0 { m.sig = *keyedHash(&m.sig, caveatId) } else { m.sig = *keyedHash2(&m.sig, verificationId, caveatId) } return nil } func keyedHash2(key *[keyLen]byte, d1, d2 []byte) *[hashLen]byte { var data [hashLen * 2]byte copy(data[0:], keyedHash(key, d1)[:]) copy(data[hashLen:], keyedHash(key, d2)[:]) return keyedHash(key, data[:]) } // Bind prepares the macaroon for being used to discharge the // macaroon with the given signature sig. This must be // used before it is used in the discharges argument to Verify. func (m *Macaroon) Bind(sig []byte) { m.sig = *bindForRequest(sig, &m.sig) } // AddFirstPartyCaveat adds a caveat that will be verified // by the target service. func (m *Macaroon) AddFirstPartyCaveat(condition []byte) error { m.addCaveat(condition, nil, "") return nil } // AddThirdPartyCaveat adds a third-party caveat to the macaroon, // using the given shared root key, caveat id and location hint. // The caveat id should encode the root key in some // way, either by encrypting it with a key known to the third party // or by holding a reference to it stored in the third party's // storage. func (m *Macaroon) AddThirdPartyCaveat(rootKey, caveatId []byte, loc string) error { return m.addThirdPartyCaveatWithRand(rootKey, caveatId, loc, rand.Reader) } // addThirdPartyCaveatWithRand adds a third-party caveat to the macaroon, using // the given source of randomness for encrypting the caveat id. func (m *Macaroon) addThirdPartyCaveatWithRand(rootKey, caveatId []byte, loc string, r io.Reader) error { derivedKey := makeKey(rootKey) verificationId, err := encrypt(&m.sig, derivedKey, r) if err != nil { return err } m.addCaveat(caveatId, verificationId, loc) return nil } var zeroKey [hashLen]byte // bindForRequest binds the given macaroon // to the given signature of its parent macaroon. func bindForRequest(rootSig []byte, dischargeSig *[hashLen]byte) *[hashLen]byte { if bytes.Equal(rootSig, dischargeSig[:]) { return dischargeSig } return keyedHash2(&zeroKey, rootSig, dischargeSig[:]) } // Verify verifies that the receiving macaroon is valid. // The root key must be the same that the macaroon was originally // minted with. The check function is called to verify each // first-party caveat - it should return an error if the // condition is not met. // // The discharge macaroons should be provided in discharges. // // Verify returns nil if the verification succeeds. func (m *Macaroon) Verify(rootKey []byte, check func(caveat string) error, discharges []*Macaroon) error { var vctx verificationContext vctx.init(rootKey, m, discharges, check) return vctx.verify(m, rootKey) } // VerifySignature verifies the signature of the given macaroon with respect // to the root key, but it does not validate any first-party caveats. Instead // it returns all the applicable first party caveats on success. // // The caller is responsible for checking the returned first party caveat // conditions. func (m *Macaroon) VerifySignature(rootKey []byte, discharges []*Macaroon) ([]string, error) { n := len(m.caveats) for _, dm := range discharges { n += len(dm.caveats) } conds := make([]string, 0, n) var vctx verificationContext vctx.init(rootKey, m, discharges, func(cond string) error { conds = append(conds, cond) return nil }) err := vctx.verify(m, rootKey) if err != nil { return nil, err } return conds, nil } // TraceVerify verifies the signature of the macaroon without checking // any of the first party caveats, and returns a slice of Traces holding // the operations used when verifying the macaroons. // // Each element in the returned slice corresponds to the // operation for one of the argument macaroons, with m at index 0, // and discharges at 1 onwards. func (m *Macaroon) TraceVerify(rootKey []byte, discharges []*Macaroon) ([]Trace, error) { var vctx verificationContext vctx.init(rootKey, m, discharges, func(string) error { return nil }) vctx.traces = make([]Trace, len(discharges)+1) err := vctx.verify(m, rootKey) return vctx.traces, err } type verificationContext struct { used []bool discharges []*Macaroon rootSig *[hashLen]byte traces []Trace check func(caveat string) error } func (vctx *verificationContext) init(rootKey []byte, root *Macaroon, discharges []*Macaroon, check func(caveat string) error) { *vctx = verificationContext{ discharges: discharges, used: make([]bool, len(discharges)), rootSig: &root.sig, check: check, } } func (vctx *verificationContext) verify(root *Macaroon, rootKey []byte) error { vctx.traceRootKey(0, rootKey) vctx.trace(0, TraceMakeKey, rootKey, nil) derivedKey := makeKey(rootKey) if err := vctx.verify0(root, 0, derivedKey); err != nil { vctx.trace(0, TraceFail, nil, nil) return err } for i, wasUsed := range vctx.used { if !wasUsed { vctx.trace(i+1, TraceFail, nil, nil) return fmt.Errorf("discharge macaroon %q was not used", vctx.discharges[i].Id()) } } return nil } func (vctx *verificationContext) verify0(m *Macaroon, index int, rootKey *[hashLen]byte) error { vctx.trace(index, TraceHash, m.id, nil) caveatSig := keyedHash(rootKey, m.id) for i, cav := range m.caveats { if cav.isThirdParty() { cavKey, err := decrypt(caveatSig, cav.VerificationId) if err != nil { return fmt.Errorf("failed to decrypt caveat %d signature: %v", i, err) } dm, di, err := vctx.findDischarge(cav.Id) if err != nil { return err } vctx.traceRootKey(di+1, cavKey[:]) if err := vctx.verify0(dm, di+1, cavKey); err != nil { vctx.trace(di+1, TraceFail, nil, nil) return err } vctx.trace(index, TraceHash, cav.VerificationId, cav.Id) caveatSig = keyedHash2(caveatSig, cav.VerificationId, cav.Id) } else { vctx.trace(index, TraceHash, cav.Id, nil) caveatSig = keyedHash(caveatSig, cav.Id) if err := vctx.check(string(cav.Id)); err != nil { return err } } } if index > 0 { vctx.trace(index, TraceBind, vctx.rootSig[:], caveatSig[:]) caveatSig = bindForRequest(vctx.rootSig[:], caveatSig) } // TODO perhaps we should actually do this check before doing // all the potentially expensive caveat checks. if !hmac.Equal(caveatSig[:], m.sig[:]) { return fmt.Errorf("signature mismatch after caveat verification") } return nil } func (vctx *verificationContext) findDischarge(id []byte) (dm *Macaroon, index int, err error) { for di, dm := range vctx.discharges { if !bytes.Equal(dm.id, id) { continue } // Don't use a discharge macaroon more than once. // It's important that we do this check here rather than after // verify as it prevents potentially infinite recursion. if vctx.used[di] { return nil, 0, fmt.Errorf("discharge macaroon %q was used more than once", dm.Id()) } vctx.used[di] = true return dm, di, nil } return nil, 0, fmt.Errorf("cannot find discharge macaroon for caveat %x", id) } func (vctx *verificationContext) trace(index int, op TraceOpKind, data1, data2 []byte) { if vctx.traces != nil { vctx.traces[index].Ops = append(vctx.traces[index].Ops, TraceOp{ Kind: op, Data1: data1, Data2: data2, }) } } func (vctx *verificationContext) traceRootKey(index int, rootKey []byte) { if vctx.traces != nil { vctx.traces[index].RootKey = rootKey[:] } } macaroon-2.1.0/macaroon_test.go000066400000000000000000000734131333723420700165120ustar00rootroot00000000000000package macaroon_test import ( "encoding/base64" "encoding/hex" "encoding/json" "fmt" "testing" qt "github.com/frankban/quicktest" "gopkg.in/macaroon.v2" ) func never(string) error { return fmt.Errorf("condition is never true") } func TestNoCaveats(t *testing.T) { c := qt.New(t) rootKey := []byte("secret") m := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion) c.Assert(m.Location(), qt.Equals, "a location") c.Assert(m.Id(), qt.DeepEquals, []byte("some id")) err := m.Verify(rootKey, never, nil) c.Assert(err, qt.IsNil) } func TestFirstPartyCaveat(t *testing.T) { c := qt.New(t) 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, qt.IsNil) c.Assert(tested, qt.DeepEquals, caveats) m.AddFirstPartyCaveat([]byte("not met")) err = m.Verify(rootKey, check, nil) c.Assert(err, qt.Equals, expectErr) c.Assert(tested["not met"], qt.Equals, true) } func TestThirdPartyCaveat(t *testing.T) { c := qt.New(t) 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, qt.IsNil) dm := MustNew(dischargeRootKey, thirdPartyCaveatId, "remote location", macaroon.LatestVersion) dm.Bind(m.Signature()) err = m.Verify(rootKey, never, []*macaroon.Macaroon{dm}) c.Assert(err, qt.IsNil) } func TestThirdPartyCaveatBadRandom(t *testing.T) { c := qt.New(t) 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, qt.ErrorMatches, "cannot generate random bytes: fail") } func TestSetLocation(t *testing.T) { c := qt.New(t) rootKey := []byte("secret") m := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion) c.Assert(m.Location(), qt.Equals, "a location") m.SetLocation("another location") c.Assert(m.Location(), qt.Equals, "another location") } var equalTests = []struct { about string m1, m2 macaroonSpec expect bool }{{ about: "same_everywhere", m1: macaroonSpec{ rootKey: "root-key", id: "root-id", caveats: []caveat{{ condition: "a", }, { condition: "d", }}, }, m2: macaroonSpec{ rootKey: "root-key", id: "root-id", caveats: []caveat{{ condition: "a", }, { condition: "d", }}, }, expect: true, }, { about: "root_key_differs", m1: macaroonSpec{ rootKey: "root-key", id: "root-id", caveats: []caveat{{ condition: "a", }, { condition: "d", }}, }, m2: macaroonSpec{ rootKey: "root-key1", id: "root-id", caveats: []caveat{{ condition: "a", }, { condition: "d", }}, }, expect: false, }, { about: "id_differs", m1: macaroonSpec{ rootKey: "root-key", id: "root-id", }, m2: macaroonSpec{ rootKey: "root-key", id: "root-id1", }, expect: false, }, { about: "extra_caveat_1", m1: macaroonSpec{ rootKey: "root-key", id: "root-id", caveats: []caveat{{ condition: "a", }, { condition: "d", }, { condition: "d", }}, }, m2: macaroonSpec{ rootKey: "root-key", id: "root-id", caveats: []caveat{{ condition: "a", }, { condition: "d", }}, }, expect: false, }, { about: "extra_caveat_2", m1: macaroonSpec{ rootKey: "root-key1", id: "root-id", caveats: []caveat{{ condition: "a", }, { condition: "d", }}, }, m2: macaroonSpec{ rootKey: "root-key", id: "root-id", caveats: []caveat{{ condition: "a", }, { condition: "d", }, { condition: "d", }}, }, expect: false, }, { about: "caveat_condition_differs", m1: macaroonSpec{ rootKey: "root-key", id: "root-id", caveats: []caveat{{ condition: "a", location: "b", rootKey: "c", }}, }, m2: macaroonSpec{ rootKey: "root-key", id: "root-id", caveats: []caveat{{ condition: "a1", location: "b", rootKey: "c", }}, }, expect: false, }} func TestEqual(t *testing.T) { c := qt.New(t) for _, test := range equalTests { c.Run(test.about, func(c *qt.C) { m1 := makeMacaroon(test.m1) m2 := makeMacaroon(test.m2) c.Assert(m1.Equal(m2), qt.Equals, test.expect) }) } } func TestEqualNil(t *testing.T) { c := qt.New(t) var nilm *macaroon.Macaroon var m = MustNew([]byte("k"), []byte("x"), "l", macaroon.LatestVersion) c.Assert(nilm.Equal(nilm), qt.Equals, true) c.Assert(nilm.Equal(m), qt.Equals, false) c.Assert(m.Equal(nilm), qt.Equals, false) } 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 TestVerify(t *testing.T) { c := qt.New(t) 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, qt.ErrorMatches, cond.expectErr) } else { c.Assert(err, qt.IsNil) } // Cloned macaroon should have same verify result. cloneErr := macaroons[0].Clone().Verify(rootKey, check, macaroons[1:]) if err == nil { c.Assert(cloneErr, qt.Equals, nil) } else { c.Assert(cloneErr.Error(), qt.Equals, err.Error()) } } } } func TestTraceVerify(t *testing.T) { c := qt.New(t) rootKey, macaroons := makeMacaroons(multilevelThirdPartyCaveatMacaroons) traces, err := macaroons[0].TraceVerify(rootKey, macaroons[1:]) c.Assert(err, qt.Equals, nil) c.Assert(traces, qt.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]), qt.Equals, b64str(m.Signature()), qt.Commentf("macaroon %d", i)) } } func TestTraceVerifyFailure(t *testing.T) { c := qt.New(t) 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, qt.Equals, nil) var jm macaroon.MacaroonJSONV2 err = json.Unmarshal(data, &jm) c.Assert(err, qt.Equals, nil) jm.Caveats[1].CID = "cond2 corrupted" data, err = json.Marshal(jm) c.Assert(err, qt.Equals, nil) var corruptm *macaroon.Macaroon err = json.Unmarshal(data, &corruptm) c.Assert(err, qt.Equals, nil) traces, err := corruptm.TraceVerify(rootKey, nil) c.Assert(err, qt.ErrorMatches, `signature mismatch after caveat verification`) c.Assert(traces, qt.HasLen, 1) var kinds []macaroon.TraceOpKind for _, op := range traces[0].Ops { kinds = append(kinds, op.Kind) } c.Assert(kinds, qt.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 TestVerifySignature(t *testing.T) { c := qt.New(t) 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, qt.IsNil) c.Assert(conds, qt.DeepEquals, []string{"cond3", "cond4", "cond1", "cond2"}) conds, err = macaroons[0].VerifySignature(nil, macaroons[1:]) c.Assert(err, qt.ErrorMatches, `failed to decrypt caveat 0 signature: decryption failure`) c.Assert(conds, qt.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 TestMarshalJSON(t *testing.T) { c := qt.New(t) for _, vers := range jsonTestVersions { c.Run(fmt.Sprintf("version_%v", vers), func(c *qt.C) { testMarshalJSONWithVersion(c, vers) }) } } func testMarshalJSONWithVersion(c *qt.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, qt.IsNil) var m1 macaroon.Macaroon err = json.Unmarshal(m0JSON, &m1) c.Assert(err, qt.IsNil) c.Assert(m0.Location(), qt.Equals, m1.Location()) c.Assert(string(m0.Id()), qt.Equals, string(m1.Id())) c.Assert( hex.EncodeToString(m0.Signature()), qt.Equals, hex.EncodeToString(m1.Signature())) c.Assert(m1.Version(), qt.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 TestJSONRoundTrip(t *testing.T) { c := qt.New(t) for _, test := range jsonRoundTripTests { c.Run(fmt.Sprintf("v%v_%s", test.expectVers, test.about), func(c *qt.C) { testJSONRoundTripWithVersion(c, test.data, test.expectVers, test.expectExactRoundTrip) }) } } func testJSONRoundTripWithVersion(c *qt.C, jsonData string, vers macaroon.Version, expectExactRoundTrip bool) { var m macaroon.Macaroon err := json.Unmarshal([]byte(jsonData), &m) c.Assert(err, qt.IsNil) assertLibMacaroonsMacaroon(c, &m) c.Assert(m.Version(), qt.Equals, vers) data, err := m.MarshalJSON() c.Assert(err, qt.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, qt.IsNil) var original interface{} err = json.Unmarshal([]byte(jsonData), &original) c.Assert(err, qt.IsNil) c.Assert(got, qt.DeepEquals, original, qt.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, qt.IsNil) assertLibMacaroonsMacaroon(c, &m1) c.Assert(m.Version(), qt.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 *qt.C, m *macaroon.Macaroon) { c.Assert(fmt.Sprintf("%x", m.Signature()), qt.Equals, "d27db2fd1f22760e4c3dae8137e2d8fc1df6c0741c18aed4b97256bf78d1f55c") c.Assert(m.Location(), qt.Equals, "http://mybank/") c.Assert(string(m.Id()), qt.Equals, "we used our other secret key") c.Assert(m.Caveats(), qt.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 TestJSONDecodeError(t *testing.T) { c := qt.New(t) 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, qt.ErrorMatches, test.expectError) } } func TestFirstPartyCaveatWithInvalidUTF8(t *testing.T) { c := qt.New(t) rootKey := []byte("secret") badString := "foo\xff" m0 := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion) err := m0.AddFirstPartyCaveat([]byte(badString)) c.Assert(err, qt.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 *qt.C, m0, m1 *macaroon.Macaroon) { m0json, err := m0.MarshalJSON() c.Assert(err, qt.IsNil) m1json, err := m1.MarshalJSON() var m0val, m1val interface{} err = json.Unmarshal(m0json, &m0val) c.Assert(err, qt.IsNil) err = json.Unmarshal(m1json, &m1val) c.Assert(err, qt.IsNil) c.Assert(m0val, qt.DeepEquals, m1val) } func TestBinaryRoundTrip(t *testing.T) { c := qt.New(t) // 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, qt.IsNil) err = m0.AddFirstPartyCaveat([]byte("second caveat")) c.Assert(err, qt.IsNil) err = m0.AddThirdPartyCaveat([]byte("shared root key"), []byte("3rd party caveat"), "remote.com") c.Assert(err, qt.IsNil) data, err := m0.MarshalBinary() c.Assert(err, qt.IsNil) var m1 macaroon.Macaroon err = m1.UnmarshalBinary(data) c.Assert(err, qt.IsNil) assertEqualMacaroons(c, m0, &m1) } func TestBinaryMarshalingAgainstLibmacaroon(t *testing.T) { c := qt.New(t) // Test that a libmacaroon marshalled macaroon can be correctly unmarshaled data, err := base64.RawURLEncoding.DecodeString( "MDAxY2xvY2F0aW9uIGh0dHA6Ly9teWJhbmsvCjAwMmNpZGVudGlmaWVyIHdlIHVzZWQgb3VyIG90aGVyIHNlY3JldCBrZXkKMDAxZGNpZCBhY2NvdW50ID0gMzczNTkyODU1OQowMDMwY2lkIHRoaXMgd2FzIGhvdyB3ZSByZW1pbmQgYXV0aCBvZiBrZXkvcHJlZAowMDUxdmlkIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANNuxQLgWIbR8CefBV-lJVTRbRbBsUB0u7g_8P3XncL-CY8O1KKwkRMOa120aiCoawowMDFiY2wgaHR0cDovL2F1dGgubXliYW5rLwowMDJmc2lnbmF0dXJlINJ9sv0fInYOTD2ugTfi2Pwd9sB0HBiu1LlyVr940fVcCg") c.Assert(err, qt.IsNil) var m0 macaroon.Macaroon err = m0.UnmarshalBinary(data) c.Assert(err, qt.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, qt.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 TestBinaryFieldBase64Choice(t *testing.T) { c := qt.New(t) 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, qt.Equals, nil) var x struct { Id *string `json:"i"` Id64 *string `json:"i64"` } err = json.Unmarshal(data, &x) c.Assert(err, qt.Equals, nil) if test.expectBase64 { c.Assert(x.Id64, qt.Not(qt.IsNil)) c.Assert(x.Id, qt.IsNil) idDec, err := base64.RawURLEncoding.DecodeString(*x.Id64) c.Assert(err, qt.Equals, nil) c.Assert(string(idDec), qt.Equals, test.id) } else { c.Assert(x.Id64, qt.IsNil) c.Assert(x.Id, qt.Not(qt.IsNil)) c.Assert(*x.Id, qt.Equals, test.id) } } } macaroon-2.1.0/marshal-v1.go000066400000000000000000000126361333723420700156270ustar00rootroot00000000000000package macaroon import ( "encoding/base64" "encoding/hex" "encoding/json" "fmt" "unicode/utf8" ) // macaroonJSONV1 defines the V1 JSON format for macaroons. type macaroonJSONV1 struct { Caveats []caveatJSONV1 `json:"caveats"` Location string `json:"location"` Identifier string `json:"identifier"` Signature string `json:"signature"` // hex-encoded } // caveatJSONV1 defines the V1 JSON format for caveats within a macaroon. type caveatJSONV1 struct { CID string `json:"cid"` VID string `json:"vid,omitempty"` Location string `json:"cl,omitempty"` } // marshalJSONV1 marshals the macaroon to the V1 JSON format. func (m *Macaroon) marshalJSONV1() ([]byte, error) { if !utf8.Valid(m.id) { return nil, fmt.Errorf("macaroon id is not valid UTF-8") } mjson := macaroonJSONV1{ Location: m.location, Identifier: string(m.id), Signature: hex.EncodeToString(m.sig[:]), Caveats: make([]caveatJSONV1, len(m.caveats)), } for i, cav := range m.caveats { if !utf8.Valid(cav.Id) { return nil, fmt.Errorf("caveat id is not valid UTF-8") } mjson.Caveats[i] = caveatJSONV1{ Location: cav.Location, CID: string(cav.Id), VID: base64.RawURLEncoding.EncodeToString(cav.VerificationId), } } data, err := json.Marshal(mjson) if err != nil { return nil, fmt.Errorf("cannot marshal json data: %v", err) } return data, nil } // initJSONV1 initializes m from the JSON-unmarshaled data // held in mjson. func (m *Macaroon) initJSONV1(mjson *macaroonJSONV1) error { m.init([]byte(mjson.Identifier), mjson.Location, V1) sig, err := hex.DecodeString(mjson.Signature) if err != nil { return fmt.Errorf("cannot decode macaroon signature %q: %v", m.sig, err) } if len(sig) != hashLen { return fmt.Errorf("signature has unexpected length %d", len(sig)) } copy(m.sig[:], sig) m.caveats = m.caveats[:0] for _, cav := range mjson.Caveats { vid, err := Base64Decode([]byte(cav.VID)) if err != nil { return fmt.Errorf("cannot decode verification id %q: %v", cav.VID, err) } m.appendCaveat([]byte(cav.CID), vid, cav.Location) } return nil } // The original (v1) binary format of a macaroon is as follows. // Each identifier represents a v1 packet. // // location // identifier // ( // caveatId? // verificationId? // caveatLocation? // )* // signature // parseBinaryV1 parses the given data in V1 format into the macaroon. The macaroon's // internal data structures will retain references to the data. It // returns the data after the end of the macaroon. func (m *Macaroon) parseBinaryV1(data []byte) ([]byte, error) { var err error loc, err := expectPacketV1(data, fieldNameLocation) if err != nil { return nil, err } data = data[loc.totalLen:] id, err := expectPacketV1(data, fieldNameIdentifier) if err != nil { return nil, err } data = data[id.totalLen:] m.init(id.data, string(loc.data), V1) var cav Caveat for { p, err := parsePacketV1(data) if err != nil { return nil, err } data = data[p.totalLen:] switch field := string(p.fieldName); field { case fieldNameSignature: // At the end of the caveats we find the signature. if cav.Id != nil { m.caveats = append(m.caveats, cav) } if len(p.data) != hashLen { return nil, fmt.Errorf("signature has unexpected length %d", len(p.data)) } copy(m.sig[:], p.data) return data, nil case fieldNameCaveatId: if cav.Id != nil { m.caveats = append(m.caveats, cav) cav = Caveat{} } cav.Id = p.data case fieldNameVerificationId: if cav.VerificationId != nil { return nil, fmt.Errorf("repeated field %q in caveat", fieldNameVerificationId) } cav.VerificationId = p.data case fieldNameCaveatLocation: if cav.Location != "" { return nil, fmt.Errorf("repeated field %q in caveat", fieldNameLocation) } cav.Location = string(p.data) default: return nil, fmt.Errorf("unexpected field %q", field) } } } func expectPacketV1(data []byte, kind string) (packetV1, error) { p, err := parsePacketV1(data) if err != nil { return packetV1{}, err } if field := string(p.fieldName); field != kind { return packetV1{}, fmt.Errorf("unexpected field %q; expected %s", field, kind) } return p, nil } // appendBinaryV1 appends the binary encoding of m to data. func (m *Macaroon) appendBinaryV1(data []byte) ([]byte, error) { var ok bool data, ok = appendPacketV1(data, fieldNameLocation, []byte(m.location)) if !ok { return nil, fmt.Errorf("failed to append location to macaroon, packet is too long") } data, ok = appendPacketV1(data, fieldNameIdentifier, m.id) if !ok { return nil, fmt.Errorf("failed to append identifier to macaroon, packet is too long") } for _, cav := range m.caveats { data, ok = appendPacketV1(data, fieldNameCaveatId, cav.Id) if !ok { return nil, fmt.Errorf("failed to append caveat id to macaroon, packet is too long") } if cav.VerificationId == nil { continue } data, ok = appendPacketV1(data, fieldNameVerificationId, cav.VerificationId) if !ok { return nil, fmt.Errorf("failed to append verification id to macaroon, packet is too long") } data, ok = appendPacketV1(data, fieldNameCaveatLocation, []byte(cav.Location)) if !ok { return nil, fmt.Errorf("failed to append verification id to macaroon, packet is too long") } } data, ok = appendPacketV1(data, fieldNameSignature, m.sig[:]) if !ok { return nil, fmt.Errorf("failed to append signature to macaroon, packet is too long") } return data, nil } macaroon-2.1.0/marshal-v2.go000066400000000000000000000156721333723420700156330ustar00rootroot00000000000000package macaroon import ( "encoding/base64" "encoding/json" "fmt" "unicode/utf8" ) // macaroonJSONV2 defines the V2 JSON format for macaroons. type macaroonJSONV2 struct { Caveats []caveatJSONV2 `json:"c,omitempty"` Location string `json:"l,omitempty"` Identifier string `json:"i,omitempty"` Identifier64 string `json:"i64,omitempty"` Signature string `json:"s,omitempty"` Signature64 string `json:"s64,omitempty"` } // caveatJSONV2 defines the V2 JSON format for caveats within a macaroon. type caveatJSONV2 struct { CID string `json:"i,omitempty"` CID64 string `json:"i64,omitempty"` VID string `json:"v,omitempty"` VID64 string `json:"v64,omitempty"` Location string `json:"l,omitempty"` } func (m *Macaroon) marshalJSONV2() ([]byte, error) { mjson := macaroonJSONV2{ Location: m.location, Caveats: make([]caveatJSONV2, len(m.caveats)), } putJSONBinaryField(m.id, &mjson.Identifier, &mjson.Identifier64) putJSONBinaryField(m.sig[:], &mjson.Signature, &mjson.Signature64) for i, cav := range m.caveats { cavjson := caveatJSONV2{ Location: cav.Location, } putJSONBinaryField(cav.Id, &cavjson.CID, &cavjson.CID64) putJSONBinaryField(cav.VerificationId, &cavjson.VID, &cavjson.VID64) mjson.Caveats[i] = cavjson } data, err := json.Marshal(mjson) if err != nil { return nil, fmt.Errorf("cannot marshal json data: %v", err) } return data, nil } // initJSONV2 initializes m from the JSON-unmarshaled data // held in mjson. func (m *Macaroon) initJSONV2(mjson *macaroonJSONV2) error { id, err := jsonBinaryField(mjson.Identifier, mjson.Identifier64) if err != nil { return fmt.Errorf("invalid identifier: %v", err) } m.init(id, mjson.Location, V2) sig, err := jsonBinaryField(mjson.Signature, mjson.Signature64) if err != nil { return fmt.Errorf("invalid signature: %v", err) } if len(sig) != hashLen { return fmt.Errorf("signature has unexpected length %d", len(sig)) } copy(m.sig[:], sig) m.caveats = make([]Caveat, 0, len(mjson.Caveats)) for _, cav := range mjson.Caveats { cid, err := jsonBinaryField(cav.CID, cav.CID64) if err != nil { return fmt.Errorf("invalid cid in caveat: %v", err) } vid, err := jsonBinaryField(cav.VID, cav.VID64) if err != nil { return fmt.Errorf("invalid vid in caveat: %v", err) } m.appendCaveat(cid, vid, cav.Location) } return nil } // putJSONBinaryField puts the value of x into one // of the appropriate fields depending on its value. func putJSONBinaryField(x []byte, s, sb64 *string) { if !utf8.Valid(x) { *sb64 = base64.RawURLEncoding.EncodeToString(x) return } // We could use either string or base64 encoding; // choose the most compact of the two possibilities. b64len := base64.RawURLEncoding.EncodedLen(len(x)) sx := string(x) if jsonEnc, _ := json.Marshal(sx); len(jsonEnc)-2 <= b64len+2 { // The JSON encoding is smaller than the base 64 encoding. // NB marshaling a string can never return an error; // it always includes the two quote characters; // but using base64 also uses two extra characters for the // "64" suffix on the field name. If all is equal, prefer string // encoding because it's more readable. *s = sx return } *sb64 = base64.RawURLEncoding.EncodeToString(x) } // jsonBinaryField returns the value of a JSON field that may // be string, hex or base64-encoded. func jsonBinaryField(s, sb64 string) ([]byte, error) { switch { case s != "": if sb64 != "" { return nil, fmt.Errorf("ambiguous field encoding") } return []byte(s), nil case sb64 != "": return Base64Decode([]byte(sb64)) } return []byte{}, nil } // The v2 binary format of a macaroon is as follows. // All entries other than the version are packets as // parsed by parsePacketV2. // // version [1 byte] // location? // identifier // eos // ( // location? // identifier // verificationId? // eos // )* // eos // signature // // See also https://github.com/rescrv/libmacaroons/blob/master/doc/format.txt // parseBinaryV2 parses the given data in V2 format into the macaroon. The macaroon's // internal data structures will retain references to the data. It // returns the data after the end of the macaroon. func (m *Macaroon) parseBinaryV2(data []byte) ([]byte, error) { // The version has already been checked, so // skip it. data = data[1:] data, section, err := parseSectionV2(data) if err != nil { return nil, err } var loc string if len(section) > 0 && section[0].fieldType == fieldLocation { loc = string(section[0].data) section = section[1:] } if len(section) != 1 || section[0].fieldType != fieldIdentifier { return nil, fmt.Errorf("invalid macaroon header") } id := section[0].data m.init(id, loc, V2) for { rest, section, err := parseSectionV2(data) if err != nil { return nil, err } data = rest if len(section) == 0 { break } var cav Caveat if len(section) > 0 && section[0].fieldType == fieldLocation { cav.Location = string(section[0].data) section = section[1:] } if len(section) == 0 || section[0].fieldType != fieldIdentifier { return nil, fmt.Errorf("no identifier in caveat") } cav.Id = section[0].data section = section[1:] if len(section) == 0 { // First party caveat. if cav.Location != "" { return nil, fmt.Errorf("location not allowed in first party caveat") } m.caveats = append(m.caveats, cav) continue } if len(section) != 1 { return nil, fmt.Errorf("extra fields found in caveat") } if section[0].fieldType != fieldVerificationId { return nil, fmt.Errorf("invalid field found in caveat") } cav.VerificationId = section[0].data m.caveats = append(m.caveats, cav) } data, sig, err := parsePacketV2(data) if err != nil { return nil, err } if sig.fieldType != fieldSignature { return nil, fmt.Errorf("unexpected field found instead of signature") } if len(sig.data) != hashLen { return nil, fmt.Errorf("signature has unexpected length") } copy(m.sig[:], sig.data) return data, nil } // appendBinaryV2 appends the binary-encoded macaroon // in v2 format to data. func (m *Macaroon) appendBinaryV2(data []byte) []byte { // Version byte. data = append(data, 2) if len(m.location) > 0 { data = appendPacketV2(data, packetV2{ fieldType: fieldLocation, data: []byte(m.location), }) } data = appendPacketV2(data, packetV2{ fieldType: fieldIdentifier, data: m.id, }) data = appendEOSV2(data) for _, cav := range m.caveats { if len(cav.Location) > 0 { data = appendPacketV2(data, packetV2{ fieldType: fieldLocation, data: []byte(cav.Location), }) } data = appendPacketV2(data, packetV2{ fieldType: fieldIdentifier, data: cav.Id, }) if len(cav.VerificationId) > 0 { data = appendPacketV2(data, packetV2{ fieldType: fieldVerificationId, data: []byte(cav.VerificationId), }) } data = appendEOSV2(data) } data = appendEOSV2(data) data = appendPacketV2(data, packetV2{ fieldType: fieldSignature, data: m.sig[:], }) return data } macaroon-2.1.0/marshal.go000066400000000000000000000145031333723420700152760ustar00rootroot00000000000000package macaroon import ( "encoding/base64" "encoding/json" "fmt" ) // Version specifies the version of a macaroon. // In version 1, the macaroon id and all caveats // must be UTF-8-compatible strings, and the // size of any part of the macaroon may not exceed // approximately 64K. In version 2, // all field may be arbitrary binary blobs. type Version uint16 const ( // V1 specifies version 1 macaroons. V1 Version = 1 // V2 specifies version 2 macaroons. V2 Version = 2 // LatestVersion holds the latest supported version. LatestVersion = V2 ) // String returns a string representation of the version; // for example V1 formats as "v1". func (v Version) String() string { return fmt.Sprintf("v%d", v) } // Version returns the version of the macaroon. func (m *Macaroon) Version() Version { return m.version } // MarshalJSON implements json.Marshaler by marshaling the // macaroon in JSON format. The serialisation format is determined // by the macaroon's version. func (m *Macaroon) MarshalJSON() ([]byte, error) { switch m.version { case V1: return m.marshalJSONV1() case V2: return m.marshalJSONV2() default: return nil, fmt.Errorf("unknown version %v", m.version) } } // UnmarshalJSON implements json.Unmarshaller by unmarshaling // the given macaroon in JSON format. It accepts both V1 and V2 // forms encoded forms, and also a base64-encoded JSON string // containing the binary-marshaled macaroon. // // After unmarshaling, the macaroon's version will reflect // the version that it was unmarshaled as. func (m *Macaroon) UnmarshalJSON(data []byte) error { if data[0] == '"' { // It's a string, so it must be a base64-encoded binary form. var s string if err := json.Unmarshal(data, &s); err != nil { return err } data, err := Base64Decode([]byte(s)) if err != nil { return err } if err := m.UnmarshalBinary(data); err != nil { return err } return nil } // Not a string; try to unmarshal into both kinds of macaroon object. // This assumes that neither format has any fields in common. // For subsequent versions we may need to change this approach. type MacaroonJSONV1 macaroonJSONV1 type MacaroonJSONV2 macaroonJSONV2 var both struct { *MacaroonJSONV1 *MacaroonJSONV2 } if err := json.Unmarshal(data, &both); err != nil { return err } switch { case both.MacaroonJSONV1 != nil && both.MacaroonJSONV2 != nil: return fmt.Errorf("cannot determine macaroon encoding version") case both.MacaroonJSONV1 != nil: if err := m.initJSONV1((*macaroonJSONV1)(both.MacaroonJSONV1)); err != nil { return err } m.version = V1 case both.MacaroonJSONV2 != nil: if err := m.initJSONV2((*macaroonJSONV2)(both.MacaroonJSONV2)); err != nil { return err } m.version = V2 default: return fmt.Errorf("invalid JSON macaroon encoding") } return nil } // UnmarshalBinary implements encoding.BinaryUnmarshaler. // It accepts both V1 and V2 binary encodings. func (m *Macaroon) UnmarshalBinary(data []byte) error { // Copy the data to avoid retaining references to it // in the internal data structures. data = append([]byte(nil), data...) _, err := m.parseBinary(data) return err } // parseBinary parses the macaroon in binary format // from the given data and returns where the parsed data ends. // // It retains references to data. func (m *Macaroon) parseBinary(data []byte) ([]byte, error) { if len(data) == 0 { return nil, fmt.Errorf("empty macaroon data") } v := data[0] if v == 2 { // Version 2 binary format. data, err := m.parseBinaryV2(data) if err != nil { return nil, fmt.Errorf("unmarshal v2: %v", err) } m.version = V2 return data, nil } if isASCIIHex(v) { // It's a hex digit - version 1 binary format data, err := m.parseBinaryV1(data) if err != nil { return nil, fmt.Errorf("unmarshal v1: %v", err) } m.version = V1 return data, nil } return nil, fmt.Errorf("cannot determine data format of binary-encoded macaroon") } // MarshalBinary implements encoding.BinaryMarshaler by // formatting the macaroon according to the version specified // by MarshalAs. func (m *Macaroon) MarshalBinary() ([]byte, error) { return m.appendBinary(nil) } // appendBinary appends the binary-formatted macaroon to // the given data, formatting it according to the macaroon's // version. func (m *Macaroon) appendBinary(data []byte) ([]byte, error) { switch m.version { case V1: return m.appendBinaryV1(data) case V2: return m.appendBinaryV2(data), nil default: return nil, fmt.Errorf("bad macaroon version %v", m.version) } } // Slice defines a collection of macaroons. By convention, the // first macaroon in the slice is a primary macaroon and the rest // are discharges for its third party caveats. type Slice []*Macaroon // MarshalBinary implements encoding.BinaryMarshaler. func (s Slice) MarshalBinary() ([]byte, error) { var data []byte var err error for _, m := range s { data, err = m.appendBinary(data) if err != nil { return nil, fmt.Errorf("failed to marshal macaroon %q: %v", m.Id(), err) } } return data, nil } // UnmarshalBinary implements encoding.BinaryUnmarshaler. // It accepts all known binary encodings for the data - all the // embedded macaroons need not be encoded in the same format. func (s *Slice) UnmarshalBinary(data []byte) error { // Prevent the internal data structures from holding onto the // slice by copying it first. data = append([]byte(nil), data...) *s = (*s)[:0] for len(data) > 0 { var m Macaroon rest, err := m.parseBinary(data) if err != nil { return fmt.Errorf("cannot unmarshal macaroon: %v", err) } *s = append(*s, &m) data = rest } return nil } const ( padded = 1 << iota stdEncoding ) var codecs = [4]*base64.Encoding{ 0: base64.RawURLEncoding, padded: base64.URLEncoding, stdEncoding: base64.RawStdEncoding, stdEncoding | padded: base64.StdEncoding, } // Base64Decode base64-decodes the given data. // It accepts both standard and URL encodings, both // padded and unpadded. func Base64Decode(data []byte) ([]byte, error) { encoding := 0 if len(data) > 0 && data[len(data)-1] == '=' { encoding |= padded } for _, b := range data { if b == '/' || b == '+' { encoding |= stdEncoding break } } codec := codecs[encoding] buf := make([]byte, codec.DecodedLen(len(data))) n, err := codec.Decode(buf, data) if err == nil { return buf[0:n], nil } return nil, err } macaroon-2.1.0/marshal_test.go000066400000000000000000000156071333723420700163430ustar00rootroot00000000000000package macaroon_test import ( "encoding/json" "testing" qt "github.com/frankban/quicktest" "gopkg.in/macaroon.v2" ) func TestMarshalUnmarshalMacaroonV1(t *testing.T) { c := qt.New(t) testMarshalUnmarshalWithVersion(c, macaroon.V1) } func TestMarshalUnmarshalMacaroonV2(t *testing.T) { c := qt.New(t) testMarshalUnmarshalWithVersion(c, macaroon.V2) } func testMarshalUnmarshalWithVersion(c *qt.C, vers macaroon.Version) { rootKey := []byte("secret") m := MustNew(rootKey, []byte("some id"), "a location", vers) // Adding the third party caveat before the first party caveat // tests a former bug where the caveat wasn't zeroed // before moving to the next caveat. err := m.AddThirdPartyCaveat([]byte("shared root key"), []byte("3rd party caveat"), "remote.com") c.Assert(err, qt.Equals, nil) err = m.AddFirstPartyCaveat([]byte("a caveat")) c.Assert(err, qt.Equals, nil) b, err := m.MarshalBinary() c.Assert(err, qt.Equals, nil) var um macaroon.Macaroon err = um.UnmarshalBinary(b) c.Assert(err, qt.Equals, nil) c.Assert(um.Location(), qt.Equals, m.Location()) c.Assert(string(um.Id()), qt.Equals, string(m.Id())) c.Assert(um.Signature(), qt.DeepEquals, m.Signature()) c.Assert(um.Caveats(), qt.DeepEquals, m.Caveats()) c.Assert(um.Version(), qt.Equals, vers) um.SetVersion(m.Version()) c.Assert(m, qt.DeepEquals, &um) } func TestMarshalBinaryRoundTrip(t *testing.T) { c := qt.New(t) // This data holds the V2 binary encoding of data := []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", ) var m macaroon.Macaroon err := m.UnmarshalBinary(data) c.Assert(err, qt.Equals, nil) assertLibMacaroonsMacaroon(c, &m) c.Assert(m.Version(), qt.Equals, macaroon.V2) data1, err := m.MarshalBinary() c.Assert(err, qt.Equals, nil) c.Assert(data1, qt.DeepEquals, data) } func TestBinaryJSONRoundTripV1(t *testing.T) { c := qt.New(t) testBinaryJSONRoundTrip(c, macaroon.V1) } func TestBinaryJSONRoundTripV2(t *testing.T) { c := qt.New(t) testBinaryJSONRoundTrip(c, macaroon.V2) } func testBinaryJSONRoundTrip(c *qt.C, vers macaroon.Version) { m1 := MustNew([]byte("rootkey"), []byte("some id"), "a location", vers) err := m1.AddFirstPartyCaveat([]byte("a caveat")) c.Assert(err, qt.Equals, nil) err = m1.AddThirdPartyCaveat([]byte("shared root key"), []byte("3rd party caveat"), "remote.com") c.Assert(err, qt.Equals, nil) binData1, err := m1.MarshalBinary() c.Assert(err, qt.Equals, nil) jsonData1, err := json.Marshal(m1) c.Assert(err, qt.Equals, nil) var m2 *macaroon.Macaroon err = json.Unmarshal(jsonData1, &m2) c.Assert(err, qt.Equals, nil) binData2, err := m2.MarshalBinary() c.Assert(err, qt.Equals, nil) c.Assert(binData1, qt.DeepEquals, binData2) } func TestMarshalUnmarshalSliceV1(t *testing.T) { c := qt.New(t) testMarshalUnmarshalSliceWithVersion(c, macaroon.V1) } func TestMarshalUnmarshalSliceV2(t *testing.T) { c := qt.New(t) testMarshalUnmarshalSliceWithVersion(c, macaroon.V2) } func testMarshalUnmarshalSliceWithVersion(c *qt.C, vers macaroon.Version) { rootKey := []byte("secret") m1 := MustNew(rootKey, []byte("some id"), "a location", vers) m2 := MustNew(rootKey, []byte("some other id"), "another location", vers) err := m1.AddFirstPartyCaveat([]byte("a caveat")) c.Assert(err, qt.Equals, nil) err = m2.AddFirstPartyCaveat([]byte("another caveat")) c.Assert(err, qt.Equals, nil) macaroons := macaroon.Slice{m1, m2} b, err := macaroons.MarshalBinary() c.Assert(err, qt.Equals, nil) var unmarshaledMacs macaroon.Slice err = unmarshaledMacs.UnmarshalBinary(b) c.Assert(err, qt.Equals, nil) c.Assert(unmarshaledMacs, qt.HasLen, len(macaroons)) for i, m := range macaroons { um := unmarshaledMacs[i] c.Assert(um.Location(), qt.Equals, m.Location()) c.Assert(string(um.Id()), qt.Equals, string(m.Id())) c.Assert(um.Signature(), qt.DeepEquals, m.Signature()) c.Assert(um.Caveats(), qt.DeepEquals, m.Caveats()) c.Assert(um.Version(), qt.Equals, vers) um.SetVersion(m.Version()) } c.Assert(macaroons, qt.DeepEquals, unmarshaledMacs) // Check that appending a caveat to the first does not // affect the second. for i := 0; i < 10; i++ { err = unmarshaledMacs[0].AddFirstPartyCaveat([]byte("caveat")) c.Assert(err, qt.Equals, nil) } unmarshaledMacs[1].SetVersion(macaroons[1].Version()) c.Assert(unmarshaledMacs[1], qt.DeepEquals, macaroons[1]) c.Assert(err, qt.Equals, nil) } func TestSliceRoundTripV1(t *testing.T) { c := qt.New(t) testSliceRoundTripWithVersion(c, macaroon.V1) } func TestSliceRoundTripV2(t *testing.T) { c := qt.New(t) testSliceRoundTripWithVersion(c, macaroon.V2) } func testSliceRoundTripWithVersion(c *qt.C, vers macaroon.Version) { rootKey := []byte("secret") m1 := MustNew(rootKey, []byte("some id"), "a location", vers) m2 := MustNew(rootKey, []byte("some other id"), "another location", vers) err := m1.AddFirstPartyCaveat([]byte("a caveat")) c.Assert(err, qt.Equals, nil) err = m2.AddFirstPartyCaveat([]byte("another caveat")) c.Assert(err, qt.Equals, nil) macaroons := macaroon.Slice{m1, m2} b, err := macaroons.MarshalBinary() c.Assert(err, qt.Equals, nil) var unmarshaledMacs macaroon.Slice err = unmarshaledMacs.UnmarshalBinary(b) c.Assert(err, qt.Equals, nil) marshaledMacs, err := unmarshaledMacs.MarshalBinary() c.Assert(err, qt.Equals, nil) c.Assert(b, qt.DeepEquals, marshaledMacs) } var base64DecodeTests = []struct { about string input string expect string expectError string }{{ about: "empty string", input: "", expect: "", }, { about: "standard encoding, padded", input: "Z29+IQ==", expect: "go~!", }, { about: "URL encoding, padded", input: "Z29-IQ==", expect: "go~!", }, { about: "standard encoding, not padded", input: "Z29+IQ", expect: "go~!", }, { about: "URL encoding, not padded", input: "Z29-IQ", expect: "go~!", }, { about: "standard encoding, too much padding", input: "Z29+IQ===", expectError: `illegal base64 data at input byte 8`, }} func TestBase64Decode(t *testing.T) { c := qt.New(t) for i, test := range base64DecodeTests { c.Logf("test %d: %s", i, test.about) out, err := macaroon.Base64Decode([]byte(test.input)) if test.expectError != "" { c.Assert(err, qt.ErrorMatches, test.expectError) } else { c.Assert(err, qt.Equals, nil) c.Assert(string(out), qt.Equals, test.expect) } } } macaroon-2.1.0/packet-v1.go000066400000000000000000000062651333723420700154500ustar00rootroot00000000000000package macaroon import ( "bytes" "fmt" ) // field names, as defined in libmacaroons const ( fieldNameLocation = "location" fieldNameIdentifier = "identifier" fieldNameSignature = "signature" fieldNameCaveatId = "cid" fieldNameVerificationId = "vid" fieldNameCaveatLocation = "cl" ) // maxPacketV1Len is the maximum allowed length of a packet in the v1 macaroon // serialization format. const maxPacketV1Len = 0xffff // The original macaroon binary encoding is made from a sequence // of "packets", each of which has a field name and some data. // The encoding is: // // - four ascii hex digits holding the entire packet size (including // the digits themselves). // // - the field name, followed by an ascii space. // // - the raw data // // - a newline (\n) character // // The packet struct below holds a reference into Macaroon.data. type packetV1 struct { // ftype holds the field name of the packet. fieldName []byte // data holds the packet's data. data []byte // len holds the total length in bytes // of the packet, including any header. totalLen int } // parsePacket parses the packet at the start of the // given data. func parsePacketV1(data []byte) (packetV1, error) { if len(data) < 6 { return packetV1{}, fmt.Errorf("packet too short") } plen, ok := parseSizeV1(data) if !ok { return packetV1{}, fmt.Errorf("cannot parse size") } if plen > len(data) { return packetV1{}, fmt.Errorf("packet size too big") } if plen < 4 { return packetV1{}, fmt.Errorf("packet size too small") } data = data[4:plen] i := bytes.IndexByte(data, ' ') if i <= 0 { return packetV1{}, fmt.Errorf("cannot parse field name") } fieldName := data[0:i] if data[len(data)-1] != '\n' { return packetV1{}, fmt.Errorf("no terminating newline found") } return packetV1{ fieldName: fieldName, data: data[i+1 : len(data)-1], totalLen: plen, }, nil } // appendPacketV1 appends a packet with the given field name // and data to the given buffer. If the field and data were // too long to be encoded, it returns nil, false; otherwise // it returns the appended buffer. func appendPacketV1(buf []byte, field string, data []byte) ([]byte, bool) { plen := packetV1Size(field, data) if plen > maxPacketV1Len { return nil, false } buf = appendSizeV1(buf, plen) buf = append(buf, field...) buf = append(buf, ' ') buf = append(buf, data...) buf = append(buf, '\n') return buf, true } func packetV1Size(field string, data []byte) int { return 4 + len(field) + 1 + len(data) + 1 } var hexDigits = []byte("0123456789abcdef") func appendSizeV1(data []byte, size int) []byte { return append(data, hexDigits[size>>12], hexDigits[(size>>8)&0xf], hexDigits[(size>>4)&0xf], hexDigits[size&0xf], ) } func parseSizeV1(data []byte) (int, bool) { d0, ok0 := asciiHex(data[0]) d1, ok1 := asciiHex(data[1]) d2, ok2 := asciiHex(data[2]) d3, ok3 := asciiHex(data[3]) return d0<<12 + d1<<8 + d2<<4 + d3, ok0 && ok1 && ok2 && ok3 } func asciiHex(b byte) (int, bool) { switch { case b >= '0' && b <= '9': return int(b) - '0', true case b >= 'a' && b <= 'f': return int(b) - 'a' + 0xa, true } return 0, false } func isASCIIHex(b byte) bool { _, ok := asciiHex(b) return ok } macaroon-2.1.0/packet-v1_test.go000066400000000000000000000045401333723420700165010ustar00rootroot00000000000000package macaroon import ( "strconv" "strings" "testing" "unicode" qt "github.com/frankban/quicktest" ) func TestAppendPacket(t *testing.T) { c := qt.New(t) data, ok := appendPacketV1(nil, "field", []byte("some data")) c.Assert(ok, qt.Equals, true) c.Assert(string(data), qt.Equals, "0014field some data\n") data, ok = appendPacketV1(data, "otherfield", []byte("more and more data")) c.Assert(ok, qt.Equals, true) c.Assert(string(data), qt.Equals, "0014field some data\n0022otherfield more and more data\n") } func TestAppendPacketTooBig(t *testing.T) { c := qt.New(t) data, ok := appendPacketV1(nil, "field", make([]byte, 65532)) c.Assert(ok, qt.Equals, false) c.Assert(data, qt.IsNil) } var parsePacketV1Tests = []struct { data string expect packetV1 expectErr string }{{ expectErr: "packet too short", }, { data: "0014field some data\n", expect: packetV1{ fieldName: []byte("field"), data: []byte("some data"), totalLen: 20, }, }, { data: "0015field some data\n", expectErr: "packet size too big", }, { data: "0003a\n", expectErr: "packet size too small", }, { data: "0014fieldwithoutanyspaceordata\n", expectErr: "cannot parse field name", }, { data: "fedcsomefield " + strings.Repeat("x", 0xfedc-len("0000somefield \n")) + "\n", expect: packetV1{ fieldName: []byte("somefield"), data: []byte(strings.Repeat("x", 0xfedc-len("0000somefield \n"))), totalLen: 0xfedc, }, }, { data: "zzzzbadpacketsizenomacaroon", expectErr: "cannot parse size", }} func TestParsePacketV1(t *testing.T) { c := qt.New(t) for i, test := range parsePacketV1Tests { c.Logf("test %d: %q", i, truncate(test.data)) p, err := parsePacketV1([]byte(test.data)) if test.expectErr != "" { c.Assert(err, qt.ErrorMatches, test.expectErr) c.Assert(p, packetEquals, packetV1{}) continue } c.Assert(err, qt.Equals, nil) c.Assert(p, packetEquals, test.expect) } } func truncate(d string) string { if len(d) > 50 { return d[0:50] + "..." } return d } func TestAsciiHex(t *testing.T) { c := qt.New(t) for b := 0; b < 256; b++ { n, err := strconv.ParseInt(string(b), 16, 8) value, ok := asciiHex(byte(b)) if err != nil || unicode.IsUpper(rune(b)) { c.Assert(ok, qt.Equals, false) c.Assert(value, qt.Equals, 0) } else { c.Assert(ok, qt.Equals, true) c.Assert(value, qt.Equals, int(n)) } } } macaroon-2.1.0/packet-v2.go000066400000000000000000000054621333723420700154470ustar00rootroot00000000000000package macaroon import ( "encoding/binary" "fmt" ) type fieldType int // Field constants as used in the binary encoding. const ( fieldEOS fieldType = 0 fieldLocation fieldType = 1 fieldIdentifier fieldType = 2 fieldVerificationId fieldType = 4 fieldSignature fieldType = 6 ) type packetV2 struct { // fieldType holds the type of the field. fieldType fieldType // data holds the packet's data. data []byte } // parseSectionV2 parses a sequence of packets // in data. The sequence is terminated by a packet // with a field type of fieldEOS. func parseSectionV2(data []byte) ([]byte, []packetV2, error) { prevFieldType := fieldType(-1) var packets []packetV2 for { if len(data) == 0 { return nil, nil, fmt.Errorf("section extends past end of buffer") } rest, p, err := parsePacketV2(data) if err != nil { return nil, nil, err } if p.fieldType == fieldEOS { return rest, packets, nil } if p.fieldType <= prevFieldType { return nil, nil, fmt.Errorf("fields out of order") } packets = append(packets, p) prevFieldType = p.fieldType data = rest } } // parsePacketV2 parses a V2 data package at the start // of the given data. // The format of a packet is as follows: // // fieldType(varint) payloadLen(varint) data[payloadLen bytes] // // apart from fieldEOS which has no payloadLen or data (it's // a single zero byte). func parsePacketV2(data []byte) ([]byte, packetV2, error) { data, ft, err := parseVarint(data) if err != nil { return nil, packetV2{}, err } p := packetV2{ fieldType: fieldType(ft), } if p.fieldType == fieldEOS { return data, p, nil } data, payloadLen, err := parseVarint(data) if err != nil { return nil, packetV2{}, err } if payloadLen > len(data) { return nil, packetV2{}, fmt.Errorf("field data extends past end of buffer") } p.data = data[0:payloadLen] return data[payloadLen:], p, nil } // parseVarint parses the variable-length integer // at the start of the given data and returns rest // of the buffer and the number. func parseVarint(data []byte) ([]byte, int, error) { val, n := binary.Uvarint(data) if n > 0 { if val > 0x7fffffff { return nil, 0, fmt.Errorf("varint value out of range") } return data[n:], int(val), nil } if n == 0 { return nil, 0, fmt.Errorf("varint value extends past end of buffer") } return nil, 0, fmt.Errorf("varint value out of range") } func appendPacketV2(data []byte, p packetV2) []byte { data = appendVarint(data, int(p.fieldType)) if p.fieldType != fieldEOS { data = appendVarint(data, len(p.data)) data = append(data, p.data...) } return data } func appendEOSV2(data []byte) []byte { return append(data, 0) } func appendVarint(data []byte, x int) []byte { var buf [binary.MaxVarintLen32]byte n := binary.PutUvarint(buf[:], uint64(x)) return append(data, buf[:n]...) } macaroon-2.1.0/packet-v2_test.go000066400000000000000000000060321333723420700165000ustar00rootroot00000000000000package macaroon import ( "testing" qt "github.com/frankban/quicktest" "github.com/google/go-cmp/cmp" ) var packetEquals = qt.CmpEquals(cmp.AllowUnexported(packetV1{}, packetV2{})) var parsePacketV2Tests = []struct { about string data string expectPacket packetV2 expectData string expectError string }{{ about: "EOS packet", data: "\x00", expectPacket: packetV2{ fieldType: fieldEOS, }, }, { about: "simple field", data: "\x02\x03xyz", expectPacket: packetV2{ fieldType: 2, data: []byte("xyz"), }, }, { about: "empty buffer", data: "", expectError: "varint value extends past end of buffer", }, { about: "varint out of range", data: "\xff\xff\xff\xff\xff\xff\x7f", expectError: "varint value out of range", }, { about: "varint way out of range", data: "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x7f", expectError: "varint value out of range", }, { about: "unterminated varint", data: "\x80", expectError: "varint value extends past end of buffer", }, { about: "field data too long", data: "\x01\x02a", expectError: "field data extends past end of buffer", }, { about: "bad data length varint", data: "\x01\xff", expectError: "varint value extends past end of buffer", }} func TestParsePacketV2(t *testing.T) { c := qt.New(t) for i, test := range parsePacketV2Tests { c.Logf("test %d: %v", i, test.about) data, p, err := parsePacketV2([]byte(test.data)) if test.expectError != "" { c.Assert(err, qt.ErrorMatches, test.expectError) c.Assert(data, qt.IsNil) c.Assert(p, packetEquals, packetV2{}) } else { c.Assert(err, qt.Equals, nil) c.Assert(p, packetEquals, test.expectPacket) } } } var parseSectionV2Tests = []struct { about string data string expectData string expectPackets []packetV2 expectError string }{{ about: "no packets", data: "\x00", }, { about: "one packet", data: "\x02\x03xyz\x00", expectPackets: []packetV2{{ fieldType: 2, data: []byte("xyz"), }}, }, { about: "two packets", data: "\x02\x03xyz\x07\x05abcde\x00", expectPackets: []packetV2{{ fieldType: 2, data: []byte("xyz"), }, { fieldType: 7, data: []byte("abcde"), }}, }, { about: "unterminated section", data: "\x02\x03xyz\x07\x05abcde", expectError: "section extends past end of buffer", }, { about: "out of order fields", data: "\x07\x05abcde\x02\x03xyz\x00", expectError: "fields out of order", }, { about: "bad packet", data: "\x07\x05abcde\xff", expectError: "varint value extends past end of buffer", }} func TestParseSectionV2(t *testing.T) { c := qt.New(t) for i, test := range parseSectionV2Tests { c.Logf("test %d: %v", i, test.about) data, ps, err := parseSectionV2([]byte(test.data)) if test.expectError != "" { c.Assert(err, qt.ErrorMatches, test.expectError) c.Assert(data, qt.IsNil) c.Assert(ps, qt.IsNil) } else { c.Assert(err, qt.Equals, nil) c.Assert(ps, packetEquals, test.expectPackets) } } } macaroon-2.1.0/trace.go000066400000000000000000000051631333723420700147470ustar00rootroot00000000000000package macaroon import ( "fmt" ) // Trace holds all toperations involved in verifying a macaroon, // and the root key used as the initial verification key. // This can be useful for debugging macaroon implementations. type Trace struct { RootKey []byte Ops []TraceOp } // Results returns the output from all operations in the Trace. // The result from ts.Ops[i] will be in the i'th element of the // returned slice. // When a trace has resulted in a failure, the // last element will be nil. func (t Trace) Results() [][]byte { r := make([][]byte, len(t.Ops)) input := t.RootKey for i, op := range t.Ops { input = op.Result(input) r[i] = input } return r } // TraceOp holds one possible operation when verifying a macaroon. type TraceOp struct { Kind TraceOpKind `json:"kind"` Data1 []byte `json:"data1,omitempty"` Data2 []byte `json:"data2,omitempty"` } // Result returns the result of computing the given // operation with the given input data. // If op is TraceFail, it returns nil. func (op TraceOp) Result(input []byte) []byte { switch op.Kind { case TraceMakeKey: return makeKey(input)[:] case TraceHash: if len(op.Data2) == 0 { return keyedHash(bytesToKey(input), op.Data1)[:] } return keyedHash2(bytesToKey(input), op.Data1, op.Data2)[:] case TraceBind: return bindForRequest(op.Data1, bytesToKey(input))[:] case TraceFail: return nil default: panic(fmt.Errorf("unknown trace operation kind %d", op.Kind)) } } func bytesToKey(data []byte) *[keyLen]byte { var key [keyLen]byte if len(data) != keyLen { panic(fmt.Errorf("unexpected input key length; got %d want %d", len(data), keyLen)) } copy(key[:], data) return &key } // TraceOpKind represents the kind of a macaroon verification operation. type TraceOpKind int const ( TraceInvalid = TraceOpKind(iota) // TraceMakeKey represents the operation of calculating a // fixed length root key from the variable length input key. TraceMakeKey // TraceHash represents a keyed hash operation with one // or two values. If there is only one value, it will be in Data1. TraceHash // TraceBind represents the operation of binding a discharge macaroon // to its primary macaroon. Data1 holds the signature of the primary // macaroon. TraceBind // TraceFail represents a verification failure. If present, this will always // be the last operation in a trace. TraceFail ) var traceOps = []string{ TraceInvalid: "invalid", TraceMakeKey: "makekey", TraceHash: "hash", TraceBind: "bind", TraceFail: "fail", } // String returns a string representation of the operation. func (k TraceOpKind) String() string { return traceOps[k] }