497 lines
15 KiB
C++
497 lines
15 KiB
C++
/* 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;
|
|
}
|