storj/pkg/macaroon/macaroon.go
Jeff Wendling 15e74c8c3d uplink share subcommand (#1924)
* cmd/uplink: add share command to restrict an api key

This commit is an early bit of work to just implement restricting
macaroon api keys from the command line. It does not convert
api keys to be macaroons in general.

It also does not apply the path restriction caveats appropriately
yet because it does not encrypt them.

* cmd/uplink: fix path encryption for shares

It should now properly encrypt the path prefixes when adding
caveats to a macaroon.

* fix up linting problems

* print summary of caveat and require iso8601

* make clone part more clear
2019-05-14 12:15:12 -06:00

129 lines
2.8 KiB
Go

// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package macaroon
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"crypto/subtle"
)
// Macaroon is a struct that determine contextual caveats and authorization
type Macaroon struct {
head []byte
caveats [][]byte
tail []byte
}
// NewUnrestricted creates Macaroon with random Head and generated Tail
func NewUnrestricted(secret []byte) (*Macaroon, error) {
head, err := NewSecret()
if err != nil {
return nil, err
}
return &Macaroon{
head: head,
tail: sign(secret, head),
}, nil
}
func sign(secret []byte, data []byte) []byte {
signer := hmac.New(sha256.New, secret)
_, err := signer.Write(data)
if err != nil {
// Error skipped because sha256 does not return error
panic(err)
}
return signer.Sum(nil)
}
// NewSecret generates cryptographically random 32 bytes
func NewSecret() (secret []byte, err error) {
secret = make([]byte, 32)
_, err = rand.Read(secret)
if err != nil {
return nil, err
}
return secret, nil
}
// AddFirstPartyCaveat creates signed macaroon with appended caveat
func (m *Macaroon) AddFirstPartyCaveat(c []byte) (macaroon *Macaroon, err error) {
macaroon = m.Copy()
macaroon.caveats = append(macaroon.caveats, c)
macaroon.tail = sign(macaroon.tail, c)
return macaroon, nil
}
// Validate reconstructs with all caveats from the secret and compares tails,
// returning true if the tails match
func (m *Macaroon) Validate(secret []byte) (ok bool) {
tail := sign(secret, m.head)
for _, cav := range m.caveats {
tail = sign(tail, cav)
}
return subtle.ConstantTimeCompare(tail, m.tail) == 1
}
// Tails returns all ancestor tails up to and including the current tail
func (m *Macaroon) Tails(secret []byte) [][]byte {
tails := make([][]byte, 0, len(m.caveats)+1)
tail := sign(secret, m.head)
tails = append(tails, tail)
for _, cav := range m.caveats {
tail = sign(tail, cav)
tails = append(tails, tail)
}
return tails
}
// Head returns copy of macaroon head
func (m *Macaroon) Head() (head []byte) {
if len(m.head) == 0 {
return nil
}
return append([]byte(nil), m.head...)
}
// CaveatLen returns the number of caveats this macaroon has
func (m *Macaroon) CaveatLen() int {
return len(m.caveats)
}
// Caveats returns copy of macaroon caveats
func (m *Macaroon) Caveats() (caveats [][]byte) {
if len(m.caveats) == 0 {
return nil
}
caveats = make([][]byte, 0, len(m.caveats))
for _, cav := range m.caveats {
caveats = append(caveats, append([]byte(nil), cav...))
}
return caveats
}
// Tail returns copy of macaroon tail
func (m *Macaroon) Tail() (tail []byte) {
if len(m.tail) == 0 {
return nil
}
return append([]byte(nil), m.tail...)
}
// Copy return copy of macaroon
func (m *Macaroon) Copy() *Macaroon {
return &Macaroon{
head: m.Head(),
caveats: m.Caveats(),
tail: m.Tail(),
}
}