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 }