Bug 1892041 - Part 3: Update test exclusions. r=spidermonkey-reviewers,dminor
[gecko.git] / security / nss / nss-tool / db / dbtool.cc
blob5cd1f56083e66f08ef5233dc16b55d71131bf2b9
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/. */
5 #include "dbtool.h"
6 #include "argparse.h"
7 #include "nss_scoped_ptrs.h"
8 #include "util.h"
10 #include <iomanip>
11 #include <iostream>
12 #include <regex>
13 #include <sstream>
15 #include <cert.h>
16 #include <certdb.h>
17 #include <nss.h>
18 #include <pk11pub.h>
19 #include <prerror.h>
20 #include <prio.h>
22 const std::vector<std::string> kCommandArgs(
23 {"--create", "--list-certs", "--import-cert", "--list-keys", "--import-key",
24 "--delete-cert", "--delete-key", "--change-password"});
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;
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");
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";
43 if ((flags & CERTDB_TERMINAL_RECORD) && !(flags & CERTDB_TRUSTED)) {
44 ss << "p";
46 if (flags & CERTDB_TRUSTED_CA) {
47 ss << "C";
49 if (flags & CERTDB_TRUSTED_CLIENT_CA) {
50 ss << "T";
52 if (flags & CERTDB_TRUSTED) {
53 ss << "P";
55 if (flags & CERTDB_USER) {
56 ss << "u";
58 if (flags & CERTDB_SEND_WARN) {
59 ss << "w";
61 if (flags & CERTDB_INVISIBLE_CA) {
62 ss << "I";
64 if (flags & CERTDB_GOVT_APPROVED_CA) {
65 ss << "G";
67 return ss.str();
70 static const char *const keyTypeName[] = {"null", "rsa", "dsa", "fortezza",
71 "dh", "kea", "ec"};
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;
86 bool DBTool::Run(const std::vector<std::string> &arguments) {
87 ArgParser parser(arguments);
89 if (!HasSingleCommandArgument(parser)) {
90 Usage();
91 return false;
94 PRAccessHow how = PR_ACCESS_READ_OK;
95 bool readOnly = true;
96 if (HasArgumentRequiringWriteAccess(parser)) {
97 how = PR_ACCESS_WRITE_OK;
98 readOnly = false;
101 std::string initDir(".");
102 if (parser.Has("--path")) {
103 initDir = parser.Get("--path");
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;
111 std::cout << "Using database directory: " << initDir << std::endl
112 << std::endl;
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;
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;
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;
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;
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();
160 // shutdown nss
161 if (NSS_Shutdown() != SECSuccess) {
162 std::cerr << "NSS Shutdown failed!" << std::endl;
163 return false;
166 return ret;
169 bool DBTool::PathHasDBFiles(std::string path) {
170 std::regex certDBPattern("cert.*\\.db");
171 std::regex keyDBPattern("key.*\\.db");
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;
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;
190 (void)PR_CloseDir(dir);
191 return dbFileExists;
194 void DBTool::ListCertificates() {
195 ScopedCERTCertList list(PK11_ListCerts(PK11CertListAll, nullptr));
196 CERTCertListNode *node;
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;
206 for (node = CERT_LIST_HEAD(list); !CERT_LIST_END(node, list);
207 node = CERT_LIST_NEXT(node)) {
208 CERTCertificate *cert = node->cert;
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;
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 = ",,";
233 std::cout << std::setw(60) << std::left << name << " " << trusts
234 << std::endl;
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;
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");
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;
260 ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot());
261 if (slot.get() == nullptr) {
262 std::cerr << "Error: Init PK11SlotInfo failed!" << std::endl;
263 return false;
266 std::vector<uint8_t> certData = ReadInputData(derFilePath);
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;
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;
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;
290 std::cout << "Certificate import was successful!" << std::endl;
291 // TODO show information about imported certificate
292 return true;
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;
302 if (!DBLoginIfNeeded(slot)) {
303 return false;
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;
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 : "");
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;
329 if (keyName.empty()) {
330 keyName = "(none)"; // default value
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;
342 std::string keyID = StringToHex(keyIDItem);
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;
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;
357 if (count == 0) {
358 std::cout << "No keys found." << std::endl;
361 return true;
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");
371 ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot());
372 if (slot.get() == nullptr) {
373 std::cerr << "Error: Init PK11SlotInfo failed!" << std::endl;
374 return false;
377 if (!DBLoginIfNeeded(slot)) {
378 return false;
381 std::vector<uint8_t> privKeyData = ReadInputData(privKeyFilePath);
382 if (privKeyData.empty()) {
383 return false;
385 SECItem pkcs8PrivKeyItem = {
386 siBuffer, reinterpret_cast<unsigned char *>(privKeyData.data()),
387 static_cast<unsigned int>(privKeyData.size())};
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());
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;
406 std::cout << "Key import succeeded." << std::endl;
407 return true;
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;
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;
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;
433 std::cout << "Certificate with name " << certName << " deleted successfully."
434 << std::endl;
435 return true;
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;
446 ScopedPK11SlotInfo slot(PK11_GetInternalKeySlot());
447 if (slot.get() == nullptr) {
448 std::cerr << "Error: Init PK11SlotInfo failed!" << std::endl;
449 return false;
452 if (!DBLoginIfNeeded(slot)) {
453 return false;
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;
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++;
483 if (foundKeys > deletedKeys) {
484 std::cerr << "Some keys could not be deleted." << std::endl;
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;
496 return true;