// 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)
}
