/
macaroon.go
155 lines (127 loc) · 4.21 KB
/
macaroon.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
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
}