| /* 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 "nss_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; |
| } |