Add token generation using go macaroon (#437)
* Add Go macaroon library Signed-off-by: Anant Prakash <anantprakashjsr@gmail.com> * Add macaroon generation and serialization, for login token. Signed-off-by: Anant Prakash <anantprakashjsr@gmail.com> * Remove copyright, trim empty lines * Make Serialize functions private * Fix typos
This commit is contained in:
parent
89e0a9e812
commit
afeab7b2d4
37 changed files with 6295 additions and 0 deletions
26
vendor/src/github.com/go-macaroon/macaroon/LICENSE
vendored
Normal file
26
vendor/src/github.com/go-macaroon/macaroon/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
Copyright © 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.
|
||||
159
vendor/src/github.com/go-macaroon/macaroon/README.md
vendored
Normal file
159
vendor/src/github.com/go-macaroon/macaroon/README.md
vendored
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
# macaroon
|
||||
--
|
||||
import "gopkg.in/macaroon.v1"
|
||||
|
||||
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.v0
|
||||
for higher level services and operations that use macaroons.
|
||||
|
||||
## Usage
|
||||
|
||||
#### type Caveat
|
||||
|
||||
```go
|
||||
type Caveat struct {
|
||||
Id string
|
||||
Location string
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### 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 []byte, id, loc string) (*Macaroon, error)
|
||||
```
|
||||
New returns a new macaroon with the given root key, identifier and location.
|
||||
|
||||
#### func (*Macaroon) AddFirstPartyCaveat
|
||||
|
||||
```go
|
||||
func (m *Macaroon) AddFirstPartyCaveat(caveatId string) error
|
||||
```
|
||||
AddFirstPartyCaveat adds a caveat that will be verified by the target service.
|
||||
|
||||
#### func (*Macaroon) AddThirdPartyCaveat
|
||||
|
||||
```go
|
||||
func (m *Macaroon) AddThirdPartyCaveat(rootKey []byte, caveatId string, 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() string
|
||||
```
|
||||
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.
|
||||
|
||||
#### func (*Macaroon) MarshalJSON
|
||||
|
||||
```go
|
||||
func (m *Macaroon) MarshalJSON() ([]byte, error)
|
||||
```
|
||||
MarshalJSON implements json.Marshaler.
|
||||
|
||||
#### func (*Macaroon) Signature
|
||||
|
||||
```go
|
||||
func (m *Macaroon) Signature() []byte
|
||||
```
|
||||
Signature returns the macaroon's signature.
|
||||
|
||||
#### func (*Macaroon) UnmarshalBinary
|
||||
|
||||
```go
|
||||
func (m *Macaroon) UnmarshalBinary(data []byte) error
|
||||
```
|
||||
UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||
|
||||
#### func (*Macaroon) UnmarshalJSON
|
||||
|
||||
```go
|
||||
func (m *Macaroon) UnmarshalJSON(jsonData []byte) error
|
||||
```
|
||||
UnmarshalJSON implements json.Unmarshaler.
|
||||
|
||||
#### 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 true if the verification succeeds; if returns (false, nil) if the
|
||||
verification fails, and (false, err) if the verification cannot be asserted (but
|
||||
may not be false).
|
||||
|
||||
TODO(rog) is there a possible DOS attack that can cause this function to
|
||||
infinitely recurse?
|
||||
|
||||
#### type Verifier
|
||||
|
||||
```go
|
||||
type Verifier interface {
|
||||
Verify(m *Macaroon, rootKey []byte) (bool, error)
|
||||
}
|
||||
```
|
||||
4
vendor/src/github.com/go-macaroon/macaroon/TODO
vendored
Normal file
4
vendor/src/github.com/go-macaroon/macaroon/TODO
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
macaroon:
|
||||
|
||||
- verify that all signature calculations to correspond exactly
|
||||
with libmacaroons.
|
||||
109
vendor/src/github.com/go-macaroon/macaroon/bench_test.go
vendored
Normal file
109
vendor/src/github.com/go-macaroon/macaroon/bench_test.go
vendored
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
package 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
91
vendor/src/github.com/go-macaroon/macaroon/crypto.go
vendored
Normal file
91
vendor/src/github.com/go-macaroon/macaroon/crypto.go
vendored
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
package 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
|
||||
}
|
||||
66
vendor/src/github.com/go-macaroon/macaroon/crypto_test.go
vendored
Normal file
66
vendor/src/github.com/go-macaroon/macaroon/crypto_test.go
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
package macaroon
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/nacl/secretbox"
|
||||
gc "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type cryptoSuite struct{}
|
||||
|
||||
var _ = gc.Suite(&cryptoSuite{})
|
||||
|
||||
var testCryptKey = &[hashLen]byte{'k', 'e', 'y'}
|
||||
var testCryptText = &[hashLen]byte{'t', 'e', 'x', 't'}
|
||||
|
||||
func (*cryptoSuite) TestEncDec(c *gc.C) {
|
||||
b, err := encrypt(testCryptKey, testCryptText, rand.Reader)
|
||||
c.Assert(err, gc.IsNil)
|
||||
t, err := decrypt(testCryptKey, b)
|
||||
c.Assert(err, gc.IsNil)
|
||||
c.Assert(string(t[:]), gc.Equals, string(testCryptText[:]))
|
||||
}
|
||||
|
||||
func (*cryptoSuite) TestUniqueNonces(c *gc.C) {
|
||||
nonces := make(map[string]struct{})
|
||||
for i := 0; i < 100; i++ {
|
||||
nonce, err := newNonce(rand.Reader)
|
||||
c.Assert(err, gc.IsNil)
|
||||
nonces[string(nonce[:])] = struct{}{}
|
||||
}
|
||||
c.Assert(nonces, gc.HasLen, 100, gc.Commentf("duplicate nonce detected"))
|
||||
}
|
||||
|
||||
type ErrorReader struct{}
|
||||
|
||||
func (*ErrorReader) Read([]byte) (int, error) {
|
||||
return 0, fmt.Errorf("fail")
|
||||
}
|
||||
|
||||
func (*cryptoSuite) TestBadRandom(c *gc.C) {
|
||||
_, err := newNonce(&ErrorReader{})
|
||||
c.Assert(err, gc.ErrorMatches, "^cannot generate random bytes:.*")
|
||||
|
||||
_, err = encrypt(testCryptKey, testCryptText, &ErrorReader{})
|
||||
c.Assert(err, gc.ErrorMatches, "^cannot generate random bytes:.*")
|
||||
}
|
||||
|
||||
func (*cryptoSuite) TestBadCiphertext(c *gc.C) {
|
||||
buf := randomBytes(nonceLen + secretbox.Overhead)
|
||||
for i := range buf {
|
||||
_, err := decrypt(testCryptKey, buf[0:i])
|
||||
c.Assert(err, gc.ErrorMatches, "message too short")
|
||||
}
|
||||
_, err := decrypt(testCryptKey, buf)
|
||||
c.Assert(err, gc.ErrorMatches, "decryption failure")
|
||||
}
|
||||
|
||||
func randomBytes(n int) []byte {
|
||||
buf := make([]byte, n)
|
||||
if _, err := rand.Reader.Read(buf); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return buf
|
||||
}
|
||||
14
vendor/src/github.com/go-macaroon/macaroon/export_test.go
vendored
Normal file
14
vendor/src/github.com/go-macaroon/macaroon/export_test.go
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package 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
|
||||
}
|
||||
366
vendor/src/github.com/go-macaroon/macaroon/macaroon.go
vendored
Normal file
366
vendor/src/github.com/go-macaroon/macaroon/macaroon.go
vendored
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
// 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.v1
|
||||
// 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
|
||||
}
|
||||
|
||||
// Caveat holds a first person 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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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[:]
|
||||
}
|
||||
}
|
||||
914
vendor/src/github.com/go-macaroon/macaroon/macaroon_test.go
vendored
Normal file
914
vendor/src/github.com/go-macaroon/macaroon/macaroon_test.go
vendored
Normal file
|
|
@ -0,0 +1,914 @@
|
|||
package macaroon_test
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
jc "github.com/juju/testing/checkers"
|
||||
gc "gopkg.in/check.v1"
|
||||
|
||||
"gopkg.in/macaroon.v2"
|
||||
)
|
||||
|
||||
func TestPackage(t *testing.T) {
|
||||
gc.TestingT(t)
|
||||
}
|
||||
|
||||
type macaroonSuite struct{}
|
||||
|
||||
var _ = gc.Suite(&macaroonSuite{})
|
||||
|
||||
func never(string) error {
|
||||
return fmt.Errorf("condition is never true")
|
||||
}
|
||||
|
||||
func (*macaroonSuite) TestNoCaveats(c *gc.C) {
|
||||
rootKey := []byte("secret")
|
||||
m := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion)
|
||||
c.Assert(m.Location(), gc.Equals, "a location")
|
||||
c.Assert(m.Id(), gc.DeepEquals, []byte("some id"))
|
||||
|
||||
err := m.Verify(rootKey, never, nil)
|
||||
c.Assert(err, gc.IsNil)
|
||||
}
|
||||
|
||||
func (*macaroonSuite) TestFirstPartyCaveat(c *gc.C) {
|
||||
rootKey := []byte("secret")
|
||||
m := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion)
|
||||
|
||||
caveats := map[string]bool{
|
||||
"a caveat": true,
|
||||
"another caveat": true,
|
||||
}
|
||||
tested := make(map[string]bool)
|
||||
|
||||
for cav := range caveats {
|
||||
m.AddFirstPartyCaveat([]byte(cav))
|
||||
}
|
||||
expectErr := fmt.Errorf("condition not met")
|
||||
check := func(cav string) error {
|
||||
tested[cav] = true
|
||||
if caveats[cav] {
|
||||
return nil
|
||||
}
|
||||
return expectErr
|
||||
}
|
||||
err := m.Verify(rootKey, check, nil)
|
||||
c.Assert(err, gc.IsNil)
|
||||
|
||||
c.Assert(tested, gc.DeepEquals, caveats)
|
||||
|
||||
m.AddFirstPartyCaveat([]byte("not met"))
|
||||
err = m.Verify(rootKey, check, nil)
|
||||
c.Assert(err, gc.Equals, expectErr)
|
||||
|
||||
c.Assert(tested["not met"], gc.Equals, true)
|
||||
}
|
||||
|
||||
func (*macaroonSuite) TestThirdPartyCaveat(c *gc.C) {
|
||||
rootKey := []byte("secret")
|
||||
m := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion)
|
||||
|
||||
dischargeRootKey := []byte("shared root key")
|
||||
thirdPartyCaveatId := []byte("3rd party caveat")
|
||||
err := m.AddThirdPartyCaveat(dischargeRootKey, thirdPartyCaveatId, "remote.com")
|
||||
c.Assert(err, gc.IsNil)
|
||||
|
||||
dm := MustNew(dischargeRootKey, thirdPartyCaveatId, "remote location", macaroon.LatestVersion)
|
||||
dm.Bind(m.Signature())
|
||||
err = m.Verify(rootKey, never, []*macaroon.Macaroon{dm})
|
||||
c.Assert(err, gc.IsNil)
|
||||
}
|
||||
|
||||
func (*macaroonSuite) TestThirdPartyCaveatBadRandom(c *gc.C) {
|
||||
rootKey := []byte("secret")
|
||||
m := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion)
|
||||
dischargeRootKey := []byte("shared root key")
|
||||
thirdPartyCaveatId := []byte("3rd party caveat")
|
||||
|
||||
err := macaroon.AddThirdPartyCaveatWithRand(m, dischargeRootKey, thirdPartyCaveatId, "remote.com", &macaroon.ErrorReader{})
|
||||
c.Assert(err, gc.ErrorMatches, "cannot generate random bytes: fail")
|
||||
}
|
||||
|
||||
func (*macaroonSuite) TestSetLocation(c *gc.C) {
|
||||
rootKey := []byte("secret")
|
||||
m := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion)
|
||||
c.Assert(m.Location(), gc.Equals, "a location")
|
||||
m.SetLocation("another location")
|
||||
c.Assert(m.Location(), gc.Equals, "another location")
|
||||
}
|
||||
|
||||
type conditionTest struct {
|
||||
conditions map[string]bool
|
||||
expectErr string
|
||||
}
|
||||
|
||||
var verifyTests = []struct {
|
||||
about string
|
||||
macaroons []macaroonSpec
|
||||
conditions []conditionTest
|
||||
}{{
|
||||
about: "single third party caveat without discharge",
|
||||
macaroons: []macaroonSpec{{
|
||||
rootKey: "root-key",
|
||||
id: "root-id",
|
||||
caveats: []caveat{{
|
||||
condition: "wonderful",
|
||||
}, {
|
||||
condition: "bob-is-great",
|
||||
location: "bob",
|
||||
rootKey: "bob-caveat-root-key",
|
||||
}},
|
||||
}},
|
||||
conditions: []conditionTest{{
|
||||
conditions: map[string]bool{
|
||||
"wonderful": true,
|
||||
},
|
||||
expectErr: fmt.Sprintf(`cannot find discharge macaroon for caveat %x`, "bob-is-great"),
|
||||
}},
|
||||
}, {
|
||||
about: "single third party caveat with discharge",
|
||||
macaroons: []macaroonSpec{{
|
||||
rootKey: "root-key",
|
||||
id: "root-id",
|
||||
caveats: []caveat{{
|
||||
condition: "wonderful",
|
||||
}, {
|
||||
condition: "bob-is-great",
|
||||
location: "bob",
|
||||
rootKey: "bob-caveat-root-key",
|
||||
}},
|
||||
}, {
|
||||
location: "bob",
|
||||
rootKey: "bob-caveat-root-key",
|
||||
id: "bob-is-great",
|
||||
}},
|
||||
conditions: []conditionTest{{
|
||||
conditions: map[string]bool{
|
||||
"wonderful": true,
|
||||
},
|
||||
}, {
|
||||
conditions: map[string]bool{
|
||||
"wonderful": false,
|
||||
},
|
||||
expectErr: `condition "wonderful" not met`,
|
||||
}},
|
||||
}, {
|
||||
about: "single third party caveat with discharge with mismatching root key",
|
||||
macaroons: []macaroonSpec{{
|
||||
rootKey: "root-key",
|
||||
id: "root-id",
|
||||
caveats: []caveat{{
|
||||
condition: "wonderful",
|
||||
}, {
|
||||
condition: "bob-is-great",
|
||||
location: "bob",
|
||||
rootKey: "bob-caveat-root-key",
|
||||
}},
|
||||
}, {
|
||||
location: "bob",
|
||||
rootKey: "bob-caveat-root-key-wrong",
|
||||
id: "bob-is-great",
|
||||
}},
|
||||
conditions: []conditionTest{{
|
||||
conditions: map[string]bool{
|
||||
"wonderful": true,
|
||||
},
|
||||
expectErr: `signature mismatch after caveat verification`,
|
||||
}},
|
||||
}, {
|
||||
about: "single third party caveat with two discharges",
|
||||
macaroons: []macaroonSpec{{
|
||||
rootKey: "root-key",
|
||||
id: "root-id",
|
||||
caveats: []caveat{{
|
||||
condition: "wonderful",
|
||||
}, {
|
||||
condition: "bob-is-great",
|
||||
location: "bob",
|
||||
rootKey: "bob-caveat-root-key",
|
||||
}},
|
||||
}, {
|
||||
location: "bob",
|
||||
rootKey: "bob-caveat-root-key",
|
||||
id: "bob-is-great",
|
||||
caveats: []caveat{{
|
||||
condition: "splendid",
|
||||
}},
|
||||
}, {
|
||||
location: "bob",
|
||||
rootKey: "bob-caveat-root-key",
|
||||
id: "bob-is-great",
|
||||
caveats: []caveat{{
|
||||
condition: "top of the world",
|
||||
}},
|
||||
}},
|
||||
conditions: []conditionTest{{
|
||||
conditions: map[string]bool{
|
||||
"wonderful": true,
|
||||
},
|
||||
expectErr: `condition "splendid" not met`,
|
||||
}, {
|
||||
conditions: map[string]bool{
|
||||
"wonderful": true,
|
||||
"splendid": true,
|
||||
"top of the world": true,
|
||||
},
|
||||
expectErr: `discharge macaroon "bob-is-great" was not used`,
|
||||
}, {
|
||||
conditions: map[string]bool{
|
||||
"wonderful": true,
|
||||
"splendid": false,
|
||||
"top of the world": true,
|
||||
},
|
||||
expectErr: `condition "splendid" not met`,
|
||||
}, {
|
||||
conditions: map[string]bool{
|
||||
"wonderful": true,
|
||||
"splendid": true,
|
||||
"top of the world": false,
|
||||
},
|
||||
expectErr: `discharge macaroon "bob-is-great" was not used`,
|
||||
}},
|
||||
}, {
|
||||
about: "one discharge used for two macaroons",
|
||||
macaroons: []macaroonSpec{{
|
||||
rootKey: "root-key",
|
||||
id: "root-id",
|
||||
caveats: []caveat{{
|
||||
condition: "somewhere else",
|
||||
location: "bob",
|
||||
rootKey: "bob-caveat-root-key",
|
||||
}, {
|
||||
condition: "bob-is-great",
|
||||
location: "charlie",
|
||||
rootKey: "bob-caveat-root-key",
|
||||
}},
|
||||
}, {
|
||||
location: "bob",
|
||||
rootKey: "bob-caveat-root-key",
|
||||
id: "somewhere else",
|
||||
caveats: []caveat{{
|
||||
condition: "bob-is-great",
|
||||
location: "charlie",
|
||||
rootKey: "bob-caveat-root-key",
|
||||
}},
|
||||
}, {
|
||||
location: "bob",
|
||||
rootKey: "bob-caveat-root-key",
|
||||
id: "bob-is-great",
|
||||
}},
|
||||
conditions: []conditionTest{{
|
||||
expectErr: `discharge macaroon "bob-is-great" was used more than once`,
|
||||
}},
|
||||
}, {
|
||||
about: "recursive third party caveat",
|
||||
macaroons: []macaroonSpec{{
|
||||
rootKey: "root-key",
|
||||
id: "root-id",
|
||||
caveats: []caveat{{
|
||||
condition: "bob-is-great",
|
||||
location: "bob",
|
||||
rootKey: "bob-caveat-root-key",
|
||||
}},
|
||||
}, {
|
||||
location: "bob",
|
||||
rootKey: "bob-caveat-root-key",
|
||||
id: "bob-is-great",
|
||||
caveats: []caveat{{
|
||||
condition: "bob-is-great",
|
||||
location: "charlie",
|
||||
rootKey: "bob-caveat-root-key",
|
||||
}},
|
||||
}},
|
||||
conditions: []conditionTest{{
|
||||
expectErr: `discharge macaroon "bob-is-great" was used more than once`,
|
||||
}},
|
||||
}, {
|
||||
about: "two third party caveats",
|
||||
macaroons: []macaroonSpec{{
|
||||
rootKey: "root-key",
|
||||
id: "root-id",
|
||||
caveats: []caveat{{
|
||||
condition: "wonderful",
|
||||
}, {
|
||||
condition: "bob-is-great",
|
||||
location: "bob",
|
||||
rootKey: "bob-caveat-root-key",
|
||||
}, {
|
||||
condition: "charlie-is-great",
|
||||
location: "charlie",
|
||||
rootKey: "charlie-caveat-root-key",
|
||||
}},
|
||||
}, {
|
||||
location: "bob",
|
||||
rootKey: "bob-caveat-root-key",
|
||||
id: "bob-is-great",
|
||||
caveats: []caveat{{
|
||||
condition: "splendid",
|
||||
}},
|
||||
}, {
|
||||
location: "charlie",
|
||||
rootKey: "charlie-caveat-root-key",
|
||||
id: "charlie-is-great",
|
||||
caveats: []caveat{{
|
||||
condition: "top of the world",
|
||||
}},
|
||||
}},
|
||||
conditions: []conditionTest{{
|
||||
conditions: map[string]bool{
|
||||
"wonderful": true,
|
||||
"splendid": true,
|
||||
"top of the world": true,
|
||||
},
|
||||
}, {
|
||||
conditions: map[string]bool{
|
||||
"wonderful": true,
|
||||
"splendid": false,
|
||||
"top of the world": true,
|
||||
},
|
||||
expectErr: `condition "splendid" not met`,
|
||||
}, {
|
||||
conditions: map[string]bool{
|
||||
"wonderful": true,
|
||||
"splendid": true,
|
||||
"top of the world": false,
|
||||
},
|
||||
expectErr: `condition "top of the world" not met`,
|
||||
}},
|
||||
}, {
|
||||
about: "third party caveat with undischarged third party caveat",
|
||||
macaroons: []macaroonSpec{{
|
||||
rootKey: "root-key",
|
||||
id: "root-id",
|
||||
caveats: []caveat{{
|
||||
condition: "wonderful",
|
||||
}, {
|
||||
condition: "bob-is-great",
|
||||
location: "bob",
|
||||
rootKey: "bob-caveat-root-key",
|
||||
}},
|
||||
}, {
|
||||
location: "bob",
|
||||
rootKey: "bob-caveat-root-key",
|
||||
id: "bob-is-great",
|
||||
caveats: []caveat{{
|
||||
condition: "splendid",
|
||||
}, {
|
||||
condition: "barbara-is-great",
|
||||
location: "barbara",
|
||||
rootKey: "barbara-caveat-root-key",
|
||||
}},
|
||||
}},
|
||||
conditions: []conditionTest{{
|
||||
conditions: map[string]bool{
|
||||
"wonderful": true,
|
||||
"splendid": true,
|
||||
},
|
||||
expectErr: fmt.Sprintf(`cannot find discharge macaroon for caveat %x`, "barbara-is-great"),
|
||||
}},
|
||||
}, {
|
||||
about: "multilevel third party caveats",
|
||||
macaroons: multilevelThirdPartyCaveatMacaroons,
|
||||
conditions: []conditionTest{{
|
||||
conditions: map[string]bool{
|
||||
"wonderful": true,
|
||||
"splendid": true,
|
||||
"high-fiving": true,
|
||||
"spiffing": true,
|
||||
},
|
||||
}, {
|
||||
conditions: map[string]bool{
|
||||
"wonderful": true,
|
||||
"splendid": true,
|
||||
"high-fiving": false,
|
||||
"spiffing": true,
|
||||
},
|
||||
expectErr: `condition "high-fiving" not met`,
|
||||
}},
|
||||
}, {
|
||||
about: "unused discharge",
|
||||
macaroons: []macaroonSpec{{
|
||||
rootKey: "root-key",
|
||||
id: "root-id",
|
||||
}, {
|
||||
rootKey: "other-key",
|
||||
id: "unused",
|
||||
}},
|
||||
conditions: []conditionTest{{
|
||||
expectErr: `discharge macaroon "unused" was not used`,
|
||||
}},
|
||||
}}
|
||||
|
||||
var multilevelThirdPartyCaveatMacaroons = []macaroonSpec{{
|
||||
rootKey: "root-key",
|
||||
id: "root-id",
|
||||
caveats: []caveat{{
|
||||
condition: "wonderful",
|
||||
}, {
|
||||
condition: "bob-is-great",
|
||||
location: "bob",
|
||||
rootKey: "bob-caveat-root-key",
|
||||
}, {
|
||||
condition: "charlie-is-great",
|
||||
location: "charlie",
|
||||
rootKey: "charlie-caveat-root-key",
|
||||
}},
|
||||
}, {
|
||||
location: "bob",
|
||||
rootKey: "bob-caveat-root-key",
|
||||
id: "bob-is-great",
|
||||
caveats: []caveat{{
|
||||
condition: "splendid",
|
||||
}, {
|
||||
condition: "barbara-is-great",
|
||||
location: "barbara",
|
||||
rootKey: "barbara-caveat-root-key",
|
||||
}},
|
||||
}, {
|
||||
location: "charlie",
|
||||
rootKey: "charlie-caveat-root-key",
|
||||
id: "charlie-is-great",
|
||||
caveats: []caveat{{
|
||||
condition: "splendid",
|
||||
}, {
|
||||
condition: "celine-is-great",
|
||||
location: "celine",
|
||||
rootKey: "celine-caveat-root-key",
|
||||
}},
|
||||
}, {
|
||||
location: "barbara",
|
||||
rootKey: "barbara-caveat-root-key",
|
||||
id: "barbara-is-great",
|
||||
caveats: []caveat{{
|
||||
condition: "spiffing",
|
||||
}, {
|
||||
condition: "ben-is-great",
|
||||
location: "ben",
|
||||
rootKey: "ben-caveat-root-key",
|
||||
}},
|
||||
}, {
|
||||
location: "ben",
|
||||
rootKey: "ben-caveat-root-key",
|
||||
id: "ben-is-great",
|
||||
}, {
|
||||
location: "celine",
|
||||
rootKey: "celine-caveat-root-key",
|
||||
id: "celine-is-great",
|
||||
caveats: []caveat{{
|
||||
condition: "high-fiving",
|
||||
}},
|
||||
}}
|
||||
|
||||
func (*macaroonSuite) TestVerify(c *gc.C) {
|
||||
for i, test := range verifyTests {
|
||||
c.Logf("test %d: %s", i, test.about)
|
||||
rootKey, macaroons := makeMacaroons(test.macaroons)
|
||||
for _, cond := range test.conditions {
|
||||
c.Logf("conditions %#v", cond.conditions)
|
||||
check := func(cav string) error {
|
||||
if cond.conditions[cav] {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("condition %q not met", cav)
|
||||
}
|
||||
err := macaroons[0].Verify(
|
||||
rootKey,
|
||||
check,
|
||||
macaroons[1:],
|
||||
)
|
||||
if cond.expectErr != "" {
|
||||
c.Assert(err, gc.ErrorMatches, cond.expectErr)
|
||||
} else {
|
||||
c.Assert(err, gc.IsNil)
|
||||
}
|
||||
|
||||
// Cloned macaroon should have same verify result.
|
||||
cloneErr := macaroons[0].Clone().Verify(rootKey, check, macaroons[1:])
|
||||
c.Assert(cloneErr, gc.DeepEquals, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (*macaroonSuite) TestTraceVerify(c *gc.C) {
|
||||
rootKey, macaroons := makeMacaroons(multilevelThirdPartyCaveatMacaroons)
|
||||
traces, err := macaroons[0].TraceVerify(rootKey, macaroons[1:])
|
||||
c.Assert(err, gc.Equals, nil)
|
||||
c.Assert(traces, gc.HasLen, len(macaroons))
|
||||
// Check that we can run through the resulting operations and
|
||||
// arrive at the same signature.
|
||||
for i, m := range macaroons {
|
||||
r := traces[i].Results()
|
||||
c.Assert(b64str(r[len(r)-1]), gc.Equals, b64str(m.Signature()), gc.Commentf("macaroon %d", i))
|
||||
}
|
||||
}
|
||||
|
||||
func (*macaroonSuite) TestTraceVerifyFailure(c *gc.C) {
|
||||
rootKey, macaroons := makeMacaroons([]macaroonSpec{{
|
||||
rootKey: "xxx",
|
||||
id: "hello",
|
||||
caveats: []caveat{{
|
||||
condition: "cond1",
|
||||
}, {
|
||||
condition: "cond2",
|
||||
}, {
|
||||
condition: "cond3",
|
||||
}},
|
||||
}})
|
||||
// Marshal the macaroon, corrupt a condition, then unmarshal
|
||||
// it and check we see the expected trace failure.
|
||||
data, err := json.Marshal(macaroons[0])
|
||||
c.Assert(err, gc.Equals, nil)
|
||||
var jm macaroon.MacaroonJSONV2
|
||||
err = json.Unmarshal(data, &jm)
|
||||
c.Assert(err, gc.Equals, nil)
|
||||
jm.Caveats[1].CID = "cond2 corrupted"
|
||||
data, err = json.Marshal(jm)
|
||||
c.Assert(err, gc.Equals, nil)
|
||||
|
||||
var corruptm *macaroon.Macaroon
|
||||
err = json.Unmarshal(data, &corruptm)
|
||||
c.Assert(err, gc.Equals, nil)
|
||||
|
||||
traces, err := corruptm.TraceVerify(rootKey, nil)
|
||||
c.Assert(err, gc.ErrorMatches, `signature mismatch after caveat verification`)
|
||||
c.Assert(traces, gc.HasLen, 1)
|
||||
var kinds []macaroon.TraceOpKind
|
||||
for _, op := range traces[0].Ops {
|
||||
kinds = append(kinds, op.Kind)
|
||||
}
|
||||
c.Assert(kinds, gc.DeepEquals, []macaroon.TraceOpKind{
|
||||
macaroon.TraceMakeKey,
|
||||
macaroon.TraceHash, // id
|
||||
macaroon.TraceHash, // cond1
|
||||
macaroon.TraceHash, // cond2
|
||||
macaroon.TraceHash, // cond3
|
||||
macaroon.TraceFail, // sig mismatch
|
||||
})
|
||||
}
|
||||
|
||||
func b64str(b []byte) string {
|
||||
return base64.StdEncoding.EncodeToString(b)
|
||||
}
|
||||
|
||||
func (*macaroonSuite) TestVerifySignature(c *gc.C) {
|
||||
rootKey, macaroons := makeMacaroons([]macaroonSpec{{
|
||||
rootKey: "xxx",
|
||||
id: "hello",
|
||||
caveats: []caveat{{
|
||||
rootKey: "y",
|
||||
condition: "something",
|
||||
location: "somewhere",
|
||||
}, {
|
||||
condition: "cond1",
|
||||
}, {
|
||||
condition: "cond2",
|
||||
}},
|
||||
}, {
|
||||
rootKey: "y",
|
||||
id: "something",
|
||||
caveats: []caveat{{
|
||||
condition: "cond3",
|
||||
}, {
|
||||
condition: "cond4",
|
||||
}},
|
||||
}})
|
||||
conds, err := macaroons[0].VerifySignature(rootKey, macaroons[1:])
|
||||
c.Assert(err, gc.IsNil)
|
||||
c.Assert(conds, jc.DeepEquals, []string{"cond3", "cond4", "cond1", "cond2"})
|
||||
|
||||
conds, err = macaroons[0].VerifySignature(nil, macaroons[1:])
|
||||
c.Assert(err, gc.ErrorMatches, `failed to decrypt caveat 0 signature: decryption failure`)
|
||||
c.Assert(conds, gc.IsNil)
|
||||
}
|
||||
|
||||
// TODO(rog) move the following JSON-marshal tests into marshal_test.go.
|
||||
|
||||
// jsonTestVersions holds the various possible ways of marshaling a macaroon
|
||||
// to JSON.
|
||||
var jsonTestVersions = []macaroon.Version{
|
||||
macaroon.V1,
|
||||
macaroon.V2,
|
||||
}
|
||||
|
||||
func (s *macaroonSuite) TestMarshalJSON(c *gc.C) {
|
||||
for i, vers := range jsonTestVersions {
|
||||
c.Logf("test %d: %v", i, vers)
|
||||
s.testMarshalJSONWithVersion(c, vers)
|
||||
}
|
||||
}
|
||||
|
||||
func (*macaroonSuite) testMarshalJSONWithVersion(c *gc.C, vers macaroon.Version) {
|
||||
rootKey := []byte("secret")
|
||||
m0 := MustNew(rootKey, []byte("some id"), "a location", vers)
|
||||
m0.AddFirstPartyCaveat([]byte("account = 3735928559"))
|
||||
m0JSON, err := json.Marshal(m0)
|
||||
c.Assert(err, gc.IsNil)
|
||||
var m1 macaroon.Macaroon
|
||||
err = json.Unmarshal(m0JSON, &m1)
|
||||
c.Assert(err, gc.IsNil)
|
||||
c.Assert(m0.Location(), gc.Equals, m1.Location())
|
||||
c.Assert(string(m0.Id()), gc.Equals, string(m1.Id()))
|
||||
c.Assert(
|
||||
hex.EncodeToString(m0.Signature()),
|
||||
gc.Equals,
|
||||
hex.EncodeToString(m1.Signature()))
|
||||
c.Assert(m1.Version(), gc.Equals, vers)
|
||||
}
|
||||
|
||||
var jsonRoundTripTests = []struct {
|
||||
about string
|
||||
// data holds the marshaled data. All the data values hold
|
||||
// different encodings of the same macaroon - the same as produced
|
||||
// from the second example in libmacaroons
|
||||
// example README with the following libmacaroons code:
|
||||
//
|
||||
// secret = 'this is a different super-secret key; never use the same secret twice'
|
||||
// public = 'we used our other secret key'
|
||||
// location = 'http://mybank/'
|
||||
// M = macaroons.create(location, secret, public)
|
||||
// M = M.add_first_party_caveat('account = 3735928559')
|
||||
// caveat_key = '4; guaranteed random by a fair toss of the dice'
|
||||
// predicate = 'user = Alice'
|
||||
// identifier = 'this was how we remind auth of key/pred'
|
||||
// M = M.add_third_party_caveat('http://auth.mybank/', caveat_key, identifier)
|
||||
// m.serialize_json()
|
||||
data string
|
||||
expectExactRoundTrip bool
|
||||
expectVers macaroon.Version
|
||||
}{{
|
||||
about: "exact JSON as produced by libmacaroons",
|
||||
data: `{"caveats":[{"cid":"account = 3735928559"},{"cid":"this was how we remind auth of key\/pred","vid":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA027FAuBYhtHwJ58FX6UlVNFtFsGxQHS7uD_w_dedwv4Jjw7UorCREw5rXbRqIKhr","cl":"http:\/\/auth.mybank\/"}],"location":"http:\/\/mybank\/","identifier":"we used our other secret key","signature":"d27db2fd1f22760e4c3dae8137e2d8fc1df6c0741c18aed4b97256bf78d1f55c"}`,
|
||||
expectVers: macaroon.V1,
|
||||
expectExactRoundTrip: true,
|
||||
}, {
|
||||
about: "V2 object with std base-64 binary values",
|
||||
data: `{"c":[{"i64":"YWNjb3VudCA9IDM3MzU5Mjg1NTk="},{"i64":"dGhpcyB3YXMgaG93IHdlIHJlbWluZCBhdXRoIG9mIGtleS9wcmVk","v64":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA027FAuBYhtHwJ58FX6UlVNFtFsGxQHS7uD/w/dedwv4Jjw7UorCREw5rXbRqIKhr","l":"http://auth.mybank/"}],"l":"http://mybank/","i64":"d2UgdXNlZCBvdXIgb3RoZXIgc2VjcmV0IGtleQ==","s64":"0n2y/R8idg5MPa6BN+LY/B32wHQcGK7UuXJWv3jR9Vw="}`,
|
||||
expectVers: macaroon.V2,
|
||||
}, {
|
||||
about: "V2 object with URL base-64 binary values",
|
||||
data: `{"c":[{"i64":"YWNjb3VudCA9IDM3MzU5Mjg1NTk"},{"i64":"dGhpcyB3YXMgaG93IHdlIHJlbWluZCBhdXRoIG9mIGtleS9wcmVk","v64":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA027FAuBYhtHwJ58FX6UlVNFtFsGxQHS7uD_w_dedwv4Jjw7UorCREw5rXbRqIKhr","l":"http://auth.mybank/"}],"l":"http://mybank/","i64":"d2UgdXNlZCBvdXIgb3RoZXIgc2VjcmV0IGtleQ","s64":"0n2y_R8idg5MPa6BN-LY_B32wHQcGK7UuXJWv3jR9Vw"}`,
|
||||
expectVers: macaroon.V2,
|
||||
}, {
|
||||
about: "V2 object with URL base-64 binary values and strings for ASCII",
|
||||
data: `{"c":[{"i":"account = 3735928559"},{"i":"this was how we remind auth of key/pred","v64":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA027FAuBYhtHwJ58FX6UlVNFtFsGxQHS7uD_w_dedwv4Jjw7UorCREw5rXbRqIKhr","l":"http://auth.mybank/"}],"l":"http://mybank/","i":"we used our other secret key","s64":"0n2y_R8idg5MPa6BN-LY_B32wHQcGK7UuXJWv3jR9Vw"}`,
|
||||
expectVers: macaroon.V2,
|
||||
expectExactRoundTrip: true,
|
||||
}, {
|
||||
about: "V2 base64 encoded binary",
|
||||
data: `"` +
|
||||
base64.StdEncoding.EncodeToString([]byte(
|
||||
"\x02"+
|
||||
"\x01\x0ehttp://mybank/"+
|
||||
"\x02\x1cwe used our other secret key"+
|
||||
"\x00"+
|
||||
"\x02\x14account = 3735928559"+
|
||||
"\x00"+
|
||||
"\x01\x13http://auth.mybank/"+
|
||||
"\x02'this was how we remind auth of key/pred"+
|
||||
"\x04\x48\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\x6e\xc5\x02\xe0\x58\x86\xd1\xf0\x27\x9f\x05\x5f\xa5\x25\x54\xd1\x6d\x16\xc1\xb1\x40\x74\xbb\xb8\x3f\xf0\xfd\xd7\x9d\xc2\xfe\x09\x8f\x0e\xd4\xa2\xb0\x91\x13\x0e\x6b\x5d\xb4\x6a\x20\xa8\x6b"+
|
||||
"\x00"+
|
||||
"\x00"+
|
||||
"\x06\x20\xd2\x7d\xb2\xfd\x1f\x22\x76\x0e\x4c\x3d\xae\x81\x37\xe2\xd8\xfc\x1d\xf6\xc0\x74\x1c\x18\xae\xd4\xb9\x72\x56\xbf\x78\xd1\xf5\x5c",
|
||||
)) + `"`,
|
||||
expectVers: macaroon.V2,
|
||||
}}
|
||||
|
||||
func (s *macaroonSuite) TestJSONRoundTrip(c *gc.C) {
|
||||
for i, test := range jsonRoundTripTests {
|
||||
c.Logf("test %d (%v) %s", i, test.expectVers, test.about)
|
||||
s.testJSONRoundTripWithVersion(c, test.data, test.expectVers, test.expectExactRoundTrip)
|
||||
}
|
||||
}
|
||||
|
||||
func (*macaroonSuite) testJSONRoundTripWithVersion(c *gc.C, jsonData string, vers macaroon.Version, expectExactRoundTrip bool) {
|
||||
var m macaroon.Macaroon
|
||||
err := json.Unmarshal([]byte(jsonData), &m)
|
||||
c.Assert(err, gc.IsNil)
|
||||
assertLibMacaroonsMacaroon(c, &m)
|
||||
c.Assert(m.Version(), gc.Equals, vers)
|
||||
|
||||
data, err := m.MarshalJSON()
|
||||
c.Assert(err, gc.IsNil)
|
||||
|
||||
if expectExactRoundTrip {
|
||||
// The data is in canonical form, so we can check that
|
||||
// the round-tripped data is the same as the original
|
||||
// data when unmarshalled into an interface{}.
|
||||
var got interface{}
|
||||
err = json.Unmarshal(data, &got)
|
||||
c.Assert(err, gc.IsNil)
|
||||
|
||||
var original interface{}
|
||||
err = json.Unmarshal([]byte(jsonData), &original)
|
||||
c.Assert(err, gc.IsNil)
|
||||
|
||||
c.Assert(got, jc.DeepEquals, original, gc.Commentf("data: %s", data))
|
||||
}
|
||||
// Check that we can unmarshal the marshaled data anyway
|
||||
// and the macaroon still looks the same.
|
||||
var m1 macaroon.Macaroon
|
||||
err = m1.UnmarshalJSON(data)
|
||||
c.Assert(err, gc.IsNil)
|
||||
assertLibMacaroonsMacaroon(c, &m1)
|
||||
c.Assert(m.Version(), gc.Equals, vers)
|
||||
}
|
||||
|
||||
// assertLibMacaroonsMacaroon asserts that m looks like the macaroon
|
||||
// created in the README of the libmacaroons documentation.
|
||||
// In particular, the signature is the same one reported there.
|
||||
func assertLibMacaroonsMacaroon(c *gc.C, m *macaroon.Macaroon) {
|
||||
c.Assert(fmt.Sprintf("%x", m.Signature()), gc.Equals,
|
||||
"d27db2fd1f22760e4c3dae8137e2d8fc1df6c0741c18aed4b97256bf78d1f55c")
|
||||
c.Assert(m.Location(), gc.Equals, "http://mybank/")
|
||||
c.Assert(string(m.Id()), gc.Equals, "we used our other secret key")
|
||||
c.Assert(m.Caveats(), jc.DeepEquals, []macaroon.Caveat{{
|
||||
Id: []byte("account = 3735928559"),
|
||||
}, {
|
||||
Id: []byte("this was how we remind auth of key/pred"),
|
||||
VerificationId: decodeB64("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA027FAuBYhtHwJ58FX6UlVNFtFsGxQHS7uD_w_dedwv4Jjw7UorCREw5rXbRqIKhr"),
|
||||
Location: "http://auth.mybank/",
|
||||
}})
|
||||
}
|
||||
|
||||
var jsonDecodeErrorTests = []struct {
|
||||
about string
|
||||
data string
|
||||
expectError string
|
||||
}{{
|
||||
about: "ambiguous id #1",
|
||||
data: `{"i": "hello", "i64": "abcd", "s64": "ZDI3ZGIyZmQxZjIyNzYwZTRjM2RhZTgxMzdlMmQ4ZmMK"}`,
|
||||
expectError: "invalid identifier: ambiguous field encoding",
|
||||
}, {
|
||||
about: "ambiguous signature",
|
||||
data: `{"i": "hello", "s": "345", "s64": "543467"}`,
|
||||
expectError: "invalid signature: ambiguous field encoding",
|
||||
}, {
|
||||
about: "signature too short",
|
||||
data: `{"i": "hello", "s64": "0n2y/R8idg5MPa6BN+LY/B32wHQcGK7UuXJWv3jR9Q"}`,
|
||||
expectError: "signature has unexpected length 31",
|
||||
}, {
|
||||
about: "signature too long",
|
||||
data: `{"i": "hello", "s64": "0n2y/R8idg5MPa6BN+LY/B32wHQcGK7UuXJWv3jR9dP1"}`,
|
||||
expectError: "signature has unexpected length 33",
|
||||
}, {
|
||||
about: "invalid caveat id",
|
||||
data: `{"i": "hello", "s64": "0n2y/R8idg5MPa6BN+LY/B32wHQcGK7UuXJWv3jR9Vw", "c": [{"i": "hello", "i64": "00"}]}`,
|
||||
expectError: "invalid cid in caveat: ambiguous field encoding",
|
||||
}, {
|
||||
about: "invalid caveat vid",
|
||||
data: `{"i": "hello", "s64": "0n2y/R8idg5MPa6BN+LY/B32wHQcGK7UuXJWv3jR9Vw", "c": [{"i": "hello", "v": "hello", "v64": "00"}]}`,
|
||||
expectError: "invalid vid in caveat: ambiguous field encoding",
|
||||
}}
|
||||
|
||||
func (*macaroonSuite) TestJSONDecodeError(c *gc.C) {
|
||||
for i, test := range jsonDecodeErrorTests {
|
||||
c.Logf("test %d: %v", i, test.about)
|
||||
var m macaroon.Macaroon
|
||||
err := json.Unmarshal([]byte(test.data), &m)
|
||||
c.Assert(err, gc.ErrorMatches, test.expectError)
|
||||
}
|
||||
}
|
||||
|
||||
func (*macaroonSuite) TestFirstPartyCaveatWithInvalidUTF8(c *gc.C) {
|
||||
rootKey := []byte("secret")
|
||||
badString := "foo\xff"
|
||||
|
||||
m0 := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion)
|
||||
err := m0.AddFirstPartyCaveat([]byte(badString))
|
||||
c.Assert(err, gc.Equals, nil)
|
||||
}
|
||||
|
||||
func decodeB64(s string) []byte {
|
||||
data, err := base64.RawURLEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
type caveat struct {
|
||||
rootKey string
|
||||
location string
|
||||
condition string
|
||||
}
|
||||
|
||||
type macaroonSpec struct {
|
||||
rootKey string
|
||||
id string
|
||||
caveats []caveat
|
||||
location string
|
||||
}
|
||||
|
||||
func makeMacaroons(mspecs []macaroonSpec) (rootKey []byte, macaroons macaroon.Slice) {
|
||||
for _, mspec := range mspecs {
|
||||
macaroons = append(macaroons, makeMacaroon(mspec))
|
||||
}
|
||||
primary := macaroons[0]
|
||||
for _, m := range macaroons[1:] {
|
||||
m.Bind(primary.Signature())
|
||||
}
|
||||
return []byte(mspecs[0].rootKey), macaroons
|
||||
}
|
||||
|
||||
func makeMacaroon(mspec macaroonSpec) *macaroon.Macaroon {
|
||||
m := MustNew([]byte(mspec.rootKey), []byte(mspec.id), mspec.location, macaroon.LatestVersion)
|
||||
for _, cav := range mspec.caveats {
|
||||
if cav.location != "" {
|
||||
err := m.AddThirdPartyCaveat([]byte(cav.rootKey), []byte(cav.condition), cav.location)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
m.AddFirstPartyCaveat([]byte(cav.condition))
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func assertEqualMacaroons(c *gc.C, m0, m1 *macaroon.Macaroon) {
|
||||
m0json, err := m0.MarshalJSON()
|
||||
c.Assert(err, gc.IsNil)
|
||||
m1json, err := m1.MarshalJSON()
|
||||
var m0val, m1val interface{}
|
||||
err = json.Unmarshal(m0json, &m0val)
|
||||
c.Assert(err, gc.IsNil)
|
||||
err = json.Unmarshal(m1json, &m1val)
|
||||
c.Assert(err, gc.IsNil)
|
||||
c.Assert(m0val, gc.DeepEquals, m1val)
|
||||
}
|
||||
|
||||
func (*macaroonSuite) TestBinaryRoundTrip(c *gc.C) {
|
||||
// Test the binary marshalling and unmarshalling of a macaroon with
|
||||
// first and third party caveats.
|
||||
rootKey := []byte("secret")
|
||||
m0 := MustNew(rootKey, []byte("some id"), "a location", macaroon.LatestVersion)
|
||||
err := m0.AddFirstPartyCaveat([]byte("first caveat"))
|
||||
c.Assert(err, gc.IsNil)
|
||||
err = m0.AddFirstPartyCaveat([]byte("second caveat"))
|
||||
c.Assert(err, gc.IsNil)
|
||||
err = m0.AddThirdPartyCaveat([]byte("shared root key"), []byte("3rd party caveat"), "remote.com")
|
||||
c.Assert(err, gc.IsNil)
|
||||
data, err := m0.MarshalBinary()
|
||||
c.Assert(err, gc.IsNil)
|
||||
var m1 macaroon.Macaroon
|
||||
err = m1.UnmarshalBinary(data)
|
||||
c.Assert(err, gc.IsNil)
|
||||
assertEqualMacaroons(c, m0, &m1)
|
||||
}
|
||||
|
||||
func (*macaroonSuite) TestBinaryMarshalingAgainstLibmacaroon(c *gc.C) {
|
||||
// Test that a libmacaroon marshalled macaroon can be correctly unmarshaled
|
||||
data, err := base64.RawURLEncoding.DecodeString(
|
||||
"MDAxY2xvY2F0aW9uIGh0dHA6Ly9teWJhbmsvCjAwMmNpZGVudGlmaWVyIHdlIHVzZWQgb3VyIG90aGVyIHNlY3JldCBrZXkKMDAxZGNpZCBhY2NvdW50ID0gMzczNTkyODU1OQowMDMwY2lkIHRoaXMgd2FzIGhvdyB3ZSByZW1pbmQgYXV0aCBvZiBrZXkvcHJlZAowMDUxdmlkIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANNuxQLgWIbR8CefBV-lJVTRbRbBsUB0u7g_8P3XncL-CY8O1KKwkRMOa120aiCoawowMDFiY2wgaHR0cDovL2F1dGgubXliYW5rLwowMDJmc2lnbmF0dXJlINJ9sv0fInYOTD2ugTfi2Pwd9sB0HBiu1LlyVr940fVcCg")
|
||||
c.Assert(err, gc.IsNil)
|
||||
var m0 macaroon.Macaroon
|
||||
err = m0.UnmarshalBinary(data)
|
||||
c.Assert(err, gc.IsNil)
|
||||
jsonData := []byte(`{"caveats":[{"cid":"account = 3735928559"},{"cid":"this was how we remind auth of key\/pred","vid":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA027FAuBYhtHwJ58FX6UlVNFtFsGxQHS7uD_w_dedwv4Jjw7UorCREw5rXbRqIKhr","cl":"http:\/\/auth.mybank\/"}],"location":"http:\/\/mybank\/","identifier":"we used our other secret key","signature":"d27db2fd1f22760e4c3dae8137e2d8fc1df6c0741c18aed4b97256bf78d1f55c"}`)
|
||||
var m1 macaroon.Macaroon
|
||||
err = m1.UnmarshalJSON(jsonData)
|
||||
c.Assert(err, gc.IsNil)
|
||||
assertEqualMacaroons(c, &m0, &m1)
|
||||
}
|
||||
|
||||
var binaryFieldBase64ChoiceTests = []struct {
|
||||
id string
|
||||
expectBase64 bool
|
||||
}{
|
||||
{"x", false},
|
||||
{"\x00", true},
|
||||
{"\x03\x00", true},
|
||||
{"a longer id with more stuff", false},
|
||||
{"a longer id with more stuff and one invalid \xff", true},
|
||||
{"a longer id with more stuff and one encoded \x00", false},
|
||||
}
|
||||
|
||||
func (*macaroonSuite) TestBinaryFieldBase64Choice(c *gc.C) {
|
||||
for i, test := range binaryFieldBase64ChoiceTests {
|
||||
c.Logf("test %d: %q", i, test.id)
|
||||
m := MustNew([]byte{0}, []byte(test.id), "", macaroon.LatestVersion)
|
||||
data, err := json.Marshal(m)
|
||||
c.Assert(err, gc.Equals, nil)
|
||||
var x struct {
|
||||
Id *string `json:"i"`
|
||||
Id64 *string `json:"i64"`
|
||||
}
|
||||
err = json.Unmarshal(data, &x)
|
||||
c.Assert(err, gc.Equals, nil)
|
||||
if test.expectBase64 {
|
||||
c.Assert(x.Id64, gc.NotNil)
|
||||
c.Assert(x.Id, gc.IsNil)
|
||||
idDec, err := base64.RawURLEncoding.DecodeString(*x.Id64)
|
||||
c.Assert(err, gc.Equals, nil)
|
||||
c.Assert(string(idDec), gc.Equals, test.id)
|
||||
} else {
|
||||
c.Assert(x.Id64, gc.IsNil)
|
||||
c.Assert(x.Id, gc.NotNil)
|
||||
c.Assert(*x.Id, gc.Equals, test.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
190
vendor/src/github.com/go-macaroon/macaroon/marshal-v1.go
vendored
Normal file
190
vendor/src/github.com/go-macaroon/macaroon/marshal-v1.go
vendored
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
package 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
|
||||
}
|
||||
253
vendor/src/github.com/go-macaroon/macaroon/marshal-v2.go
vendored
Normal file
253
vendor/src/github.com/go-macaroon/macaroon/marshal-v2.go
vendored
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
package 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
|
||||
}
|
||||
239
vendor/src/github.com/go-macaroon/macaroon/marshal.go
vendored
Normal file
239
vendor/src/github.com/go-macaroon/macaroon/marshal.go
vendored
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
package 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
|
||||
}
|
||||
203
vendor/src/github.com/go-macaroon/macaroon/marshal_test.go
vendored
Normal file
203
vendor/src/github.com/go-macaroon/macaroon/marshal_test.go
vendored
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
package macaroon_test
|
||||
|
||||
import (
|
||||
jc "github.com/juju/testing/checkers"
|
||||
gc "gopkg.in/check.v1"
|
||||
|
||||
"gopkg.in/macaroon.v2"
|
||||
)
|
||||
|
||||
type marshalSuite struct{}
|
||||
|
||||
var _ = gc.Suite(&marshalSuite{})
|
||||
|
||||
func (s *marshalSuite) TestMarshalUnmarshalMacaroonV1(c *gc.C) {
|
||||
s.testMarshalUnmarshalWithVersion(c, macaroon.V1)
|
||||
}
|
||||
|
||||
func (s *marshalSuite) TestMarshalUnmarshalMacaroonV2(c *gc.C) {
|
||||
s.testMarshalUnmarshalWithVersion(c, macaroon.V2)
|
||||
}
|
||||
|
||||
func (*marshalSuite) testMarshalUnmarshalWithVersion(c *gc.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, gc.IsNil)
|
||||
|
||||
err = m.AddFirstPartyCaveat([]byte("a caveat"))
|
||||
c.Assert(err, gc.IsNil)
|
||||
|
||||
b, err := m.MarshalBinary()
|
||||
c.Assert(err, gc.IsNil)
|
||||
|
||||
var um macaroon.Macaroon
|
||||
err = um.UnmarshalBinary(b)
|
||||
c.Assert(err, gc.IsNil)
|
||||
|
||||
c.Assert(um.Location(), gc.Equals, m.Location())
|
||||
c.Assert(string(um.Id()), gc.Equals, string(m.Id()))
|
||||
c.Assert(um.Signature(), jc.DeepEquals, m.Signature())
|
||||
c.Assert(um.Caveats(), jc.DeepEquals, m.Caveats())
|
||||
c.Assert(um.Version(), gc.Equals, vers)
|
||||
um.SetVersion(m.Version())
|
||||
c.Assert(m, jc.DeepEquals, &um)
|
||||
}
|
||||
|
||||
func (s *marshalSuite) TestMarshalBinaryRoundTrip(c *gc.C) {
|
||||
// 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, gc.Equals, nil)
|
||||
assertLibMacaroonsMacaroon(c, &m)
|
||||
c.Assert(m.Version(), gc.Equals, macaroon.V2)
|
||||
|
||||
data1, err := m.MarshalBinary()
|
||||
c.Assert(err, gc.Equals, nil)
|
||||
c.Assert(data1, jc.DeepEquals, data)
|
||||
}
|
||||
|
||||
func (s *marshalSuite) TestMarshalUnmarshalSliceV1(c *gc.C) {
|
||||
s.testMarshalUnmarshalSliceWithVersion(c, macaroon.V1)
|
||||
}
|
||||
|
||||
func (s *marshalSuite) TestMarshalUnmarshalSliceV2(c *gc.C) {
|
||||
s.testMarshalUnmarshalSliceWithVersion(c, macaroon.V2)
|
||||
}
|
||||
|
||||
func (*marshalSuite) testMarshalUnmarshalSliceWithVersion(c *gc.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, gc.IsNil)
|
||||
err = m2.AddFirstPartyCaveat([]byte("another caveat"))
|
||||
c.Assert(err, gc.IsNil)
|
||||
|
||||
macaroons := macaroon.Slice{m1, m2}
|
||||
|
||||
b, err := macaroons.MarshalBinary()
|
||||
c.Assert(err, gc.IsNil)
|
||||
|
||||
var unmarshaledMacs macaroon.Slice
|
||||
err = unmarshaledMacs.UnmarshalBinary(b)
|
||||
c.Assert(err, gc.IsNil)
|
||||
|
||||
c.Assert(unmarshaledMacs, gc.HasLen, len(macaroons))
|
||||
for i, m := range macaroons {
|
||||
um := unmarshaledMacs[i]
|
||||
c.Assert(um.Location(), gc.Equals, m.Location())
|
||||
c.Assert(string(um.Id()), gc.Equals, string(m.Id()))
|
||||
c.Assert(um.Signature(), jc.DeepEquals, m.Signature())
|
||||
c.Assert(um.Caveats(), jc.DeepEquals, m.Caveats())
|
||||
c.Assert(um.Version(), gc.Equals, vers)
|
||||
um.SetVersion(m.Version())
|
||||
}
|
||||
c.Assert(macaroons, jc.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, gc.IsNil)
|
||||
}
|
||||
unmarshaledMacs[1].SetVersion(macaroons[1].Version())
|
||||
c.Assert(unmarshaledMacs[1], jc.DeepEquals, macaroons[1])
|
||||
c.Assert(err, gc.IsNil)
|
||||
}
|
||||
|
||||
func (s *marshalSuite) TestSliceRoundTripV1(c *gc.C) {
|
||||
s.testSliceRoundTripWithVersion(c, macaroon.V1)
|
||||
}
|
||||
|
||||
func (s *marshalSuite) TestSliceRoundTripV2(c *gc.C) {
|
||||
s.testSliceRoundTripWithVersion(c, macaroon.V2)
|
||||
}
|
||||
|
||||
func (*marshalSuite) testSliceRoundTripWithVersion(c *gc.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, gc.IsNil)
|
||||
err = m2.AddFirstPartyCaveat([]byte("another caveat"))
|
||||
c.Assert(err, gc.IsNil)
|
||||
|
||||
macaroons := macaroon.Slice{m1, m2}
|
||||
|
||||
b, err := macaroons.MarshalBinary()
|
||||
c.Assert(err, gc.IsNil)
|
||||
|
||||
var unmarshaledMacs macaroon.Slice
|
||||
err = unmarshaledMacs.UnmarshalBinary(b)
|
||||
c.Assert(err, gc.IsNil)
|
||||
|
||||
marshaledMacs, err := unmarshaledMacs.MarshalBinary()
|
||||
c.Assert(err, gc.IsNil)
|
||||
|
||||
c.Assert(b, jc.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 (*marshalSuite) TestBase64Decode(c *gc.C) {
|
||||
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, gc.ErrorMatches, test.expectError)
|
||||
} else {
|
||||
c.Assert(err, gc.Equals, nil)
|
||||
c.Assert(string(out), gc.Equals, test.expect)
|
||||
}
|
||||
}
|
||||
}
|
||||
133
vendor/src/github.com/go-macaroon/macaroon/packet-v1.go
vendored
Normal file
133
vendor/src/github.com/go-macaroon/macaroon/packet-v1.go
vendored
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
package 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
|
||||
}
|
||||
98
vendor/src/github.com/go-macaroon/macaroon/packet-v1_test.go
vendored
Normal file
98
vendor/src/github.com/go-macaroon/macaroon/packet-v1_test.go
vendored
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
package macaroon
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
gc "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type packetV1Suite struct{}
|
||||
|
||||
var _ = gc.Suite(&packetV1Suite{})
|
||||
|
||||
func (*packetV1Suite) TestAppendPacket(c *gc.C) {
|
||||
data, ok := appendPacketV1(nil, "field", []byte("some data"))
|
||||
c.Assert(ok, gc.Equals, true)
|
||||
c.Assert(string(data), gc.Equals, "0014field some data\n")
|
||||
|
||||
data, ok = appendPacketV1(data, "otherfield", []byte("more and more data"))
|
||||
c.Assert(ok, gc.Equals, true)
|
||||
c.Assert(string(data), gc.Equals, "0014field some data\n0022otherfield more and more data\n")
|
||||
}
|
||||
|
||||
func (*packetV1Suite) TestAppendPacketTooBig(c *gc.C) {
|
||||
data, ok := appendPacketV1(nil, "field", make([]byte, 65532))
|
||||
c.Assert(ok, gc.Equals, false)
|
||||
c.Assert(data, gc.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 (*packetV1Suite) TestParsePacketV1(c *gc.C) {
|
||||
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, gc.ErrorMatches, test.expectErr)
|
||||
c.Assert(p, gc.DeepEquals, packetV1{})
|
||||
continue
|
||||
}
|
||||
c.Assert(err, gc.IsNil)
|
||||
c.Assert(p, gc.DeepEquals, test.expect)
|
||||
}
|
||||
}
|
||||
|
||||
func truncate(d string) string {
|
||||
if len(d) > 50 {
|
||||
return d[0:50] + "..."
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func (*packetV1Suite) TestAsciiHex(c *gc.C) {
|
||||
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, gc.Equals, false)
|
||||
c.Assert(value, gc.Equals, 0)
|
||||
} else {
|
||||
c.Assert(ok, gc.Equals, true)
|
||||
c.Assert(value, gc.Equals, int(n))
|
||||
}
|
||||
}
|
||||
}
|
||||
117
vendor/src/github.com/go-macaroon/macaroon/packet-v2.go
vendored
Normal file
117
vendor/src/github.com/go-macaroon/macaroon/packet-v2.go
vendored
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
package 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]...)
|
||||
}
|
||||
126
vendor/src/github.com/go-macaroon/macaroon/packet-v2_test.go
vendored
Normal file
126
vendor/src/github.com/go-macaroon/macaroon/packet-v2_test.go
vendored
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
package macaroon
|
||||
|
||||
import (
|
||||
jc "github.com/juju/testing/checkers"
|
||||
gc "gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type packetV2Suite struct{}
|
||||
|
||||
var _ = gc.Suite(&packetV2Suite{})
|
||||
|
||||
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 (*packetV2Suite) TestParsePacketV2(c *gc.C) {
|
||||
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, gc.ErrorMatches, test.expectError)
|
||||
c.Assert(data, gc.IsNil)
|
||||
c.Assert(p, gc.DeepEquals, packetV2{})
|
||||
} else {
|
||||
c.Assert(err, gc.IsNil)
|
||||
c.Assert(p, jc.DeepEquals, 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 (*packetV2Suite) TestParseSectionV2(c *gc.C) {
|
||||
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, gc.ErrorMatches, test.expectError)
|
||||
c.Assert(data, gc.IsNil)
|
||||
c.Assert(ps, gc.IsNil)
|
||||
} else {
|
||||
c.Assert(err, gc.IsNil)
|
||||
c.Assert(ps, jc.DeepEquals, test.expectPackets)
|
||||
}
|
||||
}
|
||||
}
|
||||
102
vendor/src/github.com/go-macaroon/macaroon/trace.go
vendored
Normal file
102
vendor/src/github.com/go-macaroon/macaroon/trace.go
vendored
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
package 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]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue