15e74c8c3d
* 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
129 lines
2.8 KiB
Go
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(),
|
|
}
|
|
}
|