/* 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 ]" << std::endl;
std::cerr <<
" --create" << std::endl;
std::cerr <<
" --change-password" << std::endl;
std::cerr <<
" --list-certs" << std::endl;
std::cerr <<
" --import-cert [] --name [--trusts ]"
<< std::endl;
std::cerr <<
" --list-keys" << std::endl;
std::cerr <<
" --import-key [ [-- name ]]" << std::endl;
std::cerr <<
" --delete-cert " << std::endl;
std::cerr <<
" --delete-key " << 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) <<
""
<< 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;
}