forked from lightningnetwork/lnd
-
Notifications
You must be signed in to change notification settings - Fork 24
/
macaroon_jar.go
162 lines (140 loc) · 4.86 KB
/
macaroon_jar.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
156
157
158
159
160
161
162
package main
import (
"encoding/base64"
"encoding/hex"
"fmt"
"strings"
"github.com/decred/dcrlnd/internal/snacl"
"gopkg.in/macaroon.v2"
)
const (
encryptionPrefix = "snacl:"
)
// getPasswordFn is a function that asks the user to type a password after
// presenting it the given prompt.
type getPasswordFn func(prompt string) ([]byte, error)
// macaroonJar is a struct that represents all macaroons of a profile.
type macaroonJar struct {
Default string `json:"default,omitempty"`
Timeout int64 `json:"timeout,omitempty"`
IP string `json:"ip,omitempty"`
Jar []*macaroonEntry `json:"jar"`
}
// macaroonEntry is a struct that represents a single macaroon. Its content can
// either be cleartext (hex encoded) or encrypted (snacl secretbox).
type macaroonEntry struct {
Name string `json:"name"`
Data string `json:"data"`
}
// loadMacaroon returns the fully usable macaroon instance from the entry. This
// detects whether the macaroon needs to be decrypted and does so if necessary.
// An encrypted macaroon that needs to be decrypted will prompt for the user's
// password by calling the provided password callback. Normally that should
// result in the user being prompted for the password in the terminal.
func (e *macaroonEntry) loadMacaroon(
pwCallback getPasswordFn) (*macaroon.Macaroon, error) {
if len(strings.TrimSpace(e.Data)) == 0 {
return nil, fmt.Errorf("macaroon data is empty")
}
var (
macBytes []byte
err error
)
// Either decrypt or simply decode the macaroon data.
if strings.HasPrefix(e.Data, encryptionPrefix) {
parts := strings.Split(e.Data, ":")
if len(parts) != 3 {
return nil, fmt.Errorf("invalid encrypted macaroon " +
"format, expected 'snacl:<key_base64>:" +
"<encrypted_macaroon_base64>'")
}
pw, err := pwCallback("Enter macaroon encryption password: ")
if err != nil {
return nil, fmt.Errorf("could not read password from "+
"terminal: %v", err)
}
macBytes, err = decryptMacaroon(parts[1], parts[2], pw)
if err != nil {
return nil, fmt.Errorf("unable to decrypt macaroon: %v",
err)
}
} else {
macBytes, err = hex.DecodeString(e.Data)
if err != nil {
return nil, fmt.Errorf("unable to hex decode "+
"macaroon: %v", err)
}
}
// Parse the macaroon data into its native struct.
mac := &macaroon.Macaroon{}
if err := mac.UnmarshalBinary(macBytes); err != nil {
return nil, fmt.Errorf("unable to decode macaroon: %v", err)
}
return mac, nil
}
// storeMacaroon stores a native macaroon instance to the entry. If a non-nil
// password is provided, then the macaroon is encrypted with that password. If
// not, the macaroon is stored as plain text.
func (e *macaroonEntry) storeMacaroon(mac *macaroon.Macaroon, pw []byte) error {
// First of all, make sure we can serialize the macaroon.
macBytes, err := mac.MarshalBinary()
if err != nil {
return fmt.Errorf("unable to marshal macaroon: %v", err)
}
if len(pw) == 0 {
e.Data = hex.EncodeToString(macBytes)
return nil
}
// The user did set a password. Let's derive an encryption key from it.
key, err := snacl.NewSecretKey(
&pw, snacl.DefaultN, snacl.DefaultR, snacl.DefaultP,
)
if err != nil {
return fmt.Errorf("unable to create encryption key: %v", err)
}
// Encrypt the macaroon data with the derived key and store it in the
// human readable format snacl:<key_base64>:<encrypted_macaroon_base64>.
encryptedMac, err := key.Encrypt(macBytes)
if err != nil {
return fmt.Errorf("unable to encrypt macaroon: %v", err)
}
keyB64 := base64.StdEncoding.EncodeToString(key.Marshal())
dataB64 := base64.StdEncoding.EncodeToString(encryptedMac)
e.Data = fmt.Sprintf("%s%s:%s", encryptionPrefix, keyB64, dataB64)
return nil
}
// decryptMacaroon decrypts the cipher text macaroon by using the serialized
// encryption key and the password.
func decryptMacaroon(keyB64, dataB64 string, pw []byte) ([]byte, error) {
// Base64 decode both the marshalled encryption key and macaroon data.
keyData, err := base64.StdEncoding.DecodeString(keyB64)
if err != nil {
return nil, fmt.Errorf("could not base64 decode encryption "+
"key: %v", err)
}
encryptedMac, err := base64.StdEncoding.DecodeString(dataB64)
if err != nil {
return nil, fmt.Errorf("could not base64 decode macaroon "+
"data: %v", err)
}
// Unmarshal the encryption key and ask the user for the password.
key := &snacl.SecretKey{}
err = key.Unmarshal(keyData)
if err != nil {
return nil, fmt.Errorf("could not unmarshal encryption key: %v",
err)
}
// Derive the final encryption key and then decrypt the macaroon with
// it.
err = key.DeriveKey(&pw)
if err != nil {
return nil, fmt.Errorf("could not derive encryption key, "+
"possibly due to incorrect password: %v", err)
}
macBytes, err := key.Decrypt(encryptedMac)
if err != nil {
return nil, fmt.Errorf("could not decrypt macaroon data: %v",
err)
}
return macBytes, nil
}