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 }