tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

dbtool.cc (15229B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "dbtool.h"
      6 #include "argparse.h"
      7 #include "nss_scoped_ptrs.h"
      8 #include "util.h"
      9 
     10 #include <iomanip>
     11 #include <iostream>
     12 #include <regex>
     13 #include <sstream>
     14 
     15 #include <cert.h>
     16 #include <certdb.h>
     17 #include <nss.h>
     18 #include <pk11pub.h>
     19 #include <prerror.h>
     20 #include <prio.h>
     21 
     22 const std::vector<std::string> kCommandArgs(
     23    {"--create", "--list-certs", "--import-cert", "--list-keys", "--import-key",
     24     "--delete-cert", "--delete-key", "--change-password"});
     25 
     26 static bool HasSingleCommandArgument(const ArgParser &parser) {
     27  auto pred = [&](const std::string &cmd) { return parser.Has(cmd); };
     28  return std::count_if(kCommandArgs.begin(), kCommandArgs.end(), pred) == 1;
     29 }
     30 
     31 static bool HasArgumentRequiringWriteAccess(const ArgParser &parser) {
     32  return parser.Has("--create") || parser.Has("--import-cert") ||
     33         parser.Has("--import-key") || parser.Has("--delete-cert") ||
     34         parser.Has("--delete-key") || parser.Has("--change-password");
     35 }
     36 
     37 static std::string PrintFlags(unsigned int flags) {
     38  std::stringstream ss;
     39  if ((flags & CERTDB_VALID_CA) && !(flags & CERTDB_TRUSTED_CA) &&
     40      !(flags & CERTDB_TRUSTED_CLIENT_CA)) {
     41    ss << "c";
     42  }
     43  if ((flags & CERTDB_TERMINAL_RECORD) && !(flags & CERTDB_TRUSTED)) {
     44    ss << "p";
     45  }
     46  if (flags & CERTDB_TRUSTED_CA) {
     47    ss << "C";
     48  }
     49  if (flags & CERTDB_TRUSTED_CLIENT_CA) {
     50    ss << "T";
     51  }
     52  if (flags & CERTDB_TRUSTED) {
     53    ss << "P";
     54  }
     55  if (flags & CERTDB_USER) {
     56    ss << "u";
     57  }
     58  if (flags & CERTDB_SEND_WARN) {
     59    ss << "w";
     60  }
     61  if (flags & CERTDB_INVISIBLE_CA) {
     62    ss << "I";
     63  }
     64  if (flags & CERTDB_GOVT_APPROVED_CA) {
     65    ss << "G";
     66  }
     67  return ss.str();
     68 }
     69 
     70 static const char *const keyTypeName[] = {"null", "rsa", "dsa", "fortezza",
     71                                          "dh",   "kea", "ec"};
     72 
     73 void DBTool::Usage() {
     74  std::cerr << "Usage: nss db [--path <directory>]" << std::endl;
     75  std::cerr << "  --create" << std::endl;
     76  std::cerr << "  --change-password" << std::endl;
     77  std::cerr << "  --list-certs" << std::endl;
     78  std::cerr << "  --import-cert [<path>] --name <name> [--trusts <trusts>]"
     79            << std::endl;
     80  std::cerr << "  --list-keys" << std::endl;
     81  std::cerr << "  --import-key [<path> [-- name <name>]]" << std::endl;
     82  std::cerr << "  --delete-cert <name>" << std::endl;
     83  std::cerr << "  --delete-key <name>" << std::endl;
     84 }
     85 
     86 bool DBTool::Run(const std::vector<std::string> &arguments) {
     87  ArgParser parser(arguments);
     88 
     89  if (!HasSingleCommandArgument(parser)) {
     90    Usage();
     91    return false;
     92  }
     93 
     94  PRAccessHow how = PR_ACCESS_READ_OK;
     95  bool readOnly = true;
     96  if (HasArgumentRequiringWriteAccess(parser)) {
     97    how = PR_ACCESS_WRITE_OK;
     98    readOnly = false;
     99  }
    100 
    101  std::string initDir(".");
    102  if (parser.Has("--path")) {
    103    initDir = parser.Get("--path");
    104  }
    105  if (PR_Access(initDir.c_str(), how) != PR_SUCCESS) {
    106    std::cerr << "Directory '" << initDir
    107              << "' does not exist or you don't have permissions!" << std::endl;
    108    return false;
    109  }
    110 
    111  std::cout << "Using database directory: " << initDir << std::endl
    112            << std::endl;
    113 
    114  bool dbFilesExist = PathHasDBFiles(initDir);
    115  if (parser.Has("--create") && dbFilesExist) {
    116    std::cerr << "Trying to create database files in a directory where they "
    117                 "already exists. Delete the db files before creating new ones."
    118              << std::endl;
    119    return false;
    120  }
    121  if (!parser.Has("--create") && !dbFilesExist) {
    122    std::cerr << "No db files found." << std::endl;
    123    std::cerr << "Create them using 'nss db --create [--path /foo/bar]' before "
    124                 "continuing."
    125              << std::endl;
    126    return false;
    127  }
    128 
    129  // init NSS
    130  const char *certPrefix = "";  // certutil -P option  --- can leave this empty
    131  SECStatus rv = NSS_Initialize(initDir.c_str(), certPrefix, certPrefix,
    132                                "secmod.db", readOnly ? NSS_INIT_READONLY : 0);
    133  if (rv != SECSuccess) {
    134    std::cerr << "NSS init failed!" << std::endl;
    135    return false;
    136  }
    137 
    138  bool ret = true;
    139  if (parser.Has("--list-certs")) {
    140    ListCertificates();
    141  } else if (parser.Has("--import-cert")) {
    142    ret = ImportCertificate(parser);
    143  } else if (parser.Has("--create")) {
    144    ret = InitSlotPassword();
    145    if (ret) {
    146      std::cout << "DB files created successfully." << std::endl;
    147    }
    148  } else if (parser.Has("--list-keys")) {
    149    ret = ListKeys();
    150  } else if (parser.Has("--import-key")) {
    151    ret = ImportKey(parser);
    152  } else if (parser.Has("--delete-cert")) {
    153    ret = DeleteCert(parser);
    154  } else if (parser.Has("--delete-key")) {
    155    ret = DeleteKey(parser);
    156  } else if (parser.Has("--change-password")) {
    157    ret = ChangeSlotPassword();
    158  }
    159 
    160  // shutdown nss
    161  if (NSS_Shutdown() != SECSuccess) {
    162    std::cerr << "NSS Shutdown failed!" << std::endl;
    163    return false;
    164  }
    165 
    166  return ret;
    167 }
    168 
    169 bool DBTool::PathHasDBFiles(std::string path) {
    170  std::regex certDBPattern("cert.*\\.db");
    171  std::regex keyDBPattern("key.*\\.db");
    172 
    173  PRDir *dir = PR_OpenDir(path.c_str());
    174  if (!dir) {
    175    std::cerr << "Directory " << path << " could not be accessed!" << std::endl;
    176    return false;
    177  }
    178 
    179  PRDirEntry *ent;
    180  bool dbFileExists = false;
    181  while ((ent = PR_ReadDir(dir, PR_SKIP_BOTH))) {
    182    if (std::regex_match(ent->name, certDBPattern) ||
    183        std::regex_match(ent->name, keyDBPattern) ||
    184        "secmod.db" == std::string(ent->name)) {
    185      dbFileExists = true;
    186      break;
    187    }
    188  }
    189 
    190  (void)PR_CloseDir(dir);
    191  return dbFileExists;
    192 }
    193 
    194 void DBTool::ListCertificates() {
    195  ScopedCERTCertList list(PK11_ListCerts(PK11CertListAll, nullptr));
    196  CERTCertListNode *node;
    197 
    198  std::cout << std::setw(60) << std::left << "Certificate Nickname"
    199            << " "
    200            << "Trust Attributes" << std::endl;
    201  std::cout << std::setw(60) << std::left << ""
    202            << " "
    203            << "SSL,S/MIME,JAR/XPI" << std::endl
    204            << std::endl;
    205 
    206  for (node = CERT_LIST_HEAD(list); !CERT_LIST_END(node, list);
    207       node = CERT_LIST_NEXT(node)) {
    208    CERTCertificate *cert = node->cert;
    209 
    210    std::string name("(unknown)");
    211    char *appData = static_cast<char *>(node->appData);
    212    if (appData && strlen(appData) > 0) {
    213      name = appData;
    214    } else if (cert->nickname && strlen(cert->nickname) > 0) {
    215      name = cert->nickname;
    216    } else if (cert->emailAddr && strlen(cert->emailAddr) > 0) {
    217      name = cert->emailAddr;
    218    }
    219 
    220    CERTCertTrust trust;
    221    std::string trusts;
    222    if (CERT_GetCertTrust(cert, &trust) == SECSuccess) {
    223      std::stringstream ss;
    224      ss << PrintFlags(trust.sslFlags);
    225      ss << ",";
    226      ss << PrintFlags(trust.emailFlags);
    227      ss << ",";
    228      ss << PrintFlags(trust.objectSigningFlags);
    229      trusts = ss.str();
    230    } else {
    231      trusts = ",,";
    232    }
    233    std::cout << std::setw(60) << std::left << name << " " << trusts
    234              << std::endl;
    235  }
    236 }
    237 
    238 bool DBTool::ImportCertificate(const ArgParser &parser) {
    239  if (!parser.Has("--name")) {
    240    std::cerr << "A name (--name) is required to import a certificate."
    241              << std::endl;
    242    Usage();
    243    return false;
    244  }
    245 
    246  std::string derFilePath = parser.Get("--import-cert");
    247  std::string certName = parser.Get("--name");
    248  std::string trustString("TCu,Cu,Tu");
    249  if (parser.Has("--trusts")) {
    250    trustString = parser.Get("--trusts");
    251  }
    252 
    253  CERTCertTrust trust;
    254  SECStatus rv = CERT_DecodeTrustString(&trust, trustString.c_str());
    255  if (rv != SECSuccess) {
    256    std::cerr << "Cannot decode trust string!" << std::endl;
    257    return false;
    258  }
    259 
    260  ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot());
    261  if (slot.get() == nullptr) {
    262    std::cerr << "Error: Init PK11SlotInfo failed!" << std::endl;
    263    return false;
    264  }
    265 
    266  std::vector<uint8_t> certData = ReadInputData(derFilePath);
    267 
    268  ScopedCERTCertificate cert(CERT_DecodeCertFromPackage(
    269      reinterpret_cast<char *>(certData.data()), certData.size()));
    270  if (cert.get() == nullptr) {
    271    std::cerr << "Error: Could not decode certificate!" << std::endl;
    272    return false;
    273  }
    274 
    275  rv = PK11_ImportCert(slot.get(), cert.get(), CK_INVALID_HANDLE,
    276                       certName.c_str(), PR_FALSE);
    277  if (rv != SECSuccess) {
    278    // TODO handle authentication -> PK11_Authenticate (see certutil.c line
    279    // 134)
    280    std::cerr << "Error: Could not add certificate to database!" << std::endl;
    281    return false;
    282  }
    283 
    284  rv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), cert.get(), &trust);
    285  if (rv != SECSuccess) {
    286    std::cerr << "Cannot change cert's trust" << std::endl;
    287    return false;
    288  }
    289 
    290  std::cout << "Certificate import was successful!" << std::endl;
    291  // TODO show information about imported certificate
    292  return true;
    293 }
    294 
    295 bool DBTool::ListKeys() {
    296  ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot());
    297  if (slot.get() == nullptr) {
    298    std::cerr << "Error: Init PK11SlotInfo failed!" << std::endl;
    299    return false;
    300  }
    301 
    302  if (!DBLoginIfNeeded(slot)) {
    303    return false;
    304  }
    305 
    306  ScopedSECKEYPrivateKeyList list(PK11_ListPrivateKeysInSlot(slot.get()));
    307  if (list.get() == nullptr) {
    308    std::cerr << "Listing private keys failed with error "
    309              << PR_ErrorToName(PR_GetError()) << std::endl;
    310    return false;
    311  }
    312 
    313  SECKEYPrivateKeyListNode *node;
    314  int count = 0;
    315  for (node = PRIVKEY_LIST_HEAD(list.get());
    316       !PRIVKEY_LIST_END(node, list.get()); node = PRIVKEY_LIST_NEXT(node)) {
    317    char *keyNameRaw = PK11_GetPrivateKeyNickname(node->key);
    318    std::string keyName(keyNameRaw ? keyNameRaw : "");
    319 
    320    if (keyName.empty()) {
    321      ScopedCERTCertificate cert(PK11_GetCertFromPrivateKey(node->key));
    322      if (cert.get()) {
    323        if (cert->nickname && strlen(cert->nickname) > 0) {
    324          keyName = cert->nickname;
    325        } else if (cert->emailAddr && strlen(cert->emailAddr) > 0) {
    326          keyName = cert->emailAddr;
    327        }
    328      }
    329      if (keyName.empty()) {
    330        keyName = "(none)";  // default value
    331      }
    332    }
    333 
    334    SECKEYPrivateKey *key = node->key;
    335    ScopedSECItem keyIDItem(PK11_GetLowLevelKeyIDForPrivateKey(key));
    336    if (keyIDItem.get() == nullptr) {
    337      std::cerr << "Error: PK11_GetLowLevelKeyIDForPrivateKey failed!"
    338                << std::endl;
    339      continue;
    340    }
    341 
    342    std::string keyID = StringToHex(keyIDItem);
    343 
    344    if (count++ == 0) {
    345      // print header
    346      std::cout << std::left << std::setw(20) << "<key#, key name>"
    347                << std::setw(20) << "key type"
    348                << "key id" << std::endl;
    349    }
    350 
    351    std::stringstream leftElem;
    352    leftElem << "<" << count << ", " << keyName << ">";
    353    std::cout << std::left << std::setw(20) << leftElem.str() << std::setw(20)
    354              << keyTypeName[key->keyType] << keyID << std::endl;
    355  }
    356 
    357  if (count == 0) {
    358    std::cout << "No keys found." << std::endl;
    359  }
    360 
    361  return true;
    362 }
    363 
    364 bool DBTool::ImportKey(const ArgParser &parser) {
    365  std::string privKeyFilePath = parser.Get("--import-key");
    366  std::string name;
    367  if (parser.Has("--name")) {
    368    name = parser.Get("--name");
    369  }
    370 
    371  ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot());
    372  if (slot.get() == nullptr) {
    373    std::cerr << "Error: Init PK11SlotInfo failed!" << std::endl;
    374    return false;
    375  }
    376 
    377  if (!DBLoginIfNeeded(slot)) {
    378    return false;
    379  }
    380 
    381  std::vector<uint8_t> privKeyData = ReadInputData(privKeyFilePath);
    382  if (privKeyData.empty()) {
    383    return false;
    384  }
    385  SECItem pkcs8PrivKeyItem = {
    386      siBuffer, reinterpret_cast<unsigned char *>(privKeyData.data()),
    387      static_cast<unsigned int>(privKeyData.size())};
    388 
    389  SECItem nickname = {siBuffer, nullptr, 0};
    390  if (!name.empty()) {
    391    nickname.data = const_cast<unsigned char *>(
    392        reinterpret_cast<const unsigned char *>(name.c_str()));
    393    nickname.len = static_cast<unsigned int>(name.size());
    394  }
    395 
    396  SECStatus rv = PK11_ImportDERPrivateKeyInfo(
    397      slot.get(), &pkcs8PrivKeyItem,
    398      nickname.data == nullptr ? nullptr : &nickname, nullptr /*publicValue*/,
    399      true /*isPerm*/, false /*isPrivate*/, KU_ALL, nullptr);
    400  if (rv != SECSuccess) {
    401    std::cerr << "Importing a private key in DER format failed with error "
    402              << PR_ErrorToName(PR_GetError()) << std::endl;
    403    return false;
    404  }
    405 
    406  std::cout << "Key import succeeded." << std::endl;
    407  return true;
    408 }
    409 
    410 bool DBTool::DeleteCert(const ArgParser &parser) {
    411  std::string certName = parser.Get("--delete-cert");
    412  if (certName.empty()) {
    413    std::cerr << "A name is required to delete a certificate." << std::endl;
    414    Usage();
    415    return false;
    416  }
    417 
    418  ScopedCERTCertificate cert(CERT_FindCertByNicknameOrEmailAddr(
    419      CERT_GetDefaultCertDB(), certName.c_str()));
    420  if (!cert) {
    421    std::cerr << "Could not find certificate with name " << certName << "."
    422              << std::endl;
    423    return false;
    424  }
    425 
    426  SECStatus rv = SEC_DeletePermCertificate(cert.get());
    427  if (rv != SECSuccess) {
    428    std::cerr << "Unable to delete certificate with name " << certName << "."
    429              << std::endl;
    430    return false;
    431  }
    432 
    433  std::cout << "Certificate with name " << certName << " deleted successfully."
    434            << std::endl;
    435  return true;
    436 }
    437 
    438 bool DBTool::DeleteKey(const ArgParser &parser) {
    439  std::string keyName = parser.Get("--delete-key");
    440  if (keyName.empty()) {
    441    std::cerr << "A name is required to delete a key." << std::endl;
    442    Usage();
    443    return false;
    444  }
    445 
    446  ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot());
    447  if (slot.get() == nullptr) {
    448    std::cerr << "Error: Init PK11SlotInfo failed!" << std::endl;
    449    return false;
    450  }
    451 
    452  if (!DBLoginIfNeeded(slot)) {
    453    return false;
    454  }
    455 
    456  ScopedSECKEYPrivateKeyList list(PK11_ListPrivKeysInSlot(
    457      slot.get(), const_cast<char *>(keyName.c_str()), nullptr));
    458  if (list.get() == nullptr) {
    459    std::cerr << "Fetching private keys with nickname " << keyName
    460              << " failed with error " << PR_ErrorToName(PR_GetError())
    461              << std::endl;
    462    return false;
    463  }
    464 
    465  unsigned int foundKeys = 0, deletedKeys = 0;
    466  SECKEYPrivateKeyListNode *node;
    467  for (node = PRIVKEY_LIST_HEAD(list.get());
    468       !PRIVKEY_LIST_END(node, list.get()); node = PRIVKEY_LIST_NEXT(node)) {
    469    SECKEYPrivateKey *privKey = node->key;
    470    foundKeys++;
    471    // see PK11_DeleteTokenPrivateKey for example usage
    472    // calling PK11_DeleteTokenPrivateKey directly does not work because it also
    473    // destroys the SECKEYPrivateKey (by calling SECKEY_DestroyPrivateKey) -
    474    // then SECKEY_DestroyPrivateKeyList does not
    475    // work because it also calls SECKEY_DestroyPrivateKey
    476    SECStatus rv =
    477        PK11_DestroyTokenObject(privKey->pkcs11Slot, privKey->pkcs11ID);
    478    if (rv == SECSuccess) {
    479      deletedKeys++;
    480    }
    481  }
    482 
    483  if (foundKeys > deletedKeys) {
    484    std::cerr << "Some keys could not be deleted." << std::endl;
    485  }
    486 
    487  if (deletedKeys > 0) {
    488    std::cout << "Found " << foundKeys << " keys." << std::endl;
    489    std::cout << "Successfully deleted " << deletedKeys
    490              << " key(s) with nickname " << keyName << "." << std::endl;
    491  } else {
    492    std::cout << "No key with nickname " << keyName << " found to delete."
    493              << std::endl;
    494  }
    495 
    496  return true;
    497 }