lightning-terminal/session/macaroon.go

156 lines
4.2 KiB
Go
Raw Normal View History

package session
import (
"bytes"
"context"
"encoding/binary"
"encoding/hex"
"fmt"
"strconv"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/lnrpc"
"google.golang.org/protobuf/proto"
"gopkg.in/macaroon-bakery.v2/bakery"
"gopkg.in/macaroon.v2"
)
var (
// SuperMacaroonRootKeyPrefix is the prefix we set on a super macaroon's
// root key to clearly mark it as such.
SuperMacaroonRootKeyPrefix = [4]byte{0xFF, 0xEE, 0xDD, 0xCC}
)
// ID represents the id of a session.
type ID [4]byte
// SuperMacaroonValidator is a function type for validating a super macaroon.
type SuperMacaroonValidator func(ctx context.Context,
superMacaroon []byte, requiredPermissions []bakery.Op,
fullMethod string) error
// NewSuperMacaroonRootKeyID returns a new macaroon root key ID that has the
// prefix to mark it as a super macaroon root key.
func NewSuperMacaroonRootKeyID(id [4]byte) uint64 {
rootKeyBytes := make([]byte, 8)
copy(rootKeyBytes[:], SuperMacaroonRootKeyPrefix[:])
copy(rootKeyBytes[4:], id[:])
return binary.BigEndian.Uint64(rootKeyBytes)
}
// ParseMacaroon parses a hex encoded macaroon into its native struct.
func ParseMacaroon(macHex string) (*macaroon.Macaroon, error) {
macBytes, err := hex.DecodeString(macHex)
if err != nil {
return nil, err
}
mac := &macaroon.Macaroon{}
if err := mac.UnmarshalBinary(macBytes); err != nil {
return nil, err
}
return mac, nil
}
// IsSuperMacaroon returns true if the given hex encoded macaroon is a super
// macaroon baked by LiT which can be identified by its root key ID.
func IsSuperMacaroon(macHex string) bool {
mac, err := ParseMacaroon(macHex)
if err != nil {
return false
}
rootKeyID, err := RootKeyIDFromMacaroon(mac)
if err != nil {
return false
}
return isSuperMacaroonRootKeyID(rootKeyID)
}
// isSuperMacaroonRootKeyID returns true if the given macaroon root key ID (also
// known as storage ID) is a super macaroon, which can be identified by its
// first 4 bytes.
func isSuperMacaroonRootKeyID(rootKeyID uint64) bool {
rootKeyBytes := make([]byte, 8)
binary.BigEndian.PutUint64(rootKeyBytes, rootKeyID)
return bytes.HasPrefix(rootKeyBytes, SuperMacaroonRootKeyPrefix[:])
}
// IDFromMacaroon is a helper function that creates a session ID from
// a macaroon ID.
func IDFromMacaroon(mac *macaroon.Macaroon) (ID, error) {
rootKeyID, err := RootKeyIDFromMacaroon(mac)
if err != nil {
return ID{}, err
}
return IDFromMacRootKeyID(rootKeyID), nil
}
// IDFromMacRootKeyID converts a macaroon root key ID to a session ID.
func IDFromMacRootKeyID(rootKeyID uint64) ID {
rootKeyBytes := make([]byte, 8)
binary.BigEndian.PutUint64(rootKeyBytes[:], rootKeyID)
var id ID
copy(id[:], rootKeyBytes[4:])
return id
}
// IDFromBytes is a helper function that creates a session ID from a byte slice.
func IDFromBytes(b []byte) (ID, error) {
var id ID
if len(b) != 4 {
return id, fmt.Errorf("session ID must be 4 bytes long")
}
copy(id[:], b)
return id, nil
}
// RootKeyIDFromMacaroon extracts the root key ID of the passed macaroon.
func RootKeyIDFromMacaroon(mac *macaroon.Macaroon) (uint64, error) {
rawID := mac.Id()
if rawID[0] != byte(bakery.LatestVersion) {
return 0, fmt.Errorf("mac id is not on the latest version")
}
decodedID := &lnrpc.MacaroonId{}
idProto := rawID[1:]
err := proto.Unmarshal(idProto, decodedID)
if err != nil {
return 0, err
}
// The storage ID is a string representation of a 64-bit unsigned
// number.
return strconv.ParseUint(string(decodedID.StorageId), 10, 64)
}
// NewSessionPrivKeyAndID randomly derives a new private key and session ID
// pair.
func NewSessionPrivKeyAndID() (*btcec.PrivateKey, ID, error) {
var id ID
privateKey, err := btcec.NewPrivateKey()
if err != nil {
return nil, id, fmt.Errorf("error deriving private key: %v",
err)
}
pubKey := privateKey.PubKey()
// NOTE: we use 4 bytes [1:5] of the serialised public key to create the
// macaroon root key base along with the Session ID. This will provide
// 4 bytes of entropy. Previously, bytes [0:4] where used but this
// resulted in lower entropy due to the first byte always being either
// 0x02 or 0x03.
copy(id[:], pubKey.SerializeCompressed()[1:5])
log.Debugf("Generated new Session ID: %x", id)
return privateKey, id, nil
}