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  }