github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ccl/cmdccl/enc_utils/main.go (about)

     1  // Copyright 2017 The Cockroach Authors.
     2  //
     3  // Licensed as a CockroachDB Enterprise file under the Cockroach Community
     4  // License (the "License"); you may not use this file except in compliance with
     5  // the License. You may obtain a copy of the License at
     6  //
     7  //     https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt
     8  
     9  package main
    10  
    11  import (
    12  	"context"
    13  	"crypto/aes"
    14  	"encoding/binary"
    15  	"encoding/hex"
    16  	"flag"
    17  	"fmt"
    18  	"io/ioutil"
    19  	"path/filepath"
    20  
    21  	"github.com/cockroachdb/cockroach/pkg/ccl/storageccl/engineccl/enginepbccl"
    22  	"github.com/cockroachdb/cockroach/pkg/storage/enginepb"
    23  	"github.com/cockroachdb/cockroach/pkg/util/log"
    24  	"github.com/cockroachdb/cockroach/pkg/util/protoutil"
    25  	"github.com/cockroachdb/errors"
    26  )
    27  
    28  const fileRegistryPath = "COCKROACHDB_REGISTRY"
    29  const keyRegistryPath = "COCKROACHDB_DATA_KEYS"
    30  const currentPath = "CURRENT"
    31  const optionsPathGlob = "OPTIONS-*"
    32  
    33  var dbDir = flag.String("db-dir", "", "path to the db directory")
    34  var storeKeyPath = flag.String("store-key", "", "path to the active store key")
    35  
    36  type fileEntry struct {
    37  	envType  enginepb.EnvType
    38  	settings enginepbccl.EncryptionSettings
    39  }
    40  
    41  func (f fileEntry) String() string {
    42  	ret := fmt.Sprintf(" env type: %d, %s\n",
    43  		f.envType, f.settings.EncryptionType)
    44  	if f.settings.EncryptionType != enginepbccl.EncryptionType_Plaintext {
    45  		ret += fmt.Sprintf("  keyID: %s\n  nonce: % x\n  counter: %d\n",
    46  			f.settings.KeyId,
    47  			f.settings.Nonce,
    48  			f.settings.Counter)
    49  	}
    50  	return ret
    51  }
    52  
    53  type keyEntry struct {
    54  	encryptionType enginepbccl.EncryptionType
    55  	rawKey         []byte
    56  }
    57  
    58  func (k keyEntry) String() string {
    59  	return fmt.Sprintf("%s len: %d", k.encryptionType, len(k.rawKey))
    60  }
    61  
    62  var fileRegistry = map[string]fileEntry{}
    63  var keyRegistry = map[string]keyEntry{}
    64  
    65  func loadFileRegistry() {
    66  	data, err := ioutil.ReadFile(filepath.Join(*dbDir, fileRegistryPath))
    67  	if err != nil {
    68  		log.Fatalf(context.Background(), "could not read %s: %v", fileRegistryPath, err)
    69  	}
    70  
    71  	var reg enginepb.FileRegistry
    72  	if err := protoutil.Unmarshal(data, &reg); err != nil {
    73  		log.Fatalf(context.Background(), "could not unmarshal %s: %v", fileRegistryPath, err)
    74  	}
    75  
    76  	log.Infof(context.Background(), "file registry version: %s", reg.Version)
    77  	log.Infof(context.Background(), "file registry contains %d entries", len(reg.Files))
    78  	for name, entry := range reg.Files {
    79  		var encSettings enginepbccl.EncryptionSettings
    80  		settings := entry.EncryptionSettings
    81  		if err := protoutil.Unmarshal(settings, &encSettings); err != nil {
    82  			log.Fatalf(context.Background(), "could not unmarshal encryption setting for %s: %v", name, err)
    83  		}
    84  
    85  		fileRegistry[name] = fileEntry{entry.EnvType, encSettings}
    86  
    87  		log.Infof(context.Background(), "  %-30s level: %-8s type: %-12s keyID: %s", name, entry.EnvType, encSettings.EncryptionType, encSettings.KeyId[:8])
    88  	}
    89  }
    90  
    91  func loadStoreKey() {
    92  	if len(*storeKeyPath) == 0 || *storeKeyPath == "plain" {
    93  		log.Infof(context.Background(), "No store key specified")
    94  		return
    95  	}
    96  
    97  	data, err := ioutil.ReadFile(*storeKeyPath)
    98  	if err != nil {
    99  		log.Fatalf(context.Background(), "could not read %s: %v", *storeKeyPath, err)
   100  	}
   101  
   102  	var k keyEntry
   103  	switch len(data) {
   104  	case 48:
   105  		k.encryptionType = enginepbccl.EncryptionType_AES128_CTR
   106  	case 56:
   107  		k.encryptionType = enginepbccl.EncryptionType_AES192_CTR
   108  	case 64:
   109  		k.encryptionType = enginepbccl.EncryptionType_AES256_CTR
   110  	default:
   111  		log.Fatalf(context.Background(), "wrong key length %d, want 32 bytes + AES length", len(data))
   112  	}
   113  
   114  	// Hexadecimal representation of the first 32 bytes.
   115  	id := hex.EncodeToString(data[0:32])
   116  	// Raw key is the rest.
   117  	k.rawKey = data[32:]
   118  
   119  	keyRegistry[id] = k
   120  
   121  	log.Infof(context.Background(), "store key: %s", k)
   122  }
   123  
   124  func loadKeyRegistry() {
   125  	data, err := readFile(keyRegistryPath)
   126  	if err != nil {
   127  		log.Fatalf(context.Background(), "could not read %s: %v", keyRegistryPath, err)
   128  	}
   129  
   130  	var reg enginepbccl.DataKeysRegistry
   131  	if err := protoutil.Unmarshal(data, &reg); err != nil {
   132  		log.Fatalf(context.Background(), "could not unmarshal %s: %v", keyRegistryPath, err)
   133  	}
   134  
   135  	log.Infof(context.Background(), "key registry contains %d store keys(s) and %d data key(s)",
   136  		len(reg.StoreKeys), len(reg.DataKeys))
   137  	for _, e := range reg.StoreKeys {
   138  		log.Infof(context.Background(), "  store key: type: %-12s %v", e.EncryptionType, e)
   139  	}
   140  	for _, e := range reg.DataKeys {
   141  		log.Infof(context.Background(), "  data  key: type: %-12s %v", e.Info.EncryptionType, e.Info)
   142  	}
   143  	for k, e := range reg.DataKeys {
   144  		keyRegistry[k] = keyEntry{e.Info.EncryptionType, e.Key}
   145  	}
   146  }
   147  
   148  func loadCurrent() {
   149  	data, err := readFile(currentPath)
   150  	if err != nil {
   151  		log.Fatalf(context.Background(), "could not read %s: %v", currentPath, err)
   152  	}
   153  
   154  	log.Infof(context.Background(), "current: %s", string(data))
   155  }
   156  
   157  func loadOptions() {
   158  	absGlob := filepath.Join(*dbDir, optionsPathGlob)
   159  	paths, err := filepath.Glob(absGlob)
   160  	if err != nil {
   161  		log.Fatalf(context.Background(), "problem finding files matching %s: %v", absGlob, err)
   162  	}
   163  
   164  	for _, f := range paths {
   165  		fname := filepath.Base(f)
   166  		data, err := readFile(fname)
   167  		if err != nil {
   168  			log.Fatalf(context.Background(), "could not read %s: %v", fname, err)
   169  		}
   170  		log.Infof(context.Background(), "options file: %s starts with: %s", fname, string(data[:100]))
   171  	}
   172  }
   173  
   174  func readFile(filename string) ([]byte, error) {
   175  	if len(filename) == 0 {
   176  		return nil, errors.Errorf("filename is empty")
   177  	}
   178  
   179  	absPath := filename
   180  	if filename[0] != '/' {
   181  		absPath = filepath.Join(*dbDir, filename)
   182  	}
   183  
   184  	data, err := ioutil.ReadFile(absPath)
   185  	if err != nil {
   186  		return nil, errors.Errorf("could not read %s: %v", absPath, err)
   187  	}
   188  
   189  	reg, ok := fileRegistry[filename]
   190  	if !ok || reg.settings.EncryptionType == enginepbccl.EncryptionType_Plaintext {
   191  		// Plaintext: do nothing.
   192  		log.Infof(context.Background(), "reading plaintext %s", absPath)
   193  		return data, nil
   194  	}
   195  
   196  	// Encrypted: find the key.
   197  	key, ok := keyRegistry[reg.settings.KeyId]
   198  	if !ok {
   199  		return nil, errors.Errorf("could not find key %s for file %s", reg.settings.KeyId, absPath)
   200  	}
   201  	log.Infof(context.Background(), "decrypting %s with %s key %s...", filename, reg.settings.EncryptionType, reg.settings.KeyId[:8])
   202  
   203  	cipher, err := aes.NewCipher(key.rawKey)
   204  	if err != nil {
   205  		return nil, errors.Errorf("could not build AES cipher for file %s: %v", absPath, err)
   206  	}
   207  
   208  	size := len(data)
   209  	counter := reg.settings.Counter
   210  	nonce := reg.settings.Nonce
   211  	if len(nonce) != 12 {
   212  		log.Fatalf(context.Background(), "nonce has wrong length: %d, expected 12", len(nonce))
   213  	}
   214  
   215  	iv := make([]byte, aes.BlockSize)
   216  	for offset := 0; offset < size; offset += aes.BlockSize {
   217  		// Put nonce at beginning of IV.
   218  		copy(iv, nonce)
   219  		// Write counter to end of IV in network byte order.
   220  		binary.BigEndian.PutUint32(iv[12:], counter)
   221  		// Increment counter for next block.
   222  		counter++
   223  
   224  		// Encrypt IV (AES CTR mode is always encrypt).
   225  		cipher.Encrypt(iv, iv)
   226  
   227  		// XOR data with decrypted IV. We may have a partial block at the end of 'data'.
   228  		for i := 0; i < aes.BlockSize; i++ {
   229  			pos := offset + i
   230  			if pos >= size {
   231  				// Partial block.
   232  				break
   233  			}
   234  			data[pos] = data[pos] ^ iv[i]
   235  		}
   236  	}
   237  
   238  	return data, nil
   239  }
   240  
   241  func main() {
   242  	flag.Parse()
   243  	loadStoreKey()
   244  	loadFileRegistry()
   245  	loadKeyRegistry()
   246  	loadCurrent()
   247  	loadOptions()
   248  }