github.com/decred/dcrlnd@v0.7.6/cmd/dcrlncli/macaroon_jar.go (about) 1 package main 2 3 import ( 4 "encoding/base64" 5 "encoding/hex" 6 "fmt" 7 "strings" 8 9 "github.com/decred/dcrlnd/internal/snacl" 10 "gopkg.in/macaroon.v2" 11 ) 12 13 const ( 14 encryptionPrefix = "snacl:" 15 ) 16 17 // getPasswordFn is a function that asks the user to type a password after 18 // presenting it the given prompt. 19 type getPasswordFn func(prompt string) ([]byte, error) 20 21 // macaroonJar is a struct that represents all macaroons of a profile. 22 type macaroonJar struct { 23 Default string `json:"default,omitempty"` 24 Timeout int64 `json:"timeout,omitempty"` 25 IP string `json:"ip,omitempty"` 26 Jar []*macaroonEntry `json:"jar"` 27 } 28 29 // macaroonEntry is a struct that represents a single macaroon. Its content can 30 // either be cleartext (hex encoded) or encrypted (snacl secretbox). 31 type macaroonEntry struct { 32 Name string `json:"name"` 33 Data string `json:"data"` 34 } 35 36 // loadMacaroon returns the fully usable macaroon instance from the entry. This 37 // detects whether the macaroon needs to be decrypted and does so if necessary. 38 // An encrypted macaroon that needs to be decrypted will prompt for the user's 39 // password by calling the provided password callback. Normally that should 40 // result in the user being prompted for the password in the terminal. 41 func (e *macaroonEntry) loadMacaroon( 42 pwCallback getPasswordFn) (*macaroon.Macaroon, error) { 43 44 if len(strings.TrimSpace(e.Data)) == 0 { 45 return nil, fmt.Errorf("macaroon data is empty") 46 } 47 48 var ( 49 macBytes []byte 50 err error 51 ) 52 53 // Either decrypt or simply decode the macaroon data. 54 if strings.HasPrefix(e.Data, encryptionPrefix) { 55 parts := strings.Split(e.Data, ":") 56 if len(parts) != 3 { 57 return nil, fmt.Errorf("invalid encrypted macaroon " + 58 "format, expected 'snacl:<key_base64>:" + 59 "<encrypted_macaroon_base64>'") 60 } 61 62 pw, err := pwCallback("Enter macaroon encryption password: ") 63 if err != nil { 64 return nil, fmt.Errorf("could not read password from "+ 65 "terminal: %v", err) 66 } 67 68 macBytes, err = decryptMacaroon(parts[1], parts[2], pw) 69 if err != nil { 70 return nil, fmt.Errorf("unable to decrypt macaroon: %v", 71 err) 72 } 73 } else { 74 macBytes, err = hex.DecodeString(e.Data) 75 if err != nil { 76 return nil, fmt.Errorf("unable to hex decode "+ 77 "macaroon: %v", err) 78 } 79 } 80 81 // Parse the macaroon data into its native struct. 82 mac := &macaroon.Macaroon{} 83 if err := mac.UnmarshalBinary(macBytes); err != nil { 84 return nil, fmt.Errorf("unable to decode macaroon: %v", err) 85 } 86 return mac, nil 87 } 88 89 // storeMacaroon stores a native macaroon instance to the entry. If a non-nil 90 // password is provided, then the macaroon is encrypted with that password. If 91 // not, the macaroon is stored as plain text. 92 func (e *macaroonEntry) storeMacaroon(mac *macaroon.Macaroon, pw []byte) error { 93 // First of all, make sure we can serialize the macaroon. 94 macBytes, err := mac.MarshalBinary() 95 if err != nil { 96 return fmt.Errorf("unable to marshal macaroon: %v", err) 97 } 98 99 if len(pw) == 0 { 100 e.Data = hex.EncodeToString(macBytes) 101 return nil 102 } 103 104 // The user did set a password. Let's derive an encryption key from it. 105 key, err := snacl.NewSecretKey( 106 &pw, snacl.DefaultN, snacl.DefaultR, snacl.DefaultP, 107 ) 108 if err != nil { 109 return fmt.Errorf("unable to create encryption key: %v", err) 110 } 111 112 // Encrypt the macaroon data with the derived key and store it in the 113 // human readable format snacl:<key_base64>:<encrypted_macaroon_base64>. 114 encryptedMac, err := key.Encrypt(macBytes) 115 if err != nil { 116 return fmt.Errorf("unable to encrypt macaroon: %v", err) 117 } 118 119 keyB64 := base64.StdEncoding.EncodeToString(key.Marshal()) 120 dataB64 := base64.StdEncoding.EncodeToString(encryptedMac) 121 e.Data = fmt.Sprintf("%s%s:%s", encryptionPrefix, keyB64, dataB64) 122 123 return nil 124 } 125 126 // decryptMacaroon decrypts the cipher text macaroon by using the serialized 127 // encryption key and the password. 128 func decryptMacaroon(keyB64, dataB64 string, pw []byte) ([]byte, error) { 129 // Base64 decode both the marshalled encryption key and macaroon data. 130 keyData, err := base64.StdEncoding.DecodeString(keyB64) 131 if err != nil { 132 return nil, fmt.Errorf("could not base64 decode encryption "+ 133 "key: %v", err) 134 } 135 encryptedMac, err := base64.StdEncoding.DecodeString(dataB64) 136 if err != nil { 137 return nil, fmt.Errorf("could not base64 decode macaroon "+ 138 "data: %v", err) 139 } 140 141 // Unmarshal the encryption key and ask the user for the password. 142 key := &snacl.SecretKey{} 143 err = key.Unmarshal(keyData) 144 if err != nil { 145 return nil, fmt.Errorf("could not unmarshal encryption key: %v", 146 err) 147 } 148 149 // Derive the final encryption key and then decrypt the macaroon with 150 // it. 151 err = key.DeriveKey(&pw) 152 if err != nil { 153 return nil, fmt.Errorf("could not derive encryption key, "+ 154 "possibly due to incorrect password: %v", err) 155 } 156 macBytes, err := key.Decrypt(encryptedMac) 157 if err != nil { 158 return nil, fmt.Errorf("could not decrypt macaroon data: %v", 159 err) 160 } 161 return macBytes, nil 162 }