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 "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 
HasSingleCommandArgument(const ArgParser & parser)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 
HasArgumentRequiringWriteAccess(const ArgParser & parser)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 
PrintFlags(unsigned int flags)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 
Usage()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 
Run(const std::vector<std::string> & arguments)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 
PathHasDBFiles(std::string path)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 
ListCertificates()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 
ImportCertificate(const ArgParser & parser)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 
ListKeys()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 
ImportKey(const ArgParser & parser)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 
DeleteCert(const ArgParser & parser)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 
DeleteKey(const ArgParser & parser)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 }
498