2021-12-17 14:47:06 +01:00
|
|
|
package session
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
2022-02-07 16:13:22 +01:00
|
|
|
"context"
|
2021-12-17 14:47:06 +01:00
|
|
|
"encoding/binary"
|
|
|
|
|
"encoding/hex"
|
2022-09-23 10:44:32 +02:00
|
|
|
"fmt"
|
2021-12-17 14:47:06 +01:00
|
|
|
"strconv"
|
|
|
|
|
|
2023-08-29 14:23:42 +02:00
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
2021-12-17 14:47:06 +01:00
|
|
|
"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}
|
|
|
|
|
)
|
|
|
|
|
|
2022-09-23 10:44:32 +02:00
|
|
|
// ID represents the id of a session.
|
|
|
|
|
type ID [4]byte
|
|
|
|
|
|
2022-02-07 16:13:22 +01:00
|
|
|
// SuperMacaroonValidator is a function type for validating a super macaroon.
|
|
|
|
|
type SuperMacaroonValidator func(ctx context.Context,
|
|
|
|
|
superMacaroon []byte, requiredPermissions []bakery.Op,
|
|
|
|
|
fullMethod string) error
|
|
|
|
|
|
2021-12-17 14:47:06 +01:00
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-08 14:01:03 +02:00
|
|
|
rootKeyID, err := RootKeyIDFromMacaroon(mac)
|
2021-12-17 14:47:06 +01:00
|
|
|
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[:])
|
|
|
|
|
}
|
2022-09-23 10:44:32 +02:00
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
}
|
2023-08-29 14:23:42 +02:00
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|