github.com/decred/politeia@v1.4.0/politeiad/backendv2/tstorebe/store/mysql/encrypt.go (about)

     1  // Copyright (c) 2020-2022 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package mysql
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"database/sql"
    11  	"encoding/binary"
    12  	"encoding/json"
    13  
    14  	"github.com/decred/politeia/util"
    15  	"github.com/marcopeereboom/sbox"
    16  	"github.com/pkg/errors"
    17  	"golang.org/x/crypto/argon2"
    18  )
    19  
    20  const (
    21  	// encryptionKeyParamsKey is the kv store key for the encryption
    22  	// key params that are saved on initial key derivation.
    23  	encryptionKeyParamsKey = "store-mysql-encryptionkeyparams"
    24  )
    25  
    26  // encryptionKeyParams is saved to the kv store on initial derivation of the
    27  // encryption key. It contains the params that were used to derive the key and
    28  // a SHA256 digest of the key. Subsequent derivations will use the existing
    29  // params to derive the key and will use the digest to verify that the
    30  // encryption key has not changed.
    31  type encryptionKeyParams struct {
    32  	Digest []byte            `json:"digest"` // SHA256 digest
    33  	Params util.Argon2Params `json:"params"`
    34  }
    35  
    36  // argon2idKey derives an encryption key using the provided parameters and the
    37  // Argon2id key derivation function. The derived key is set to be the
    38  // encryption key on the mysql context.
    39  func (s *mysqlCtx) argon2idKey(password string, ap util.Argon2Params) {
    40  	k := argon2.IDKey([]byte(password), ap.Salt, ap.Time, ap.Memory,
    41  		ap.Threads, ap.KeyLen)
    42  	copy(s.key[:], k)
    43  	util.Zero(k)
    44  }
    45  
    46  // deriveEncryption derives a 32 byte key from the provided password using the
    47  // Aragon2id key derivation function. A random 16 byte salt is created the
    48  // first time the key is derived. The salt and the other argon2id params are
    49  // saved to the kv store. Subsequent calls to this fuction will pull the
    50  // existing salt and params from the kv store and use them to derive the key,
    51  // then will use the saved encryption key digest to verify that the key has
    52  // not changed.
    53  func (s *mysqlCtx) deriveEncryptionKey(password string) error {
    54  	log.Infof("Deriving encryption key")
    55  
    56  	// Check if the key params already exist in the kv store. Existing
    57  	// params means that the key has been derived previously. These
    58  	// params will be used if found. If no params exist then new ones
    59  	// will be created and saved to the kv store for future use.
    60  	blobs, err := s.Get([]string{encryptionKeyParamsKey})
    61  	if err != nil {
    62  		return err
    63  	}
    64  	var (
    65  		save bool
    66  		ekp  encryptionKeyParams
    67  	)
    68  	b, ok := blobs[encryptionKeyParamsKey]
    69  	if ok {
    70  		log.Debugf("Encryption key params found in kv store")
    71  		err = json.Unmarshal(b, &ekp)
    72  		if err != nil {
    73  			return err
    74  		}
    75  	} else {
    76  		log.Infof("Encryption key params not found; creating new ones")
    77  		ekp = encryptionKeyParams{
    78  			Params: util.NewArgon2Params(),
    79  		}
    80  		save = true
    81  	}
    82  
    83  	// Derive key
    84  	s.argon2idKey(password, ekp.Params)
    85  
    86  	// Check if the params need to be saved
    87  	keyDigest := util.Digest(s.key[:])
    88  	if save {
    89  		// This was the first time the key was derived. Save the params
    90  		// to the kv store.
    91  		ekp.Digest = keyDigest
    92  		b, err := json.Marshal(ekp)
    93  		if err != nil {
    94  			return err
    95  		}
    96  		kv := map[string][]byte{
    97  			encryptionKeyParamsKey: b,
    98  		}
    99  		err = s.Put(kv, false)
   100  		if err != nil {
   101  			return err
   102  		}
   103  
   104  		log.Infof("Encryption key params saved to kv store")
   105  	} else {
   106  		// This was not the first time the key was derived. Verify that
   107  		// the key has not changed.
   108  		if !bytes.Equal(ekp.Digest, keyDigest) {
   109  			return errors.Errorf("attempting to use different encryption key")
   110  		}
   111  	}
   112  
   113  	return nil
   114  }
   115  
   116  var emptyNonce = [24]byte{}
   117  
   118  func (s *mysqlCtx) getDBNonce(ctx context.Context, tx *sql.Tx) ([24]byte, error) {
   119  	// Get nonce value
   120  	nonce, err := s.nonce(ctx, tx)
   121  	if err != nil {
   122  		return emptyNonce, err
   123  	}
   124  
   125  	log.Tracef("Encrypting with nonce: %v", nonce)
   126  
   127  	// Prepare nonce
   128  	b := make([]byte, 8)
   129  	binary.LittleEndian.PutUint64(b, uint64(nonce))
   130  	n, err := sbox.NewNonceFromBytes(b)
   131  	if err != nil {
   132  		return emptyNonce, err
   133  	}
   134  	return n.Current(), nil
   135  }
   136  
   137  func (s *mysqlCtx) getTestNonce(ctx context.Context, tx *sql.Tx) ([24]byte, error) {
   138  	nonce, err := util.Random(8)
   139  	if err != nil {
   140  		return emptyNonce, err
   141  	}
   142  	n, err := sbox.NewNonceFromBytes(nonce)
   143  	if err != nil {
   144  		return emptyNonce, err
   145  	}
   146  	return n.Current(), nil
   147  }
   148  
   149  func (s *mysqlCtx) getNonce(ctx context.Context, tx *sql.Tx) ([24]byte, error) {
   150  	if s.testing {
   151  		return s.getTestNonce(ctx, tx)
   152  	}
   153  	return s.getDBNonce(ctx, tx)
   154  }
   155  
   156  func (s *mysqlCtx) encrypt(ctx context.Context, tx *sql.Tx, data []byte) ([]byte, error) {
   157  	nonce, err := s.getNonce(ctx, tx)
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  	return sbox.EncryptN(0, &s.key, nonce, data)
   162  }
   163  
   164  func (s *mysqlCtx) decrypt(data []byte) ([]byte, uint32, error) {
   165  	return sbox.Decrypt(&s.key, data)
   166  }
   167  
   168  // isEncrypted returns whether the provided blob has been prefixed with an sbox
   169  // header, indicating that it is an encrypted blob.
   170  func isEncrypted(b []byte) bool {
   171  	return bytes.HasPrefix(b, []byte("sbox"))
   172  }