1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
|
// 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.
package macaroon
import (
"bytes"
"crypto/hmac"
"crypto/rand"
"fmt"
"io"
)
// 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 {
// data holds the binary-marshalled form
// of the macaroon data.
data []byte
location packet
id packet
caveats []caveat
sig [hashLen]byte
}
// caveat holds a first person or third party caveat.
type caveat struct {
location packet
caveatId packet
verificationId packet
}
type Caveat struct {
Id string
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 cav.verificationId.len() > 0
}
// New returns a new macaroon with the given root key,
// identifier and location.
func New(rootKey []byte, id, loc string) (*Macaroon, error) {
var m Macaroon
if err := m.init(id, loc); err != nil {
return nil, err
}
derivedKey := makeKey(rootKey)
m.sig = *keyedHash(derivedKey, m.dataBytes(m.id))
return &m, nil
}
func (m *Macaroon) init(id, loc string) error {
var ok bool
m.location, ok = m.appendPacket(fieldLocation, []byte(loc))
if !ok {
return fmt.Errorf("macaroon location too big")
}
m.id, ok = m.appendPacket(fieldIdentifier, []byte(id))
if !ok {
return fmt.Errorf("macaroon identifier too big")
}
return nil
}
// Clone returns a copy of the receiving macaroon.
func (m *Macaroon) Clone() *Macaroon {
m1 := *m
// Ensure that if any data is appended to the new
// macaroon, it will copy data and caveats.
m1.data = m1.data[0:len(m1.data):len(m1.data)]
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.dataStr(m.location)
}
// Id returns the id of the macaroon. This can hold
// arbitrary information.
func (m *Macaroon) Id() string {
return m.dataStr(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 {
caveats := make([]Caveat, len(m.caveats))
for i, cav := range m.caveats {
caveats[i] = Caveat{
Id: m.dataStr(cav.caveatId),
Location: m.dataStr(cav.location),
}
}
return caveats
}
// appendCaveat appends a caveat without modifying the macaroon's signature.
func (m *Macaroon) appendCaveat(caveatId string, verificationId []byte, loc string) (*caveat, error) {
var cav caveat
var ok bool
if caveatId != "" {
cav.caveatId, ok = m.appendPacket(fieldCaveatId, []byte(caveatId))
if !ok {
return nil, fmt.Errorf("caveat identifier too big")
}
}
if len(verificationId) > 0 {
cav.verificationId, ok = m.appendPacket(fieldVerificationId, verificationId)
if !ok {
return nil, fmt.Errorf("caveat verification id too big")
}
}
if loc != "" {
cav.location, ok = m.appendPacket(fieldCaveatLocation, []byte(loc))
if !ok {
return nil, fmt.Errorf("caveat location too big")
}
}
m.caveats = append(m.caveats, cav)
return &m.caveats[len(m.caveats)-1], nil
}
func (m *Macaroon) addCaveat(caveatId string, verificationId []byte, loc string) error {
cav, err := m.appendCaveat(caveatId, verificationId, loc)
if err != nil {
return err
}
m.sig = *keyedHash2(&m.sig, m.dataBytes(cav.verificationId), m.dataBytes(cav.caveatId))
return nil
}
func keyedHash2(key *[keyLen]byte, d1, d2 []byte) *[hashLen]byte {
if len(d1) == 0 {
return keyedHash(key, d2)
}
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(caveatId string) error {
return m.addCaveat(caveatId, 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 []byte, caveatId string, loc string) error {
return m.addThirdPartyCaveatWithRand(rootKey, caveatId, loc, rand.Reader)
}
func (m *Macaroon) addThirdPartyCaveatWithRand(rootKey []byte, caveatId string, loc string, r io.Reader) error {
derivedKey := makeKey(rootKey)
verificationId, err := encrypt(&m.sig, derivedKey, r)
if err != nil {
return err
}
return m.addCaveat(caveatId, verificationId, loc)
}
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 {
derivedKey := makeKey(rootKey)
// TODO(rog) consider distinguishing between classes of
// check error - some errors may be resolved by minting
// a new macaroon; others may not.
used := make([]int, len(discharges))
if err := m.verify(&m.sig, derivedKey, check, discharges, used); err != nil {
return err
}
for i, dm := range discharges {
switch used[i] {
case 0:
return fmt.Errorf("discharge macaroon %q was not used", dm.Id())
case 1:
continue
default:
// Should be impossible because of check in verify, but be defensive.
return fmt.Errorf("discharge macaroon %q was used more than once", dm.Id())
}
}
return nil
}
func (m *Macaroon) verify(rootSig *[hashLen]byte, rootKey *[hashLen]byte, check func(caveat string) error, discharges []*Macaroon, used []int) error {
caveatSig := keyedHash(rootKey, m.dataBytes(m.id))
for i, cav := range m.caveats {
if cav.isThirdParty() {
cavKey, err := decrypt(caveatSig, m.dataBytes(cav.verificationId))
if err != nil {
return fmt.Errorf("failed to decrypt caveat %d signature: %v", i, err)
}
// We choose an arbitrary error from one of the
// possible discharge macaroon verifications
// if there's more than one discharge macaroon
// with the required id.
found := false
for di, dm := range discharges {
if !bytes.Equal(dm.dataBytes(dm.id), m.dataBytes(cav.caveatId)) {
continue
}
found = true
// It's important that we do this before calling verify,
// as it prevents potentially infinite recursion.
if used[di]++; used[di] > 1 {
return fmt.Errorf("discharge macaroon %q was used more than once", dm.Id())
}
if err := dm.verify(rootSig, cavKey, check, discharges, used); err != nil {
return err
}
break
}
if !found {
return fmt.Errorf("cannot find discharge macaroon for caveat %q", m.dataBytes(cav.caveatId))
}
} else {
if err := check(string(m.dataBytes(cav.caveatId))); err != nil {
return err
}
}
caveatSig = keyedHash2(caveatSig, m.dataBytes(cav.verificationId), m.dataBytes(cav.caveatId))
}
// TODO perhaps we should actually do this check before doing
// all the potentially expensive caveat checks.
boundSig := bindForRequest(rootSig[:], caveatSig)
if !hmac.Equal(boundSig[:], m.sig[:]) {
return fmt.Errorf("signature mismatch after caveat verification")
}
return nil
}
type Verifier interface {
Verify(m *Macaroon, rootKey []byte) (bool, error)
}
|