diff options
Diffstat (limited to 'security/nss/nss-tool')
-rw-r--r-- | security/nss/nss-tool/.clang-format | 4 | ||||
-rw-r--r-- | security/nss/nss-tool/common/argparse.cc | 23 | ||||
-rw-r--r-- | security/nss/nss-tool/common/argparse.h | 30 | ||||
-rw-r--r-- | security/nss/nss-tool/common/tool.h | 20 | ||||
-rw-r--r-- | security/nss/nss-tool/common/util.cc | 216 | ||||
-rw-r--r-- | security/nss/nss-tool/common/util.h | 32 | ||||
-rw-r--r-- | security/nss/nss-tool/db/dbtool.cc | 497 | ||||
-rw-r--r-- | security/nss/nss-tool/db/dbtool.h | 28 | ||||
-rw-r--r-- | security/nss/nss-tool/digest/digesttool.cc | 161 | ||||
-rw-r--r-- | security/nss/nss-tool/digest/digesttool.h | 20 | ||||
-rw-r--r-- | security/nss/nss-tool/enc/enctool.cc | 464 | ||||
-rw-r--r-- | security/nss/nss-tool/enc/enctool.h | 62 | ||||
-rw-r--r-- | security/nss/nss-tool/nss_tool.cc | 70 | ||||
-rw-r--r-- | security/nss/nss-tool/nss_tool.gyp | 31 |
14 files changed, 1658 insertions, 0 deletions
diff --git a/security/nss/nss-tool/.clang-format b/security/nss/nss-tool/.clang-format new file mode 100644 index 000000000..06e3c5115 --- /dev/null +++ b/security/nss/nss-tool/.clang-format @@ -0,0 +1,4 @@ +--- +Language: Cpp +BasedOnStyle: Google +... diff --git a/security/nss/nss-tool/common/argparse.cc b/security/nss/nss-tool/common/argparse.cc new file mode 100644 index 000000000..3b7c73891 --- /dev/null +++ b/security/nss/nss-tool/common/argparse.cc @@ -0,0 +1,23 @@ +/* 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 "argparse.h" + +ArgParser::ArgParser(const std::vector<std::string>& arguments) { + for (size_t i = 0; i < arguments.size(); i++) { + std::string arg = arguments.at(i); + if (arg.find("--") == 0) { + // look for an option argument + if (i + 1 < arguments.size() && arguments.at(i + 1).find("--") != 0) { + programArgs_[arg] = arguments.at(i + 1); + i++; + } else { + programArgs_[arg] = ""; + } + } else { + // positional argument (e.g. required argument) + positionalArgs_.push_back(arg); + } + } +} diff --git a/security/nss/nss-tool/common/argparse.h b/security/nss/nss-tool/common/argparse.h new file mode 100644 index 000000000..8645d5aaa --- /dev/null +++ b/security/nss/nss-tool/common/argparse.h @@ -0,0 +1,30 @@ +/* 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/. */ + +#ifndef argparse_h__ +#define argparse_h__ + +#include <string> +#include <unordered_map> +#include <vector> + +class ArgParser { + public: + ArgParser(const std::vector<std::string>& arguments); + + bool Has(std::string arg) const { return programArgs_.count(arg) > 0; } + + std::string Get(std::string arg) const { return programArgs_.at(arg); } + + size_t GetPositionalArgumentCount() const { return positionalArgs_.size(); } + std::string GetPositionalArgument(size_t pos) const { + return positionalArgs_.at(pos); + } + + private: + std::unordered_map<std::string, std::string> programArgs_; + std::vector<std::string> positionalArgs_; +}; + +#endif // argparse_h__ diff --git a/security/nss/nss-tool/common/tool.h b/security/nss/nss-tool/common/tool.h new file mode 100644 index 000000000..17ebcac29 --- /dev/null +++ b/security/nss/nss-tool/common/tool.h @@ -0,0 +1,20 @@ +/* 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/. */ + +#ifndef tool_h__ +#define tool_h__ + +#include <string> +#include <vector> + +class Tool { + public: + virtual bool Run(const std::vector<std::string>& arguments) = 0; + virtual ~Tool() {} + + private: + virtual void Usage() = 0; +}; + +#endif // tool_h__ diff --git a/security/nss/nss-tool/common/util.cc b/security/nss/nss-tool/common/util.cc new file mode 100644 index 000000000..77459155a --- /dev/null +++ b/security/nss/nss-tool/common/util.cc @@ -0,0 +1,216 @@ +/* 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 "util.h" + +#include <fstream> +#include <iomanip> +#include <iostream> +#include <sstream> +#include <string> + +#include <prerror.h> + +#if defined(__unix__) || defined(__APPLE__) +#include <termios.h> +#include <unistd.h> +#elif defined(WIN32) || defined(_WIN64) +#include <Windows.h> +#endif + +static std::string GetPassword(const std::string &prompt) { + std::cout << prompt << std::endl; + +#if defined(__unix__) || defined(__APPLE__) + termios oldt; + tcgetattr(STDIN_FILENO, &oldt); + termios newt = oldt; + newt.c_lflag &= ~ECHO; + tcsetattr(STDIN_FILENO, TCSANOW, &newt); +#elif defined(WIN32) || defined(_WIN64) + HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); + DWORD mode = 0; + GetConsoleMode(hStdin, &mode); + SetConsoleMode(hStdin, mode & (~ENABLE_ECHO_INPUT)); +#endif + + std::string pw; + std::getline(std::cin, pw); + +#if defined(__unix__) || defined(__APPLE__) + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); +#elif defined(WIN32) || defined(_WIN64) + SetConsoleMode(hStdin, mode); +#endif + + return pw; +} + +static char *GetModulePassword(PK11SlotInfo *slot, int retry, void *arg) { + if (arg == nullptr) { + return nullptr; + } + + PwData *pwData = reinterpret_cast<PwData *>(arg); + + if (retry > 0) { + std::cerr << "Incorrect password/PIN entered." << std::endl; + return nullptr; + } + + switch (pwData->source) { + case PW_NONE: + case PW_FROMFILE: + std::cerr << "Password input method not supported." << std::endl; + return nullptr; + case PW_PLAINTEXT: + return PL_strdup(pwData->data); + default: + break; + } + + std::cerr << "Password check failed: No password found." << std::endl; + return nullptr; +} + +static std::vector<uint8_t> ReadFromIstream(std::istream &is) { + std::vector<uint8_t> data; + while (is) { + char buf[1024]; + is.read(buf, sizeof(buf)); + data.insert(data.end(), buf, buf + is.gcount()); + } + + return data; +} + +static std::string GetNewPasswordFromUser(void) { + std::string pw; + + while (true) { + pw = GetPassword("Enter new password: "); + if (pw == GetPassword("Re-enter password: ")) { + break; + } + + std::cerr << "Passwords do not match. Try again." << std::endl; + } + + return pw; +} + +bool InitSlotPassword(void) { + ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot()); + if (slot.get() == nullptr) { + std::cerr << "Error: Init PK11SlotInfo failed!" << std::endl; + return false; + } + + std::cout << "Enter a password which will be used to encrypt your keys." + << std::endl + << std::endl; + std::string pw = GetNewPasswordFromUser(); + + SECStatus rv = PK11_InitPin(slot.get(), nullptr, pw.c_str()); + if (rv != SECSuccess) { + std::cerr << "Init db password failed." << std::endl; + return false; + } + + return true; +} + +bool ChangeSlotPassword(void) { + ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot()); + if (slot.get() == nullptr) { + std::cerr << "Error: Init PK11SlotInfo failed!" << std::endl; + return false; + } + + // get old password and authenticate to db + PK11_SetPasswordFunc(&GetModulePassword); + std::string oldPw = GetPassword("Enter your current password: "); + PwData pwData = {PW_PLAINTEXT, const_cast<char *>(oldPw.c_str())}; + SECStatus rv = PK11_Authenticate(slot.get(), false /*loadCerts*/, &pwData); + if (rv != SECSuccess) { + std::cerr << "Password incorrect." << std::endl; + return false; + } + + // get new password + std::string newPw = GetNewPasswordFromUser(); + + if (PK11_ChangePW(slot.get(), oldPw.c_str(), newPw.c_str()) != SECSuccess) { + std::cerr << "Failed to change password." << std::endl; + return false; + } + + std::cout << "Password changed successfully." << std::endl; + return true; +} + +bool DBLoginIfNeeded(const ScopedPK11SlotInfo &slot) { + if (!PK11_NeedLogin(slot.get())) { + return true; + } + + PK11_SetPasswordFunc(&GetModulePassword); + std::string pw = GetPassword("Enter your password: "); + PwData pwData = {PW_PLAINTEXT, const_cast<char *>(pw.c_str())}; + SECStatus rv = PK11_Authenticate(slot.get(), true /*loadCerts*/, &pwData); + if (rv != SECSuccess) { + std::cerr << "Could not authenticate to token " + << PK11_GetTokenName(slot.get()) << ". Failed with error " + << PR_ErrorToName(PR_GetError()) << std::endl; + return false; + } + std::cout << std::endl; + + return true; +} + +std::string StringToHex(const ScopedSECItem &input) { + std::stringstream ss; + ss << "0x"; + for (size_t i = 0; i < input->len; i++) { + ss << std::hex << std::setfill('0') << std::setw(2) + << static_cast<int>(input->data[i]); + } + + return ss.str(); +} + +std::vector<uint8_t> ReadInputData(std::string dataPath) { + std::vector<uint8_t> data; + if (dataPath.empty()) { + std::cout << "No input file path given, using stdin." << std::endl; + data = ReadFromIstream(std::cin); + } else { + std::ifstream is(dataPath, std::ifstream::binary); + if (is.good()) { + data = ReadFromIstream(is); + } else { + std::cerr << "IO Error when opening " << dataPath << std::endl; + std::cerr << "Input file does not exist or you don't have permissions." + << std::endl; + } + } + + return data; +} + +std::istream &GetStreamFromFileOrStdin(std::string &path, std::ifstream &ifs) { + if (path.empty()) { + return std::cin; + } + + ifs.open(path, std::ifstream::binary); + if (!ifs.good()) { + std::cerr << "IO Error when opening " << path << std::endl; + std::cerr << "Input file does not exist or you don't have permissions." + << std::endl; + } + + return ifs; +} diff --git a/security/nss/nss-tool/common/util.h b/security/nss/nss-tool/common/util.h new file mode 100644 index 000000000..58fb05839 --- /dev/null +++ b/security/nss/nss-tool/common/util.h @@ -0,0 +1,32 @@ +/* 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/. */ + +#ifndef util_h__ +#define util_h__ + +#include "nspr.h" +#include "scoped_ptrs.h" + +#include <secmodt.h> +#include <string> +#include <vector> + +#ifndef PORT_Malloc +#define PORT_Malloc PR_Malloc +#endif + +enum PwDataType { PW_NONE = 0, PW_FROMFILE = 1, PW_PLAINTEXT = 2 }; +typedef struct { + PwDataType source; + char *data; +} PwData; + +bool InitSlotPassword(void); +bool ChangeSlotPassword(void); +bool DBLoginIfNeeded(const ScopedPK11SlotInfo &slot); +std::string StringToHex(const ScopedSECItem &input); +std::vector<uint8_t> ReadInputData(std::string dataPath); +std::istream &GetStreamFromFileOrStdin(std::string &path, std::ifstream &ifs); + +#endif // util_h__ diff --git a/security/nss/nss-tool/db/dbtool.cc b/security/nss/nss-tool/db/dbtool.cc new file mode 100644 index 000000000..8c369cf05 --- /dev/null +++ b/security/nss/nss-tool/db/dbtool.cc @@ -0,0 +1,497 @@ +/* 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 "dbtool.h" +#include "argparse.h" +#include "scoped_ptrs.h" +#include "util.h" + +#include <iomanip> +#include <iostream> +#include <regex> +#include <sstream> + +#include <cert.h> +#include <certdb.h> +#include <nss.h> +#include <pk11pub.h> +#include <prerror.h> +#include <prio.h> + +const std::vector<std::string> kCommandArgs( + {"--create", "--list-certs", "--import-cert", "--list-keys", "--import-key", + "--delete-cert", "--delete-key", "--change-password"}); + +static bool HasSingleCommandArgument(const ArgParser &parser) { + auto pred = [&](const std::string &cmd) { return parser.Has(cmd); }; + return std::count_if(kCommandArgs.begin(), kCommandArgs.end(), pred) == 1; +} + +static bool HasArgumentRequiringWriteAccess(const ArgParser &parser) { + return parser.Has("--create") || parser.Has("--import-cert") || + parser.Has("--import-key") || parser.Has("--delete-cert") || + parser.Has("--delete-key") || parser.Has("--change-password"); +} + +static std::string PrintFlags(unsigned int flags) { + std::stringstream ss; + if ((flags & CERTDB_VALID_CA) && !(flags & CERTDB_TRUSTED_CA) && + !(flags & CERTDB_TRUSTED_CLIENT_CA)) { + ss << "c"; + } + if ((flags & CERTDB_TERMINAL_RECORD) && !(flags & CERTDB_TRUSTED)) { + ss << "p"; + } + if (flags & CERTDB_TRUSTED_CA) { + ss << "C"; + } + if (flags & CERTDB_TRUSTED_CLIENT_CA) { + ss << "T"; + } + if (flags & CERTDB_TRUSTED) { + ss << "P"; + } + if (flags & CERTDB_USER) { + ss << "u"; + } + if (flags & CERTDB_SEND_WARN) { + ss << "w"; + } + if (flags & CERTDB_INVISIBLE_CA) { + ss << "I"; + } + if (flags & CERTDB_GOVT_APPROVED_CA) { + ss << "G"; + } + return ss.str(); +} + +static const char *const keyTypeName[] = {"null", "rsa", "dsa", "fortezza", + "dh", "kea", "ec"}; + +void DBTool::Usage() { + std::cerr << "Usage: nss db [--path <directory>]" << std::endl; + std::cerr << " --create" << std::endl; + std::cerr << " --change-password" << std::endl; + std::cerr << " --list-certs" << std::endl; + std::cerr << " --import-cert [<path>] --name <name> [--trusts <trusts>]" + << std::endl; + std::cerr << " --list-keys" << std::endl; + std::cerr << " --import-key [<path> [-- name <name>]]" << std::endl; + std::cerr << " --delete-cert <name>" << std::endl; + std::cerr << " --delete-key <name>" << std::endl; +} + +bool DBTool::Run(const std::vector<std::string> &arguments) { + ArgParser parser(arguments); + + if (!HasSingleCommandArgument(parser)) { + Usage(); + return false; + } + + PRAccessHow how = PR_ACCESS_READ_OK; + bool readOnly = true; + if (HasArgumentRequiringWriteAccess(parser)) { + how = PR_ACCESS_WRITE_OK; + readOnly = false; + } + + std::string initDir("."); + if (parser.Has("--path")) { + initDir = parser.Get("--path"); + } + if (PR_Access(initDir.c_str(), how) != PR_SUCCESS) { + std::cerr << "Directory '" << initDir + << "' does not exist or you don't have permissions!" << std::endl; + return false; + } + + std::cout << "Using database directory: " << initDir << std::endl + << std::endl; + + bool dbFilesExist = PathHasDBFiles(initDir); + if (parser.Has("--create") && dbFilesExist) { + std::cerr << "Trying to create database files in a directory where they " + "already exists. Delete the db files before creating new ones." + << std::endl; + return false; + } + if (!parser.Has("--create") && !dbFilesExist) { + std::cerr << "No db files found." << std::endl; + std::cerr << "Create them using 'nss db --create [--path /foo/bar]' before " + "continuing." + << std::endl; + return false; + } + + // init NSS + const char *certPrefix = ""; // certutil -P option --- can leave this empty + SECStatus rv = NSS_Initialize(initDir.c_str(), certPrefix, certPrefix, + "secmod.db", readOnly ? NSS_INIT_READONLY : 0); + if (rv != SECSuccess) { + std::cerr << "NSS init failed!" << std::endl; + return false; + } + + bool ret = true; + if (parser.Has("--list-certs")) { + ListCertificates(); + } else if (parser.Has("--import-cert")) { + ret = ImportCertificate(parser); + } else if (parser.Has("--create")) { + ret = InitSlotPassword(); + if (ret) { + std::cout << "DB files created successfully." << std::endl; + } + } else if (parser.Has("--list-keys")) { + ret = ListKeys(); + } else if (parser.Has("--import-key")) { + ret = ImportKey(parser); + } else if (parser.Has("--delete-cert")) { + ret = DeleteCert(parser); + } else if (parser.Has("--delete-key")) { + ret = DeleteKey(parser); + } else if (parser.Has("--change-password")) { + ret = ChangeSlotPassword(); + } + + // shutdown nss + if (NSS_Shutdown() != SECSuccess) { + std::cerr << "NSS Shutdown failed!" << std::endl; + return false; + } + + return ret; +} + +bool DBTool::PathHasDBFiles(std::string path) { + std::regex certDBPattern("cert.*\\.db"); + std::regex keyDBPattern("key.*\\.db"); + + PRDir *dir = PR_OpenDir(path.c_str()); + if (!dir) { + std::cerr << "Directory " << path << " could not be accessed!" << std::endl; + return false; + } + + PRDirEntry *ent; + bool dbFileExists = false; + while ((ent = PR_ReadDir(dir, PR_SKIP_BOTH))) { + if (std::regex_match(ent->name, certDBPattern) || + std::regex_match(ent->name, keyDBPattern) || + "secmod.db" == std::string(ent->name)) { + dbFileExists = true; + break; + } + } + + (void)PR_CloseDir(dir); + return dbFileExists; +} + +void DBTool::ListCertificates() { + ScopedCERTCertList list(PK11_ListCerts(PK11CertListAll, nullptr)); + CERTCertListNode *node; + + std::cout << std::setw(60) << std::left << "Certificate Nickname" + << " " + << "Trust Attributes" << std::endl; + std::cout << std::setw(60) << std::left << "" + << " " + << "SSL,S/MIME,JAR/XPI" << std::endl + << std::endl; + + for (node = CERT_LIST_HEAD(list); !CERT_LIST_END(node, list); + node = CERT_LIST_NEXT(node)) { + CERTCertificate *cert = node->cert; + + std::string name("(unknown)"); + char *appData = static_cast<char *>(node->appData); + if (appData && strlen(appData) > 0) { + name = appData; + } else if (cert->nickname && strlen(cert->nickname) > 0) { + name = cert->nickname; + } else if (cert->emailAddr && strlen(cert->emailAddr) > 0) { + name = cert->emailAddr; + } + + CERTCertTrust trust; + std::string trusts; + if (CERT_GetCertTrust(cert, &trust) == SECSuccess) { + std::stringstream ss; + ss << PrintFlags(trust.sslFlags); + ss << ","; + ss << PrintFlags(trust.emailFlags); + ss << ","; + ss << PrintFlags(trust.objectSigningFlags); + trusts = ss.str(); + } else { + trusts = ",,"; + } + std::cout << std::setw(60) << std::left << name << " " << trusts + << std::endl; + } +} + +bool DBTool::ImportCertificate(const ArgParser &parser) { + if (!parser.Has("--name")) { + std::cerr << "A name (--name) is required to import a certificate." + << std::endl; + Usage(); + return false; + } + + std::string derFilePath = parser.Get("--import-cert"); + std::string certName = parser.Get("--name"); + std::string trustString("TCu,Cu,Tu"); + if (parser.Has("--trusts")) { + trustString = parser.Get("--trusts"); + } + + CERTCertTrust trust; + SECStatus rv = CERT_DecodeTrustString(&trust, trustString.c_str()); + if (rv != SECSuccess) { + std::cerr << "Cannot decode trust string!" << std::endl; + return false; + } + + ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot()); + if (slot.get() == nullptr) { + std::cerr << "Error: Init PK11SlotInfo failed!" << std::endl; + return false; + } + + std::vector<uint8_t> certData = ReadInputData(derFilePath); + + ScopedCERTCertificate cert(CERT_DecodeCertFromPackage( + reinterpret_cast<char *>(certData.data()), certData.size())); + if (cert.get() == nullptr) { + std::cerr << "Error: Could not decode certificate!" << std::endl; + return false; + } + + rv = PK11_ImportCert(slot.get(), cert.get(), CK_INVALID_HANDLE, + certName.c_str(), PR_FALSE); + if (rv != SECSuccess) { + // TODO handle authentication -> PK11_Authenticate (see certutil.c line + // 134) + std::cerr << "Error: Could not add certificate to database!" << std::endl; + return false; + } + + rv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), cert.get(), &trust); + if (rv != SECSuccess) { + std::cerr << "Cannot change cert's trust" << std::endl; + return false; + } + + std::cout << "Certificate import was successful!" << std::endl; + // TODO show information about imported certificate + return true; +} + +bool DBTool::ListKeys() { + ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot()); + if (slot.get() == nullptr) { + std::cerr << "Error: Init PK11SlotInfo failed!" << std::endl; + return false; + } + + if (!DBLoginIfNeeded(slot)) { + return false; + } + + ScopedSECKEYPrivateKeyList list(PK11_ListPrivateKeysInSlot(slot.get())); + if (list.get() == nullptr) { + std::cerr << "Listing private keys failed with error " + << PR_ErrorToName(PR_GetError()) << std::endl; + return false; + } + + SECKEYPrivateKeyListNode *node; + int count = 0; + for (node = PRIVKEY_LIST_HEAD(list.get()); + !PRIVKEY_LIST_END(node, list.get()); node = PRIVKEY_LIST_NEXT(node)) { + char *keyNameRaw = PK11_GetPrivateKeyNickname(node->key); + std::string keyName(keyNameRaw ? keyNameRaw : ""); + + if (keyName.empty()) { + ScopedCERTCertificate cert(PK11_GetCertFromPrivateKey(node->key)); + if (cert.get()) { + if (cert->nickname && strlen(cert->nickname) > 0) { + keyName = cert->nickname; + } else if (cert->emailAddr && strlen(cert->emailAddr) > 0) { + keyName = cert->emailAddr; + } + } + if (keyName.empty()) { + keyName = "(none)"; // default value + } + } + + SECKEYPrivateKey *key = node->key; + ScopedSECItem keyIDItem(PK11_GetLowLevelKeyIDForPrivateKey(key)); + if (keyIDItem.get() == nullptr) { + std::cerr << "Error: PK11_GetLowLevelKeyIDForPrivateKey failed!" + << std::endl; + continue; + } + + std::string keyID = StringToHex(keyIDItem); + + if (count++ == 0) { + // print header + std::cout << std::left << std::setw(20) << "<key#, key name>" + << std::setw(20) << "key type" + << "key id" << std::endl; + } + + std::stringstream leftElem; + leftElem << "<" << count << ", " << keyName << ">"; + std::cout << std::left << std::setw(20) << leftElem.str() << std::setw(20) + << keyTypeName[key->keyType] << keyID << std::endl; + } + + if (count == 0) { + std::cout << "No keys found." << std::endl; + } + + return true; +} + +bool DBTool::ImportKey(const ArgParser &parser) { + std::string privKeyFilePath = parser.Get("--import-key"); + std::string name; + if (parser.Has("--name")) { + name = parser.Get("--name"); + } + + ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot()); + if (slot.get() == nullptr) { + std::cerr << "Error: Init PK11SlotInfo failed!" << std::endl; + return false; + } + + if (!DBLoginIfNeeded(slot)) { + return false; + } + + std::vector<uint8_t> privKeyData = ReadInputData(privKeyFilePath); + if (privKeyData.empty()) { + return false; + } + SECItem pkcs8PrivKeyItem = { + siBuffer, reinterpret_cast<unsigned char *>(privKeyData.data()), + static_cast<unsigned int>(privKeyData.size())}; + + SECItem nickname = {siBuffer, nullptr, 0}; + if (!name.empty()) { + nickname.data = const_cast<unsigned char *>( + reinterpret_cast<const unsigned char *>(name.c_str())); + nickname.len = static_cast<unsigned int>(name.size()); + } + + SECStatus rv = PK11_ImportDERPrivateKeyInfo( + slot.get(), &pkcs8PrivKeyItem, + nickname.data == nullptr ? nullptr : &nickname, nullptr /*publicValue*/, + true /*isPerm*/, false /*isPrivate*/, KU_ALL, nullptr); + if (rv != SECSuccess) { + std::cerr << "Importing a private key in DER format failed with error " + << PR_ErrorToName(PR_GetError()) << std::endl; + return false; + } + + std::cout << "Key import succeeded." << std::endl; + return true; +} + +bool DBTool::DeleteCert(const ArgParser &parser) { + std::string certName = parser.Get("--delete-cert"); + if (certName.empty()) { + std::cerr << "A name is required to delete a certificate." << std::endl; + Usage(); + return false; + } + + ScopedCERTCertificate cert(CERT_FindCertByNicknameOrEmailAddr( + CERT_GetDefaultCertDB(), certName.c_str())); + if (!cert) { + std::cerr << "Could not find certificate with name " << certName << "." + << std::endl; + return false; + } + + SECStatus rv = SEC_DeletePermCertificate(cert.get()); + if (rv != SECSuccess) { + std::cerr << "Unable to delete certificate with name " << certName << "." + << std::endl; + return false; + } + + std::cout << "Certificate with name " << certName << " deleted successfully." + << std::endl; + return true; +} + +bool DBTool::DeleteKey(const ArgParser &parser) { + std::string keyName = parser.Get("--delete-key"); + if (keyName.empty()) { + std::cerr << "A name is required to delete a key." << std::endl; + Usage(); + return false; + } + + ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot()); + if (slot.get() == nullptr) { + std::cerr << "Error: Init PK11SlotInfo failed!" << std::endl; + return false; + } + + if (!DBLoginIfNeeded(slot)) { + return false; + } + + ScopedSECKEYPrivateKeyList list(PK11_ListPrivKeysInSlot( + slot.get(), const_cast<char *>(keyName.c_str()), nullptr)); + if (list.get() == nullptr) { + std::cerr << "Fetching private keys with nickname " << keyName + << " failed with error " << PR_ErrorToName(PR_GetError()) + << std::endl; + return false; + } + + unsigned int foundKeys = 0, deletedKeys = 0; + SECKEYPrivateKeyListNode *node; + for (node = PRIVKEY_LIST_HEAD(list.get()); + !PRIVKEY_LIST_END(node, list.get()); node = PRIVKEY_LIST_NEXT(node)) { + SECKEYPrivateKey *privKey = node->key; + foundKeys++; + // see PK11_DeleteTokenPrivateKey for example usage + // calling PK11_DeleteTokenPrivateKey directly does not work because it also + // destroys the SECKEYPrivateKey (by calling SECKEY_DestroyPrivateKey) - + // then SECKEY_DestroyPrivateKeyList does not + // work because it also calls SECKEY_DestroyPrivateKey + SECStatus rv = + PK11_DestroyTokenObject(privKey->pkcs11Slot, privKey->pkcs11ID); + if (rv == SECSuccess) { + deletedKeys++; + } + } + + if (foundKeys > deletedKeys) { + std::cerr << "Some keys could not be deleted." << std::endl; + } + + if (deletedKeys > 0) { + std::cout << "Found " << foundKeys << " keys." << std::endl; + std::cout << "Successfully deleted " << deletedKeys + << " key(s) with nickname " << keyName << "." << std::endl; + } else { + std::cout << "No key with nickname " << keyName << " found to delete." + << std::endl; + } + + return true; +} diff --git a/security/nss/nss-tool/db/dbtool.h b/security/nss/nss-tool/db/dbtool.h new file mode 100644 index 000000000..dd0ef0ace --- /dev/null +++ b/security/nss/nss-tool/db/dbtool.h @@ -0,0 +1,28 @@ +/* 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/. */ + +#ifndef dbtool_h__ +#define dbtool_h__ + +#include <string> +#include <vector> +#include "argparse.h" +#include "tool.h" + +class DBTool : public Tool { + public: + bool Run(const std::vector<std::string>& arguments) override; + + private: + void Usage() override; + bool PathHasDBFiles(std::string path); + void ListCertificates(); + bool ImportCertificate(const ArgParser& parser); + bool ListKeys(); + bool ImportKey(const ArgParser& parser); + bool DeleteCert(const ArgParser& parser); + bool DeleteKey(const ArgParser& parser); +}; + +#endif // dbtool_h__ diff --git a/security/nss/nss-tool/digest/digesttool.cc b/security/nss/nss-tool/digest/digesttool.cc new file mode 100644 index 000000000..08c3e3ba7 --- /dev/null +++ b/security/nss/nss-tool/digest/digesttool.cc @@ -0,0 +1,161 @@ +/* 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 "digesttool.h" +#include "argparse.h" +#include "scoped_ptrs.h" +#include "util.h" + +#include <algorithm> +#include <fstream> +#include <iomanip> +#include <iostream> + +#include <hasht.h> // contains supported digest types +#include <nss.h> +#include <pk11pub.h> +#include <prio.h> + +static SECOidData* HashTypeToOID(HASH_HashType hashtype) { + SECOidTag hashtag; + + if (hashtype <= HASH_AlgNULL || hashtype >= HASH_AlgTOTAL) { + return nullptr; + } + + switch (hashtype) { + case HASH_AlgMD5: + hashtag = SEC_OID_MD5; + break; + case HASH_AlgSHA1: + hashtag = SEC_OID_SHA1; + break; + case HASH_AlgSHA224: + hashtag = SEC_OID_SHA224; + break; + case HASH_AlgSHA256: + hashtag = SEC_OID_SHA256; + break; + case HASH_AlgSHA384: + hashtag = SEC_OID_SHA384; + break; + case HASH_AlgSHA512: + hashtag = SEC_OID_SHA512; + break; + default: + return nullptr; + } + + return SECOID_FindOIDByTag(hashtag); +} + +static SECOidData* HashNameToOID(const std::string& hashName) { + for (size_t htype = HASH_AlgNULL + 1; htype < HASH_AlgTOTAL; htype++) { + SECOidData* hashOID = HashTypeToOID(static_cast<HASH_HashType>(htype)); + if (hashOID && std::string(hashOID->desc) == hashName) { + return hashOID; + } + } + + return nullptr; +} + +static bool Digest(const ArgParser& parser, SECOidData* hashOID); +static bool ComputeDigest(std::istream& is, ScopedPK11Context& hashCtx); + +bool DigestTool::Run(const std::vector<std::string>& arguments) { + ArgParser parser(arguments); + + if (parser.GetPositionalArgumentCount() != 1) { + Usage(); + return false; + } + + // no need for a db for the digest tool + SECStatus rv = NSS_NoDB_Init("."); + if (rv != SECSuccess) { + std::cerr << "NSS init failed!" << std::endl; + return false; + } + + std::string hashName = parser.GetPositionalArgument(0); + std::transform(hashName.begin(), hashName.end(), hashName.begin(), ::toupper); + SECOidData* hashOID = HashNameToOID(hashName); + if (hashOID == nullptr) { + std::cerr << "Error: Unknown digest type " + << parser.GetPositionalArgument(0) << "." << std::endl; + return false; + } + + bool ret = Digest(parser, hashOID); + + // shutdown nss + if (NSS_Shutdown() != SECSuccess) { + std::cerr << "NSS Shutdown failed!" << std::endl; + return false; + } + + return ret; +} + +void DigestTool::Usage() { + std::cerr << "Usage: nss digest md5|sha-1|sha-224|sha-256|sha-384|sha-512 " + "[--infile <path>]" + << std::endl; +} + +static bool Digest(const ArgParser& parser, SECOidData* hashOID) { + std::string inputFile; + if (parser.Has("--infile")) { + inputFile = parser.Get("--infile"); + } + + ScopedPK11Context hashCtx(PK11_CreateDigestContext(hashOID->offset)); + if (hashCtx == nullptr) { + std::cerr << "Creating digest context failed." << std::endl; + return false; + } + PK11_DigestBegin(hashCtx.get()); + + std::ifstream fis; + std::istream& is = GetStreamFromFileOrStdin(inputFile, fis); + if (!is.good() || !ComputeDigest(is, hashCtx)) { + return false; + } + + unsigned char digest[HASH_LENGTH_MAX]; + unsigned int len; + SECStatus rv = PK11_DigestFinal(hashCtx.get(), digest, &len, HASH_LENGTH_MAX); + if (rv != SECSuccess || len == 0) { + std::cerr << "Calculating final hash value failed." << std::endl; + return false; + } + + // human readable output + for (size_t i = 0; i < len; i++) { + std::cout << std::setw(2) << std::setfill('0') << std::hex + << static_cast<int>(digest[i]); + } + std::cout << std::endl; + + return true; +} + +static bool ComputeDigest(std::istream& is, ScopedPK11Context& hashCtx) { + while (is) { + unsigned char buf[4096]; + is.read(reinterpret_cast<char*>(buf), sizeof(buf)); + if (is.fail() && !is.eof()) { + std::cerr << "Error reading from input stream." << std::endl; + return false; + } + SECStatus rv = PK11_DigestOp(hashCtx.get(), buf, is.gcount()); + if (rv != SECSuccess) { + std::cerr << "PK11_DigestOp failed." << std::endl; + return false; + } + } + + return true; +} diff --git a/security/nss/nss-tool/digest/digesttool.h b/security/nss/nss-tool/digest/digesttool.h new file mode 100644 index 000000000..0e18346f5 --- /dev/null +++ b/security/nss/nss-tool/digest/digesttool.h @@ -0,0 +1,20 @@ +/* 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/. */ + +#ifndef digest_tool_h__ +#define digest_tool_h__ + +#include <string> +#include <vector> +#include "tool.h" + +class DigestTool : public Tool { + public: + bool Run(const std::vector<std::string>& arguments) override; + + private: + void Usage() override; +}; + +#endif // digest_tool_h__ diff --git a/security/nss/nss-tool/enc/enctool.cc b/security/nss/nss-tool/enc/enctool.cc new file mode 100644 index 000000000..b3c0d1dbe --- /dev/null +++ b/security/nss/nss-tool/enc/enctool.cc @@ -0,0 +1,464 @@ +/* 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); + SECStatus rv; + 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; +} diff --git a/security/nss/nss-tool/enc/enctool.h b/security/nss/nss-tool/enc/enctool.h new file mode 100644 index 000000000..5a6a5a164 --- /dev/null +++ b/security/nss/nss-tool/enc/enctool.h @@ -0,0 +1,62 @@ +/* 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/. */ + +#ifndef enctool_h__ +#define enctool_h__ + +#include <string> +#include <vector> +#include "argparse.h" +#include "prerror.h" +#include "scoped_ptrs.h" +#include "tool.h" + +class EncTool : public Tool { + public: + bool Run(const std::vector<std::string>& arguments) override; + void Usage() override; + + private: + typedef bool (EncTool::*key_func_t)(const std::vector<uint8_t>& aad, + ScopedSECItem& chacha_key, + ScopedSECItem& params); + void PrintBytes(const std::vector<uint8_t>& bytes, const std::string& txt); + bool WriteBytes(const std::vector<uint8_t>& bytes, std::string out_file); + void PrintError(const std::string& m, PRErrorCode err, size_t line_number); + void PrintError(const std::string& m, size_t line_number); + bool GetKey(const std::vector<uint8_t>& key_bytes, ScopedSECItem& key_item); + bool 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); + bool 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); + bool GenerateAesGcmKey(const std::vector<uint8_t>& aad, + ScopedSECItem& aes_key, ScopedSECItem& params); + bool ReadAesGcmKey(const std::vector<uint8_t>& aad, ScopedSECItem& aes_key, + ScopedSECItem& params); + std::vector<uint8_t> GenerateRandomness(size_t num_bytes); + bool GenerateChachaKey(const std::vector<uint8_t>& aad, + ScopedSECItem& chacha_key, ScopedSECItem& params); + bool ReadChachaKey(const std::vector<uint8_t>& aad, ScopedSECItem& chacha_key, + ScopedSECItem& params); + bool DoCipher(std::string fileName, std::string outFile, bool encrypt, + key_func_t get_params); + size_t PrintFileSize(std::string fileName); + bool IsValidCommand(ArgParser arguments); + + bool debug_ = false; + bool write_key_ = true; + bool write_iv_ = true; + std::string key_file_ = "/tmp/key"; + std::string iv_file_ = "/tmp/iv"; + CK_MECHANISM_TYPE cipher_mech_; + + const std::string kAESCommand = "aes"; + const std::string kChaChaCommand = "chacha"; +}; + +#endif // enctool_h__ diff --git a/security/nss/nss-tool/nss_tool.cc b/security/nss/nss-tool/nss_tool.cc new file mode 100644 index 000000000..8864f140d --- /dev/null +++ b/security/nss/nss-tool/nss_tool.cc @@ -0,0 +1,70 @@ +/* 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 <algorithm> +#include <cstring> +#include <iostream> +#include <memory> +#include <string> +#include <vector> + +#include <prinit.h> + +#include "argparse.h" +#include "db/dbtool.h" +#include "digest/digesttool.h" +#include "enc/enctool.h" +#include "tool.h" + +static void Usage() { + std::cerr << "Usage: nss <command> <subcommand> [options]" << std::endl; + std::cerr << " nss db [--path <directory>] <commands>" << std::endl; + std::cerr << " nss encrypt <options>" << std::endl; + std::cerr << " nss decrypt <options>" << std::endl; + std::cerr << " nss digest <options>" << std::endl; +} + +static const std::string kDbCommand = "db"; +static const std::string kEncryptCommand = "encrypt"; +static const std::string kDecryptCommand = "decrypt"; +static const std::string kDigestCommand = "digest"; + +int main(int argc, char **argv) { + if (argc < 2) { + Usage(); + return 1; + } + std::vector<std::string> arguments(argv + 2, argv + argc); + + std::unique_ptr<Tool> tool = nullptr; + if (argv[1] == kDbCommand) { + tool = std::unique_ptr<Tool>(new DBTool()); + } + if (argv[1] == kEncryptCommand) { + tool = std::unique_ptr<Tool>(new EncTool()); + arguments.push_back("--encrypt"); + } + if (argv[1] == kDecryptCommand) { + tool = std::unique_ptr<Tool>(new EncTool()); + arguments.push_back("--decrypt"); + } + if (argv[1] == kDigestCommand) { + tool = std::unique_ptr<Tool>(new DigestTool()); + } + if (!tool) { + Usage(); + return 1; + } + + int exit_code = 0; + PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1); + + if (!tool->Run(arguments)) { + exit_code = 1; + } + + PR_Cleanup(); + + return exit_code; +} diff --git a/security/nss/nss-tool/nss_tool.gyp b/security/nss/nss-tool/nss_tool.gyp new file mode 100644 index 000000000..a5d03fcf9 --- /dev/null +++ b/security/nss/nss-tool/nss_tool.gyp @@ -0,0 +1,31 @@ +# 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/. +{ + 'includes' : [ + '../coreconf/config.gypi', + '../cmd/platlibs.gypi', + ], + 'targets' : [ + { + 'target_name' : 'nss', + 'type' : 'executable', + 'sources' : [ + 'nss_tool.cc', + 'common/argparse.cc', + 'common/util.cc', + 'db/dbtool.cc', + 'enc/enctool.cc', + 'digest/digesttool.cc' + ], + 'include_dirs': [ + 'common', + ], + 'dependencies' : [ + '<(DEPTH)/cpputil/cpputil.gyp:cpputil', + '<(DEPTH)/exports.gyp:dbm_exports', + '<(DEPTH)/exports.gyp:nss_exports', + ], + } + ], +} |