tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

enctool.cc (14676B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "enctool.h"
      6 #include "argparse.h"
      7 #include "util.h"
      8 
      9 #include "nss.h"
     10 
     11 #include <assert.h>
     12 #include <chrono>
     13 #include <fstream>
     14 #include <iomanip>
     15 #include <iostream>
     16 
     17 void EncTool::PrintError(const std::string& m, size_t line_number) {
     18  std::cerr << m << " - enctool.cc:" << line_number << std::endl;
     19 }
     20 
     21 void EncTool::PrintError(const std::string& m, PRErrorCode err,
     22                         size_t line_number) {
     23  std::cerr << m << " (error " << err << ")"
     24            << " - enctool.cc:" << line_number << std::endl;
     25 }
     26 
     27 void EncTool::PrintBytes(const std::vector<uint8_t>& bytes,
     28                         const std::string& txt) {
     29  if (debug_) {
     30    std::cerr << txt << ": ";
     31    for (uint8_t b : bytes) {
     32      std::cerr << std::setfill('0') << std::setw(2) << std::hex
     33                << static_cast<int>(b);
     34    }
     35    std::cerr << std::endl << std::dec;
     36  }
     37 }
     38 
     39 std::vector<uint8_t> EncTool::GenerateRandomness(size_t num_bytes) {
     40  std::vector<uint8_t> bytes(num_bytes);
     41  if (PK11_GenerateRandom(bytes.data(), num_bytes) != SECSuccess) {
     42    PrintError("No randomness available. Abort!", __LINE__);
     43    exit(1);
     44  }
     45  return bytes;
     46 }
     47 
     48 bool EncTool::WriteBytes(const std::vector<uint8_t>& bytes,
     49                         std::string out_file) {
     50  std::fstream output(out_file, std::ios::out | std::ios::binary);
     51  if (!output.good()) {
     52    return false;
     53  }
     54  output.write(reinterpret_cast<const char*>(
     55                   const_cast<const unsigned char*>(bytes.data())),
     56               bytes.size());
     57  output.flush();
     58  output.close();
     59  return true;
     60 }
     61 
     62 bool EncTool::GetKey(const std::vector<uint8_t>& key_bytes,
     63                     ScopedSECItem& key_item) {
     64  if (key_bytes.empty()) {
     65    return false;
     66  }
     67 
     68  // Build key.
     69  key_item =
     70      ScopedSECItem(SECITEM_AllocItem(nullptr, nullptr, key_bytes.size()));
     71  if (!key_item) {
     72    return false;
     73  }
     74  key_item->type = siBuffer;
     75  memcpy(key_item->data, key_bytes.data(), key_bytes.size());
     76  key_item->len = key_bytes.size();
     77 
     78  return true;
     79 }
     80 
     81 bool EncTool::GetAesGcmKey(const std::vector<uint8_t>& aad,
     82                           const std::vector<uint8_t>& iv_bytes,
     83                           const std::vector<uint8_t>& key_bytes,
     84                           ScopedSECItem& aes_key, ScopedSECItem& params) {
     85  if (iv_bytes.empty()) {
     86    return false;
     87  }
     88 
     89  // GCM params.
     90  CK_NSS_GCM_PARAMS* gcm_params = static_cast<CK_NSS_GCM_PARAMS*>(
     91      PORT_Malloc(sizeof(struct CK_NSS_GCM_PARAMS)));
     92  if (!gcm_params) {
     93    return false;
     94  }
     95 
     96  uint8_t* iv = static_cast<uint8_t*>(PORT_Malloc(iv_bytes.size()));
     97  if (!iv) {
     98    return false;
     99  }
    100  memcpy(iv, iv_bytes.data(), iv_bytes.size());
    101  gcm_params->pIv = iv;
    102  gcm_params->ulIvLen = iv_bytes.size();
    103  gcm_params->ulTagBits = 128;
    104  if (aad.empty()) {
    105    gcm_params->pAAD = nullptr;
    106    gcm_params->ulAADLen = 0;
    107  } else {
    108    uint8_t* ad = static_cast<uint8_t*>(PORT_Malloc(aad.size()));
    109    if (!ad) {
    110      return false;
    111    }
    112    memcpy(ad, aad.data(), aad.size());
    113    gcm_params->pAAD = ad;
    114    gcm_params->ulAADLen = aad.size();
    115  }
    116 
    117  params =
    118      ScopedSECItem(SECITEM_AllocItem(nullptr, nullptr, sizeof(*gcm_params)));
    119  if (!params) {
    120    return false;
    121  }
    122  params->len = sizeof(*gcm_params);
    123  params->type = siBuffer;
    124  params->data = reinterpret_cast<unsigned char*>(gcm_params);
    125 
    126  return GetKey(key_bytes, aes_key);
    127 }
    128 
    129 bool EncTool::GenerateAesGcmKey(const std::vector<uint8_t>& aad,
    130                                ScopedSECItem& aes_key, ScopedSECItem& params) {
    131  size_t key_size = 16, iv_size = 12;
    132  std::vector<uint8_t> iv_bytes = GenerateRandomness(iv_size);
    133  PrintBytes(iv_bytes, "IV");
    134  std::vector<uint8_t> key_bytes = GenerateRandomness(key_size);
    135  PrintBytes(key_bytes, "key");
    136  // Maybe write out the key and parameters.
    137  if (write_key_ && !WriteBytes(key_bytes, key_file_)) {
    138    return false;
    139  }
    140  if (write_iv_ && !WriteBytes(iv_bytes, iv_file_)) {
    141    return false;
    142  }
    143  return GetAesGcmKey(aad, iv_bytes, key_bytes, aes_key, params);
    144 }
    145 
    146 bool EncTool::ReadAesGcmKey(const std::vector<uint8_t>& aad,
    147                            ScopedSECItem& aes_key, ScopedSECItem& params) {
    148  std::vector<uint8_t> iv_bytes = ReadInputData(iv_file_);
    149  PrintBytes(iv_bytes, "IV");
    150  std::vector<uint8_t> key_bytes = ReadInputData(key_file_);
    151  PrintBytes(key_bytes, "key");
    152  return GetAesGcmKey(aad, iv_bytes, key_bytes, aes_key, params);
    153 }
    154 
    155 bool EncTool::GetChachaKey(const std::vector<uint8_t>& aad,
    156                           const std::vector<uint8_t>& iv_bytes,
    157                           const std::vector<uint8_t>& key_bytes,
    158                           ScopedSECItem& chacha_key, ScopedSECItem& params) {
    159  if (iv_bytes.empty()) {
    160    return false;
    161  }
    162 
    163  // AEAD params.
    164  CK_NSS_AEAD_PARAMS* aead_params = static_cast<CK_NSS_AEAD_PARAMS*>(
    165      PORT_Malloc(sizeof(struct CK_NSS_AEAD_PARAMS)));
    166  if (!aead_params) {
    167    return false;
    168  }
    169 
    170  uint8_t* iv = static_cast<uint8_t*>(PORT_Malloc(iv_bytes.size()));
    171  if (!iv) {
    172    return false;
    173  }
    174  memcpy(iv, iv_bytes.data(), iv_bytes.size());
    175  aead_params->pNonce = iv;
    176  aead_params->ulNonceLen = iv_bytes.size();
    177  aead_params->ulTagLen = 16;
    178  if (aad.empty()) {
    179    aead_params->pAAD = nullptr;
    180    aead_params->ulAADLen = 0;
    181  } else {
    182    uint8_t* ad = static_cast<uint8_t*>(PORT_Malloc(aad.size()));
    183    if (!ad) {
    184      return false;
    185    }
    186    memcpy(ad, aad.data(), aad.size());
    187    aead_params->pAAD = ad;
    188    aead_params->ulAADLen = aad.size();
    189  }
    190 
    191  params =
    192      ScopedSECItem(SECITEM_AllocItem(nullptr, nullptr, sizeof(*aead_params)));
    193  if (!params) {
    194    return false;
    195  }
    196  params->len = sizeof(*aead_params);
    197  params->type = siBuffer;
    198  params->data = reinterpret_cast<unsigned char*>(aead_params);
    199 
    200  return GetKey(key_bytes, chacha_key);
    201 }
    202 
    203 bool EncTool::GenerateChachaKey(const std::vector<uint8_t>& aad,
    204                                ScopedSECItem& chacha_key,
    205                                ScopedSECItem& params) {
    206  size_t key_size = 32, iv_size = 12;
    207  std::vector<uint8_t> iv_bytes = GenerateRandomness(iv_size);
    208  PrintBytes(iv_bytes, "IV");
    209  std::vector<uint8_t> key_bytes = GenerateRandomness(key_size);
    210  PrintBytes(key_bytes, "key");
    211  // Maybe write out the key and parameters.
    212  if (write_key_ && !WriteBytes(key_bytes, key_file_)) {
    213    return false;
    214  }
    215  if (write_iv_ && !WriteBytes(iv_bytes, iv_file_)) {
    216    return false;
    217  }
    218  return GetChachaKey(aad, iv_bytes, key_bytes, chacha_key, params);
    219 }
    220 
    221 bool EncTool::ReadChachaKey(const std::vector<uint8_t>& aad,
    222                            ScopedSECItem& chacha_key, ScopedSECItem& params) {
    223  std::vector<uint8_t> iv_bytes = ReadInputData(iv_file_);
    224  PrintBytes(iv_bytes, "IV");
    225  std::vector<uint8_t> key_bytes = ReadInputData(key_file_);
    226  PrintBytes(key_bytes, "key");
    227  return GetChachaKey(aad, iv_bytes, key_bytes, chacha_key, params);
    228 }
    229 
    230 bool EncTool::DoCipher(std::string file_name, std::string out_file,
    231                       bool encrypt, key_func_t get_params) {
    232  SECStatus rv;
    233  unsigned int outLen = 0, chunkSize = 1024;
    234  char buffer[1040];
    235  const unsigned char* bufferStart =
    236      reinterpret_cast<const unsigned char*>(buffer);
    237 
    238  ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
    239  if (!slot) {
    240    PrintError("Unable to find security device", PR_GetError(), __LINE__);
    241    return false;
    242  }
    243 
    244  ScopedSECItem key, params;
    245  if (!(this->*get_params)(std::vector<uint8_t>(), key, params)) {
    246    PrintError("Geting keys and params failed.", __LINE__);
    247    return false;
    248  }
    249 
    250  ScopedPK11SymKey symKey(
    251      PK11_ImportSymKey(slot.get(), cipher_mech_, PK11_OriginUnwrap,
    252                        CKA_DECRYPT | CKA_ENCRYPT, key.get(), nullptr));
    253  if (!symKey) {
    254    PrintError("Failure to import key into NSS", PR_GetError(), __LINE__);
    255    return false;
    256  }
    257 
    258  std::streambuf* buf;
    259  std::ofstream output_file(out_file, std::ios::out | std::ios::binary);
    260  if (!out_file.empty()) {
    261    if (!output_file.good()) {
    262      return false;
    263    }
    264    buf = output_file.rdbuf();
    265  } else {
    266    buf = std::cout.rdbuf();
    267  }
    268  std::ostream output(buf);
    269 
    270  // Read from stdin.
    271  if (file_name.empty()) {
    272    std::vector<uint8_t> data = ReadInputData("");
    273    std::vector<uint8_t> out(data.size() + 16);
    274    if (encrypt) {
    275      rv = PK11_Encrypt(symKey.get(), cipher_mech_, params.get(), out.data(),
    276                        &outLen, data.size() + 16, data.data(), data.size());
    277    } else {
    278      rv = PK11_Decrypt(symKey.get(), cipher_mech_, params.get(), out.data(),
    279                        &outLen, data.size() + 16, data.data(), data.size());
    280    }
    281    if (rv != SECSuccess) {
    282      PrintError(encrypt ? "Error encrypting" : "Error decrypting",
    283                 PR_GetError(), __LINE__);
    284      return false;
    285    };
    286    output.write(reinterpret_cast<char*>(out.data()), outLen);
    287    output.flush();
    288    if (output_file.good()) {
    289      output_file.close();
    290    } else {
    291      output << std::endl;
    292    }
    293 
    294    std::cerr << "Done " << (encrypt ? "encrypting" : "decrypting")
    295              << std::endl;
    296    return true;
    297  }
    298 
    299  // Read file from file_name.
    300  std::ifstream input(file_name, std::ios::binary);
    301  if (!input.good()) {
    302    return false;
    303  }
    304  uint8_t out[1040];
    305  while (input) {
    306    if (encrypt) {
    307      input.read(buffer, chunkSize);
    308      rv = PK11_Encrypt(symKey.get(), cipher_mech_, params.get(), out, &outLen,
    309                        chunkSize + 16, bufferStart, input.gcount());
    310    } else {
    311      // We have to read the tag when decrypting.
    312      input.read(buffer, chunkSize + 16);
    313      rv = PK11_Decrypt(symKey.get(), cipher_mech_, params.get(), out, &outLen,
    314                        chunkSize + 16, bufferStart, input.gcount());
    315    }
    316    if (rv != SECSuccess) {
    317      PrintError(encrypt ? "Error encrypting" : "Error decrypting",
    318                 PR_GetError(), __LINE__);
    319      return false;
    320    };
    321    output.write(reinterpret_cast<const char*>(out), outLen);
    322    output.flush();
    323  }
    324  if (output_file.good()) {
    325    output_file.close();
    326  } else {
    327    output << std::endl;
    328  }
    329  std::cerr << "Done " << (encrypt ? "encrypting" : "decrypting") << std::endl;
    330 
    331  return true;
    332 }
    333 
    334 size_t EncTool::PrintFileSize(std::string file_name) {
    335  std::ifstream input(file_name, std::ifstream::ate | std::ifstream::binary);
    336  auto size = input.tellg();
    337  std::cerr << "Size of file to encrypt: " << size / 1024 / 1024 << " MB"
    338            << std::endl;
    339  return size;
    340 }
    341 
    342 bool EncTool::IsValidCommand(ArgParser arguments) {
    343  // Either encrypt or decrypt is fine.
    344  bool valid = arguments.Has("--encrypt") != arguments.Has("--decrypt");
    345  // An input file is required for decryption only.
    346  valid &= arguments.Has("--in") || arguments.Has("--encrypt");
    347  // An output file is required for encryption only.
    348  valid &= arguments.Has("--out") || arguments.Has("--decrypt");
    349  // Files holding the IV and key are required for decryption.
    350  valid &= arguments.Has("--iv") || arguments.Has("--encrypt");
    351  valid &= arguments.Has("--key") || arguments.Has("--encrypt");
    352  // Cipher is always required.
    353  valid &= arguments.Has("--cipher");
    354  return valid;
    355 }
    356 
    357 bool EncTool::Run(const std::vector<std::string>& arguments) {
    358  ArgParser parser(arguments);
    359 
    360  if (!IsValidCommand(parser)) {
    361    Usage();
    362    return false;
    363  }
    364 
    365  if (NSS_NoDB_Init(nullptr) != SECSuccess) {
    366    PrintError("NSS initialization failed", PR_GetError(), __LINE__);
    367    return false;
    368  }
    369 
    370  if (parser.Has("--debug")) {
    371    debug_ = 1;
    372  }
    373  if (parser.Has("--iv")) {
    374    iv_file_ = parser.Get("--iv");
    375  } else {
    376    write_iv_ = false;
    377  }
    378  if (parser.Has("--key")) {
    379    key_file_ = parser.Get("--key");
    380  } else {
    381    write_key_ = false;
    382  }
    383 
    384  key_func_t get_params;
    385  bool encrypt = parser.Has("--encrypt");
    386  if (parser.Get("--cipher") == kAESCommand) {
    387    cipher_mech_ = CKM_AES_GCM;
    388    if (encrypt) {
    389      get_params = &EncTool::GenerateAesGcmKey;
    390    } else {
    391      get_params = &EncTool::ReadAesGcmKey;
    392    }
    393  } else if (parser.Get("--cipher") == kChaChaCommand) {
    394    cipher_mech_ = CKM_NSS_CHACHA20_POLY1305;
    395    if (encrypt) {
    396      get_params = &EncTool::GenerateChachaKey;
    397    } else {
    398      get_params = &EncTool::ReadChachaKey;
    399    }
    400  } else {
    401    Usage();
    402    return false;
    403  }
    404  // Don't write out key and iv when decrypting.
    405  if (!encrypt) {
    406    write_key_ = false;
    407    write_iv_ = false;
    408  }
    409 
    410  std::string input_file = parser.Has("--in") ? parser.Get("--in") : "";
    411  std::string output_file = parser.Has("--out") ? parser.Get("--out") : "";
    412  size_t file_size = 0;
    413  if (!input_file.empty()) {
    414    file_size = PrintFileSize(input_file);
    415  }
    416  auto begin = std::chrono::high_resolution_clock::now();
    417  if (!DoCipher(input_file, output_file, encrypt, get_params)) {
    418    (void)NSS_Shutdown();
    419    return false;
    420  }
    421  auto end = std::chrono::high_resolution_clock::now();
    422  auto ns =
    423      std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count();
    424  auto seconds = ns / 1000000000;
    425  std::cerr << ns << " ns (~" << seconds << " s) and " << std::endl;
    426  std::cerr << "That's approximately " << (double)file_size / ns << " b/ns"
    427            << std::endl;
    428 
    429  if (NSS_Shutdown() != SECSuccess) {
    430    return false;
    431  }
    432 
    433  return true;
    434 }
    435 
    436 void EncTool::Usage() {
    437  std::string const txt = R"~(
    438 Usage: nss encrypt|decrypt --cipher aes|chacha [--in <file>] [--out <file>]
    439           [--key <file>] [--iv <file>]
    440 
    441    --cipher         Set the cipher to use.
    442                     --cipher aes:    Use AES-GCM to encrypt/decrypt.
    443                     --cipher chacha: Use ChaCha20/Poly1305 to encrypt/decrypt.
    444    --in             The file to encrypt/decrypt. If no file is given, we read
    445                     from stdin (only when encrypting).
    446    --out            The file to write the ciphertext/plaintext to. If no file
    447                     is given we write the plaintext to stdout (only when
    448                     decrypting).
    449    --key            The file to write the used key to/to read the key
    450                     from. Optional parameter. When not given, don't write out
    451                     the key.
    452    --iv             The file to write the used IV to/to read the IV
    453                     from. Optional parameter. When not given, don't write out
    454                     the IV.
    455 
    456    Examples:
    457        nss encrypt --cipher aes --iv iv --key key --out ciphertext
    458        nss decrypt --cipher chacha --iv iv --key key --in ciphertex
    459 
    460    Note: This tool overrides files without asking.
    461 )~";
    462  std::cerr << txt << std::endl;
    463 }