diff options
Diffstat (limited to 'security/nss/nss-tool/db/dbtool.cc')
-rw-r--r-- | security/nss/nss-tool/db/dbtool.cc | 497 |
1 files changed, 497 insertions, 0 deletions
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; +} |