| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| #include "enctool.h" |
| #include "argparse.h" |
| #include "util.h" |
| |
| #include "nss.h" |
| |
| #include <assert.h> |
| #include <chrono> |
| #include <fstream> |
| #include <iomanip> |
| #include <iostream> |
| |
| void EncTool::PrintError(const std::string& m, size_t line_number) { |
| std::cerr << m << " - enctool.cc:" << line_number << std::endl; |
| } |
| |
| void EncTool::PrintError(const std::string& m, PRErrorCode err, |
| size_t line_number) { |
| std::cerr << m << " (error " << err << ")" |
| << " - enctool.cc:" << line_number << std::endl; |
| } |
| |
| void EncTool::PrintBytes(const std::vector<uint8_t>& bytes, |
| const std::string& txt) { |
| if (debug_) { |
| std::cerr << txt << ": "; |
| for (uint8_t b : bytes) { |
| std::cerr << std::setfill('0') << std::setw(2) << std::hex |
| << static_cast<int>(b); |
| } |
| std::cerr << std::endl << std::dec; |
| } |
| } |
| |
| std::vector<uint8_t> EncTool::GenerateRandomness(size_t num_bytes) { |
| std::vector<uint8_t> bytes(num_bytes); |
| if (PK11_GenerateRandom(bytes.data(), num_bytes) != SECSuccess) { |
| PrintError("No randomness available. Abort!", __LINE__); |
| exit(1); |
| } |
| return bytes; |
| } |
| |
| bool EncTool::WriteBytes(const std::vector<uint8_t>& bytes, |
| std::string out_file) { |
| std::fstream output(out_file, std::ios::out | std::ios::binary); |
| if (!output.good()) { |
| return false; |
| } |
| output.write(reinterpret_cast<const char*>( |
| const_cast<const unsigned char*>(bytes.data())), |
| bytes.size()); |
| output.flush(); |
| output.close(); |
| return true; |
| } |
| |
| bool EncTool::GetKey(const std::vector<uint8_t>& key_bytes, |
| ScopedSECItem& key_item) { |
| if (key_bytes.empty()) { |
| return false; |
| } |
| |
| // Build key. |
| key_item = |
| ScopedSECItem(SECITEM_AllocItem(nullptr, nullptr, key_bytes.size())); |
| if (!key_item) { |
| return false; |
| } |
| key_item->type = siBuffer; |
| memcpy(key_item->data, key_bytes.data(), key_bytes.size()); |
| key_item->len = key_bytes.size(); |
| |
| return true; |
| } |
| |
| bool EncTool::GetAesGcmKey(const std::vector<uint8_t>& aad, |
| const std::vector<uint8_t>& iv_bytes, |
| const std::vector<uint8_t>& key_bytes, |
| ScopedSECItem& aes_key, ScopedSECItem& params) { |
| if (iv_bytes.empty()) { |
| return false; |
| } |
| |
| // GCM params. |
| CK_GCM_PARAMS* gcm_params = |
| static_cast<CK_GCM_PARAMS*>(PORT_Malloc(sizeof(struct CK_GCM_PARAMS))); |
| if (!gcm_params) { |
| return false; |
| } |
| |
| uint8_t* iv = static_cast<uint8_t*>(PORT_Malloc(iv_bytes.size())); |
| if (!iv) { |
| return false; |
| } |
| memcpy(iv, iv_bytes.data(), iv_bytes.size()); |
| gcm_params->pIv = iv; |
| gcm_params->ulIvLen = iv_bytes.size(); |
| gcm_params->ulTagBits = 128; |
| if (aad.empty()) { |
| gcm_params->pAAD = nullptr; |
| gcm_params->ulAADLen = 0; |
| } else { |
| uint8_t* ad = static_cast<uint8_t*>(PORT_Malloc(aad.size())); |
| if (!ad) { |
| return false; |
| } |
| memcpy(ad, aad.data(), aad.size()); |
| gcm_params->pAAD = ad; |
| gcm_params->ulAADLen = aad.size(); |
| } |
| |
| params = |
| ScopedSECItem(SECITEM_AllocItem(nullptr, nullptr, sizeof(*gcm_params))); |
| if (!params) { |
| return false; |
| } |
| params->len = sizeof(*gcm_params); |
| params->type = siBuffer; |
| params->data = reinterpret_cast<unsigned char*>(gcm_params); |
| |
| return GetKey(key_bytes, aes_key); |
| } |
| |
| bool EncTool::GenerateAesGcmKey(const std::vector<uint8_t>& aad, |
| ScopedSECItem& aes_key, ScopedSECItem& params) { |
| size_t key_size = 16, iv_size = 12; |
| std::vector<uint8_t> iv_bytes = GenerateRandomness(iv_size); |
| PrintBytes(iv_bytes, "IV"); |
| std::vector<uint8_t> key_bytes = GenerateRandomness(key_size); |
| PrintBytes(key_bytes, "key"); |
| // Maybe write out the key and parameters. |
| if (write_key_ && !WriteBytes(key_bytes, key_file_)) { |
| return false; |
| } |
| if (write_iv_ && !WriteBytes(iv_bytes, iv_file_)) { |
| return false; |
| } |
| return GetAesGcmKey(aad, iv_bytes, key_bytes, aes_key, params); |
| } |
| |
| bool EncTool::ReadAesGcmKey(const std::vector<uint8_t>& aad, |
| ScopedSECItem& aes_key, ScopedSECItem& params) { |
| std::vector<uint8_t> iv_bytes = ReadInputData(iv_file_); |
| PrintBytes(iv_bytes, "IV"); |
| std::vector<uint8_t> key_bytes = ReadInputData(key_file_); |
| PrintBytes(key_bytes, "key"); |
| return GetAesGcmKey(aad, iv_bytes, key_bytes, aes_key, params); |
| } |
| |
| bool EncTool::GetChachaKey(const std::vector<uint8_t>& aad, |
| const std::vector<uint8_t>& iv_bytes, |
| const std::vector<uint8_t>& key_bytes, |
| ScopedSECItem& chacha_key, ScopedSECItem& params) { |
| if (iv_bytes.empty()) { |
| return false; |
| } |
| |
| // AEAD params. |
| CK_NSS_AEAD_PARAMS* aead_params = static_cast<CK_NSS_AEAD_PARAMS*>( |
| PORT_Malloc(sizeof(struct CK_NSS_AEAD_PARAMS))); |
| if (!aead_params) { |
| return false; |
| } |
| |
| uint8_t* iv = static_cast<uint8_t*>(PORT_Malloc(iv_bytes.size())); |
| if (!iv) { |
| return false; |
| } |
| memcpy(iv, iv_bytes.data(), iv_bytes.size()); |
| aead_params->pNonce = iv; |
| aead_params->ulNonceLen = iv_bytes.size(); |
| aead_params->ulTagLen = 16; |
| if (aad.empty()) { |
| aead_params->pAAD = nullptr; |
| aead_params->ulAADLen = 0; |
| } else { |
| uint8_t* ad = static_cast<uint8_t*>(PORT_Malloc(aad.size())); |
| if (!ad) { |
| return false; |
| } |
| memcpy(ad, aad.data(), aad.size()); |
| aead_params->pAAD = ad; |
| aead_params->ulAADLen = aad.size(); |
| } |
| |
| params = |
| ScopedSECItem(SECITEM_AllocItem(nullptr, nullptr, sizeof(*aead_params))); |
| if (!params) { |
| return false; |
| } |
| params->len = sizeof(*aead_params); |
| params->type = siBuffer; |
| params->data = reinterpret_cast<unsigned char*>(aead_params); |
| |
| return GetKey(key_bytes, chacha_key); |
| } |
| |
| bool EncTool::GenerateChachaKey(const std::vector<uint8_t>& aad, |
| ScopedSECItem& chacha_key, |
| ScopedSECItem& params) { |
| size_t key_size = 32, iv_size = 12; |
| std::vector<uint8_t> iv_bytes = GenerateRandomness(iv_size); |
| PrintBytes(iv_bytes, "IV"); |
| std::vector<uint8_t> key_bytes = GenerateRandomness(key_size); |
| PrintBytes(key_bytes, "key"); |
| // Maybe write out the key and parameters. |
| if (write_key_ && !WriteBytes(key_bytes, key_file_)) { |
| return false; |
| } |
| if (write_iv_ && !WriteBytes(iv_bytes, iv_file_)) { |
| return false; |
| } |
| return GetChachaKey(aad, iv_bytes, key_bytes, chacha_key, params); |
| } |
| |
| bool EncTool::ReadChachaKey(const std::vector<uint8_t>& aad, |
| ScopedSECItem& chacha_key, ScopedSECItem& params) { |
| std::vector<uint8_t> iv_bytes = ReadInputData(iv_file_); |
| PrintBytes(iv_bytes, "IV"); |
| std::vector<uint8_t> key_bytes = ReadInputData(key_file_); |
| PrintBytes(key_bytes, "key"); |
| return GetChachaKey(aad, iv_bytes, key_bytes, chacha_key, params); |
| } |
| |
| bool EncTool::DoCipher(std::string file_name, std::string out_file, |
| bool encrypt, key_func_t get_params) { |
| SECStatus rv; |
| unsigned int outLen = 0, chunkSize = 1024; |
| char buffer[1040]; |
| const unsigned char* bufferStart = |
| reinterpret_cast<const unsigned char*>(buffer); |
| |
| ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); |
| if (!slot) { |
| PrintError("Unable to find security device", PR_GetError(), __LINE__); |
| return false; |
| } |
| |
| ScopedSECItem key, params; |
| if (!(this->*get_params)(std::vector<uint8_t>(), key, params)) { |
| PrintError("Geting keys and params failed.", __LINE__); |
| return false; |
| } |
| |
| ScopedPK11SymKey symKey( |
| PK11_ImportSymKey(slot.get(), cipher_mech_, PK11_OriginUnwrap, |
| CKA_DECRYPT | CKA_ENCRYPT, key.get(), nullptr)); |
| if (!symKey) { |
| PrintError("Failure to import key into NSS", PR_GetError(), __LINE__); |
| return false; |
| } |
| |
| std::streambuf* buf; |
| std::ofstream output_file(out_file, std::ios::out | std::ios::binary); |
| if (!out_file.empty()) { |
| if (!output_file.good()) { |
| return false; |
| } |
| buf = output_file.rdbuf(); |
| } else { |
| buf = std::cout.rdbuf(); |
| } |
| std::ostream output(buf); |
| |
| // Read from stdin. |
| if (file_name.empty()) { |
| std::vector<uint8_t> data = ReadInputData(""); |
| std::vector<uint8_t> out(data.size() + 16); |
| if (encrypt) { |
| rv = PK11_Encrypt(symKey.get(), cipher_mech_, params.get(), out.data(), |
| &outLen, data.size() + 16, data.data(), data.size()); |
| } else { |
| rv = PK11_Decrypt(symKey.get(), cipher_mech_, params.get(), out.data(), |
| &outLen, data.size() + 16, data.data(), data.size()); |
| } |
| if (rv != SECSuccess) { |
| PrintError(encrypt ? "Error encrypting" : "Error decrypting", |
| PR_GetError(), __LINE__); |
| return false; |
| }; |
| output.write(reinterpret_cast<char*>(out.data()), outLen); |
| output.flush(); |
| if (output_file.good()) { |
| output_file.close(); |
| } else { |
| output << std::endl; |
| } |
| |
| std::cerr << "Done " << (encrypt ? "encrypting" : "decrypting") |
| << std::endl; |
| return true; |
| } |
| |
| // Read file from file_name. |
| std::ifstream input(file_name, std::ios::binary); |
| if (!input.good()) { |
| return false; |
| } |
| uint8_t out[1040]; |
| while (input) { |
| if (encrypt) { |
| input.read(buffer, chunkSize); |
| rv = PK11_Encrypt(symKey.get(), cipher_mech_, params.get(), out, &outLen, |
| chunkSize + 16, bufferStart, input.gcount()); |
| } else { |
| // We have to read the tag when decrypting. |
| input.read(buffer, chunkSize + 16); |
| rv = PK11_Decrypt(symKey.get(), cipher_mech_, params.get(), out, &outLen, |
| chunkSize + 16, bufferStart, input.gcount()); |
| } |
| if (rv != SECSuccess) { |
| PrintError(encrypt ? "Error encrypting" : "Error decrypting", |
| PR_GetError(), __LINE__); |
| return false; |
| }; |
| output.write(reinterpret_cast<const char*>(out), outLen); |
| output.flush(); |
| } |
| if (output_file.good()) { |
| output_file.close(); |
| } else { |
| output << std::endl; |
| } |
| std::cerr << "Done " << (encrypt ? "encrypting" : "decrypting") << std::endl; |
| |
| return true; |
| } |
| |
| size_t EncTool::PrintFileSize(std::string file_name) { |
| std::ifstream input(file_name, std::ifstream::ate | std::ifstream::binary); |
| auto size = input.tellg(); |
| std::cerr << "Size of file to encrypt: " << size / 1024 / 1024 << " MB" |
| << std::endl; |
| return size; |
| } |
| |
| bool EncTool::IsValidCommand(ArgParser arguments) { |
| // Either encrypt or decrypt is fine. |
| bool valid = arguments.Has("--encrypt") != arguments.Has("--decrypt"); |
| // An input file is required for decryption only. |
| valid &= arguments.Has("--in") || arguments.Has("--encrypt"); |
| // An output file is required for encryption only. |
| valid &= arguments.Has("--out") || arguments.Has("--decrypt"); |
| // Files holding the IV and key are required for decryption. |
| valid &= arguments.Has("--iv") || arguments.Has("--encrypt"); |
| valid &= arguments.Has("--key") || arguments.Has("--encrypt"); |
| // Cipher is always required. |
| valid &= arguments.Has("--cipher"); |
| return valid; |
| } |
| |
| bool EncTool::Run(const std::vector<std::string>& arguments) { |
| ArgParser parser(arguments); |
| |
| if (!IsValidCommand(parser)) { |
| Usage(); |
| return false; |
| } |
| |
| if (NSS_NoDB_Init(nullptr) != SECSuccess) { |
| PrintError("NSS initialization failed", PR_GetError(), __LINE__); |
| return false; |
| } |
| |
| if (parser.Has("--debug")) { |
| debug_ = 1; |
| } |
| if (parser.Has("--iv")) { |
| iv_file_ = parser.Get("--iv"); |
| } else { |
| write_iv_ = false; |
| } |
| if (parser.Has("--key")) { |
| key_file_ = parser.Get("--key"); |
| } else { |
| write_key_ = false; |
| } |
| |
| key_func_t get_params; |
| bool encrypt = parser.Has("--encrypt"); |
| if (parser.Get("--cipher") == kAESCommand) { |
| cipher_mech_ = CKM_AES_GCM; |
| if (encrypt) { |
| get_params = &EncTool::GenerateAesGcmKey; |
| } else { |
| get_params = &EncTool::ReadAesGcmKey; |
| } |
| } else if (parser.Get("--cipher") == kChaChaCommand) { |
| cipher_mech_ = CKM_NSS_CHACHA20_POLY1305; |
| if (encrypt) { |
| get_params = &EncTool::GenerateChachaKey; |
| } else { |
| get_params = &EncTool::ReadChachaKey; |
| } |
| } else { |
| Usage(); |
| return false; |
| } |
| // Don't write out key and iv when decrypting. |
| if (!encrypt) { |
| write_key_ = false; |
| write_iv_ = false; |
| } |
| |
| std::string input_file = parser.Has("--in") ? parser.Get("--in") : ""; |
| std::string output_file = parser.Has("--out") ? parser.Get("--out") : ""; |
| size_t file_size = 0; |
| if (!input_file.empty()) { |
| file_size = PrintFileSize(input_file); |
| } |
| auto begin = std::chrono::high_resolution_clock::now(); |
| if (!DoCipher(input_file, output_file, encrypt, get_params)) { |
| (void)NSS_Shutdown(); |
| return false; |
| } |
| auto end = std::chrono::high_resolution_clock::now(); |
| auto ns = |
| std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count(); |
| auto seconds = ns / 1000000000; |
| std::cerr << ns << " ns (~" << seconds << " s) and " << std::endl; |
| std::cerr << "That's approximately " << (double)file_size / ns << " b/ns" |
| << std::endl; |
| |
| if (NSS_Shutdown() != SECSuccess) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void EncTool::Usage() { |
| std::string const txt = R"~( |
| Usage: nss encrypt|decrypt --cipher aes|chacha [--in <file>] [--out <file>] |
| [--key <file>] [--iv <file>] |
| |
| --cipher Set the cipher to use. |
| --cipher aes: Use AES-GCM to encrypt/decrypt. |
| --cipher chacha: Use ChaCha20/Poly1305 to encrypt/decrypt. |
| --in The file to encrypt/decrypt. If no file is given, we read |
| from stdin (only when encrypting). |
| --out The file to write the ciphertext/plaintext to. If no file |
| is given we write the plaintext to stdout (only when |
| decrypting). |
| --key The file to write the used key to/to read the key |
| from. Optional parameter. When not given, don't write out |
| the key. |
| --iv The file to write the used IV to/to read the IV |
| from. Optional parameter. When not given, don't write out |
| the IV. |
| |
| Examples: |
| nss encrypt --cipher aes --iv iv --key key --out ciphertext |
| nss decrypt --cipher chacha --iv iv --key key --in ciphertex |
| |
| Note: This tool overrides files without asking. |
| )~"; |
| std::cerr << txt << std::endl; |
| } |