Mercurial > trustbridge
comparison cinst/nss-installer.c @ 1175:e210ecc32d69
(issue128) Rename mozilla process to trustbridge-nss-installer
author | Andre Heinecke <andre.heinecke@intevation.de> |
---|---|
date | Mon, 22 Sep 2014 11:19:43 +0200 |
parents | cinst/mozilla.c@1e429faf7c84 |
children | 12ed0b72e9f5 |
comparison
equal
deleted
inserted
replaced
1174:7175d117e69a | 1175:e210ecc32d69 |
---|---|
1 /* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik | |
2 * Software engineering by Intevation GmbH | |
3 * | |
4 * This file is Free Software under the GNU GPL (v>=2) | |
5 * and comes with ABSOLUTELY NO WARRANTY! | |
6 * See LICENSE.txt for details. | |
7 */ | |
8 /** | |
9 * @file | |
10 * @brief NSS store certificate installation process | |
11 * | |
12 * Reads from a file given on command line or stdin a list of | |
13 * instructions in the form: | |
14 * | |
15 * I:\<base64 DER econded certificate\> <BR> | |
16 * R:\<base64 DER econded certificate\> | |
17 * ... | |
18 * | |
19 * With one instruction per line. the maximum size of an input | |
20 * line is 9999 characters (including the \\r\\n) at the end of the line. | |
21 * | |
22 * Certificates marked with I: will be installed and the ones | |
23 * marked with R: will be searched and if available removed from | |
24 * the databases. | |
25 * | |
26 * This tool tries to find all NSS databases the user has | |
27 * access to and to execute the instructions on all of them. | |
28 * | |
29 * If the tool is executed with a UID of 0 or with admin privileges under | |
30 * windows it will not look into the user directories but instead try | |
31 * to write the system wide defaults. | |
32 * | |
33 * If there are other processes accessing the databases the caller | |
34 * has to ensure that those are terminated before this process is | |
35 * executed. | |
36 * | |
37 * If the same certificate is marked to be installed and to be removed | |
38 * in one call the behavior is undefined. This should be avoided and | |
39 * may lead to errors. | |
40 * | |
41 * Returns 0 on success (Even when no stores where found) an error value | |
42 * as defined in errorcodes.h otherwise. | |
43 * | |
44 * Success messages are written to stdout. Errors to stderr. For logging | |
45 * purposes each installation / removal of a certificate will be reported | |
46 * with the profile name that it modified. | |
47 * | |
48 * To get more verbose output add the --debug parameter | |
49 * as the last parameter on the command line. | |
50 * | |
51 */ | |
52 | |
53 /** | |
54 * @brief Needs to be defined to get strnlen() | |
55 */ | |
56 #define _POSIX_C_SOURCE 200809L | |
57 | |
58 /* REMOVEME: */ | |
59 #include <unistd.h> | |
60 | |
61 #include <cert.h> | |
62 #include <certdb.h> | |
63 #include <certt.h> | |
64 #include <dirent.h> | |
65 #include <nss.h> | |
66 #include <pk11pub.h> | |
67 #include <secerr.h> | |
68 #include <stdbool.h> | |
69 #include <stdio.h> | |
70 #include <stdlib.h> | |
71 #include <string.h> | |
72 #include <sys/types.h> | |
73 #include <sys/stat.h> | |
74 | |
75 #define DEBUGPREFIX "MOZ-" | |
76 #include "logging.h" | |
77 | |
78 #include "certhelp.h" | |
79 #include "errorcodes.h" | |
80 #include "portpath.h" | |
81 #include "strhelp.h" | |
82 #include "nss-secitemlist.h" | |
83 #include "util.h" | |
84 | |
85 #ifndef _WIN32 | |
86 #define CONFDIRS ".mozilla", ".thunderbird" | |
87 /* Default installation directory of ubuntu 14.4 is respected */ | |
88 #define MOZILLA_DEFAULTS "/usr/lib/thunderbird/defaults", "/usr/lib/firefox/browser/defaults" | |
89 #define MOZILLA_DBNAMES "cert8.db", "key3.db", "secmod.db" | |
90 #define NSSSHARED ".pki/nssdb" | |
91 #define NSSSHARED_GLOBAL "/etc/skel/.pki/nssdb" | |
92 #define TARGET_LINUX 1 | |
93 #define DIRSEP "/" | |
94 #else | |
95 #define MOZILLA_DEFAULTS "Mozilla Firefox\\browser\\defaults", "Mozilla Thunderbird\\defaults" | |
96 #define MOZILLA_DBNAMES NULL | |
97 #define CONFDIRS "Mozilla", "Thunderbird" | |
98 #define NSSSHARED "" | |
99 #define TARGET_LINUX NULL | |
100 #define DIRSEP "\\" | |
101 #endif | |
102 | |
103 /** | |
104 * @brief Length of string buffers used | |
105 * | |
106 * The maximal length of input is defined as 9999 (+ terminating \0). | |
107 * We use it for other other input puffers besides the IPC input, too. | |
108 * (One size fits all). | |
109 */ | |
110 #define LINEBUFLEN 10000 | |
111 | |
112 #ifdef _WIN32 | |
113 #define STRTOK_R strtok_s | |
114 #else | |
115 #define STRTOK_R strtok_r | |
116 #endif | |
117 | |
118 /** | |
119 * @brief Global Return Code | |
120 * | |
121 * This will be retuned by the programm and might be set to an | |
122 * error code on fatal errors and to and warning code on non-fatal | |
123 * errors. In case of mor than one warning the warning codes will be | |
124 * ORed together. | |
125 */ | |
126 int exit_code = 0; | |
127 | |
128 /** | |
129 * @brief Return configuration base directory. | |
130 * @returns A pointer to a string containing the path to the base | |
131 * directory holding the configuration directories for e.g. mozilla | |
132 * and thunderbird. | |
133 */ | |
134 static char * | |
135 get_conf_basedir() | |
136 { | |
137 char *cdir, *envvar; | |
138 | |
139 if (TARGET_LINUX) | |
140 envvar = "HOME" ; | |
141 else | |
142 envvar = "APPDATA"; | |
143 | |
144 if ((cdir = getenv(envvar)) != NULL) | |
145 return cdir; | |
146 else | |
147 { | |
148 ERRORPRINTF("FATAL! No %s in environment.\n", envvar); | |
149 exit(ERR_MOZ_HOMELESS); | |
150 } | |
151 } | |
152 | |
153 /** | |
154 * @brief Get a list of all mozilla profile directories | |
155 * | |
156 * Parse the profiles.ini and extract all profile paths from that. | |
157 * The expected data is in the form: | |
158 * | |
159 * [Profile99]<BR> | |
160 * IsRelative=1<BR> | |
161 * Path=Example/foo.bar | |
162 * | |
163 * or<BR> | |
164 * [Profile0]<BR> | |
165 * IsRelative=0<BR> | |
166 * Path=c:\\foo\\bar\\baz | |
167 * | |
168 * Mozilla also accepts the ini file on Windows even if it is UTF-16 | |
169 * encoded but never writes UTF-16 on its own. So currently we ignore | |
170 * this special case. | |
171 * | |
172 * @param[in] inifile_name path of the profile.ini to read. | |
173 * @return NULL terminated array of strings containing containing the | |
174 * absolute path of the profile directories. The array needs to | |
175 * be freed by the caller. | |
176 */ | |
177 static char ** | |
178 get_profile_dirs (char *inifile_name) | |
179 { | |
180 char **dirs = NULL; | |
181 char *inifile_dirname; | |
182 FILE *inifile; | |
183 char line[LINEBUFLEN]; | |
184 char *key; | |
185 char *value; | |
186 char *path = NULL; | |
187 char *fqpath; | |
188 bool inprofile = false; | |
189 bool relative_path = false; | |
190 char *saveptr; | |
191 | |
192 if ((inifile = fopen(inifile_name, "r")) != NULL) | |
193 { | |
194 DEBUGPRINTF("Searching for profile paths in: '%s'\n", inifile_name); | |
195 | |
196 inifile_dirname = port_dirname(inifile_name); | |
197 while (fgets(line, LINEBUFLEN, inifile) != NULL) | |
198 { | |
199 /* Determine if we are in an profile section */ | |
200 if (str_starts_with(line, "[Profile")) | |
201 { | |
202 relative_path = false; | |
203 inprofile = true; | |
204 } | |
205 else if (line[0] == '[') | |
206 inprofile = false; | |
207 | |
208 /* If we are in a profile parse path related stuff */ | |
209 if (inprofile) | |
210 { | |
211 saveptr = NULL; | |
212 key = STRTOK_R(line, "=", &saveptr); | |
213 value = STRTOK_R(NULL, "=", &saveptr); | |
214 str_trim(&value); | |
215 if (str_equal(key, "Path")) | |
216 { | |
217 if (relative_path) | |
218 xasprintf(&path, "%s/%s", inifile_dirname, value); | |
219 else | |
220 xasprintf(&path, "%s", value); | |
221 if ((fqpath = port_realpath(path)) != NULL) | |
222 { | |
223 DEBUGPRINTF("Found profile path: '%s'\n", fqpath); | |
224 strv_append(&dirs, fqpath, strlen(fqpath)); | |
225 free (fqpath); | |
226 } | |
227 else | |
228 { | |
229 DEBUGPRINTF("WARN! Non existent profile path: '%s'\n", path); | |
230 exit_code |= WARN_MOZ_PROFILE_DOES_NOT_EXIST; | |
231 } | |
232 free(path); | |
233 } | |
234 else if (str_equal(key, "IsRelative") && | |
235 str_starts_with(value, "1")) | |
236 relative_path = true; | |
237 } | |
238 } | |
239 fclose(inifile); | |
240 } | |
241 else | |
242 { | |
243 DEBUGPRINTF("WARN! Could not open ini file: '%s'\n", inifile_name); | |
244 exit_code |= WARN_MOZ_FAILED_TO_OPEN_INI; | |
245 } | |
246 return dirs; | |
247 } | |
248 | |
249 /** | |
250 * @brief Search for mozilla profiles.ini files | |
251 * | |
252 * Use well known paths and heuristics to find the current users | |
253 * profiles.ini files on GNU/Linux and Windows systems. | |
254 * | |
255 * @return NULL terminated array of strings containing the absolute | |
256 * path of the profiles.ini files. The array needs to be freed by the | |
257 * caller. | |
258 */ | |
259 static char ** | |
260 get_profile_inis () | |
261 { | |
262 char **inis = NULL; | |
263 char *mozpath, *fqpath, *subpath, *ppath; | |
264 DIR *mozdir; | |
265 struct dirent *mozdirent; | |
266 char *confbase = get_conf_basedir(); | |
267 const char *confdirs[] = { CONFDIRS, NULL }; | |
268 | |
269 for (int i=0; confdirs[i] != NULL; i++) | |
270 { | |
271 xasprintf(&mozpath,"%s/%s", confbase, confdirs[i]); | |
272 if ((mozdir = opendir(mozpath)) != NULL) | |
273 { | |
274 while ((mozdirent = readdir(mozdir)) != NULL) | |
275 { | |
276 xasprintf(&subpath, "%s/%s/%s", | |
277 confbase, | |
278 confdirs[i], | |
279 mozdirent->d_name); | |
280 if (port_isdir(subpath) | |
281 && (strcmp(mozdirent->d_name, "..") != 0)) | |
282 { | |
283 xasprintf(&ppath, "%s/%s/%s/%s", | |
284 confbase, | |
285 confdirs[i], | |
286 mozdirent->d_name, | |
287 "profiles.ini"); | |
288 DEBUGPRINTF("checking for %s...\n", ppath); | |
289 if ((fqpath = port_realpath(ppath)) != NULL) | |
290 { | |
291 strv_append(&inis, fqpath, strlen(fqpath)); | |
292 DEBUGPRINTF("Found mozilla ini file: '%s'\n", fqpath); | |
293 free(fqpath); | |
294 } | |
295 free(ppath); | |
296 } | |
297 free(subpath); | |
298 } | |
299 closedir(mozdir); | |
300 } | |
301 else | |
302 { | |
303 DEBUGPRINTF("Could not open %s/%s\n", confbase, confdirs[i]); | |
304 } | |
305 free(mozpath); | |
306 } | |
307 if (inis == NULL) | |
308 { | |
309 DEBUGPRINTF("No ini files found - will do nothing!\n"); | |
310 } | |
311 return inis; | |
312 } | |
313 | |
314 | |
315 /** @brief make the default nss databases readable. | |
316 * | |
317 * This uses the static paths definied in this code to ensure | |
318 * that only the defaults are touched. | |
319 * | |
320 */ | |
321 #ifndef WIN32 | |
322 static void | |
323 make_defaults_readable() | |
324 { | |
325 const char *confdirs[] = { MOZILLA_DEFAULTS, NULL }; | |
326 const char *filenames[] = { MOZILLA_DBNAMES, NULL }; | |
327 | |
328 mode_t access_mask = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; | |
329 | |
330 for (int i=0; confdirs[i] != NULL; i++) | |
331 { | |
332 for (int j=0; filenames[j] != NULL; j++) | |
333 { | |
334 char *realpath = NULL, | |
335 *path = NULL; | |
336 xasprintf (&path, "%s/profile/%s", confdirs[i], filenames[j]); | |
337 realpath = port_realpath(path); | |
338 xfree(path); | |
339 if (!realpath) | |
340 { | |
341 syslog_error_printf("Failed to find %s \n", realpath); | |
342 continue; | |
343 } | |
344 if (chmod(realpath, access_mask)) | |
345 { | |
346 syslog_error_printf("Failed to set access_mask on file.\n"); | |
347 } | |
348 xfree (realpath); | |
349 } | |
350 } | |
351 } | |
352 #endif | |
353 | |
354 /** | |
355 * @brief Collect the default profile directories for mozilla software | |
356 * | |
357 * If the default directory is found but not the profiles subdirectory | |
358 * this will create the profiles subdirectory. | |
359 * | |
360 * @return NULL terminated array of strings containing the absolute path | |
361 * to the default profile directories. Needs to be freed by the caller. | |
362 */ | |
363 static char** | |
364 get_default_profile_dirs() | |
365 { | |
366 char **retval = NULL; | |
367 | |
368 const char *confdirs[] = { MOZILLA_DEFAULTS, NULL }; | |
369 | |
370 #ifdef _WIN32 | |
371 char *program_files = get_program_files_folder(); | |
372 if (!program_files) | |
373 { | |
374 ERRORPRINTF ("Failed to look up program files folder.\n"); | |
375 return NULL; | |
376 } | |
377 #endif | |
378 | |
379 for (int i=0; confdirs[i] != NULL; i++) | |
380 { | |
381 char *realpath = NULL, | |
382 *profile_dir = NULL; | |
383 #ifndef _WIN32 | |
384 realpath = port_realpath(confdirs[i]); | |
385 #else | |
386 /* As on linux we only respect the default installation directory | |
387 mozilla firefox and thunderbird change their registry key with | |
388 each version as the key includes the version number. It would | |
389 be error prone to search the system for every instance. So we | |
390 only check the default installation directories. */ | |
391 xasprintf(&realpath, "%s" DIRSEP "%s", program_files, confdirs[i]); | |
392 #endif | |
393 if (realpath == NULL) | |
394 { | |
395 DEBUGPRINTF ("Did not find directory: '%s'\n", confdirs[i]); | |
396 continue; | |
397 } | |
398 xasprintf(&profile_dir, "%s" DIRSEP "profile", realpath); | |
399 xfree(realpath); | |
400 if (port_isdir(profile_dir)) | |
401 { | |
402 DEBUGPRINTF("Found default directory: '%s'\n", profile_dir); | |
403 /* All is well */ | |
404 strv_append (&retval, profile_dir, strlen(profile_dir)); | |
405 xfree(profile_dir); | |
406 profile_dir = NULL; | |
407 continue; | |
408 } | |
409 else | |
410 { | |
411 /* Create the directory */ | |
412 if (port_fileexits(profile_dir)) | |
413 { | |
414 DEBUGPRINTF ("Path: '%s' is not a directory but it exists. Skipping.\n", | |
415 profile_dir); | |
416 xfree(profile_dir); | |
417 profile_dir = NULL; | |
418 continue; | |
419 } | |
420 else | |
421 { | |
422 /* Lets create it */ | |
423 if (!port_mkdir_p(profile_dir, true)) | |
424 { | |
425 ERRORPRINTF ("Failed to create directory: '%s'\n", profile_dir); | |
426 xfree(profile_dir); | |
427 profile_dir = NULL; | |
428 continue; | |
429 } | |
430 strv_append (&retval, profile_dir, strlen(profile_dir)); | |
431 xfree(profile_dir); | |
432 profile_dir = NULL; | |
433 } | |
434 } | |
435 } | |
436 #ifdef WIN32 | |
437 xfree (program_files); | |
438 #endif | |
439 return retval; | |
440 } | |
441 | |
442 /** | |
443 * @brief Collect all mozilla profile directories of current user. | |
444 * @return NULL terminated array of strings containing the absolute | |
445 * path of the profile directories. The array needs to be freed by the | |
446 * caller. | |
447 */ | |
448 static char** | |
449 get_all_nssdb_dirs() | |
450 { | |
451 char **mozinis, **pdirs; | |
452 char **alldirs = NULL; | |
453 | |
454 if (is_elevated()) | |
455 { | |
456 #ifndef _WIN32 | |
457 /* NSS Shared db does not exist under windows. */ | |
458 if (!port_mkdir_p(NSSSHARED_GLOBAL, false)) | |
459 { | |
460 ERRORPRINTF("Failed to create nssshared skeleton directory. \n"); | |
461 } | |
462 else | |
463 { | |
464 strv_append(&alldirs, "sql:" NSSSHARED_GLOBAL, strlen("sql:" NSSSHARED_GLOBAL)); | |
465 } | |
466 #endif | |
467 pdirs = get_default_profile_dirs(); | |
468 if (pdirs != NULL) | |
469 { | |
470 for (int i=0; pdirs[i] != NULL; i++) | |
471 { | |
472 strv_append(&alldirs, pdirs[i], strlen(pdirs[i])); | |
473 } | |
474 strv_free(pdirs); | |
475 } | |
476 return alldirs; | |
477 } | |
478 /* Search Mozilla/Firefox/Thunderbird profiles */ | |
479 if ((mozinis = get_profile_inis()) != NULL) | |
480 { | |
481 for (int i=0; mozinis[i] != NULL; i++) | |
482 { | |
483 pdirs = | |
484 get_profile_dirs(mozinis[i]); | |
485 if (pdirs != NULL) | |
486 { | |
487 for (int i=0; pdirs[i] != NULL; i++) | |
488 { | |
489 strv_append(&alldirs, pdirs[i], strlen(pdirs[i])); | |
490 } | |
491 strv_free(pdirs); | |
492 } | |
493 } | |
494 strv_free(mozinis); | |
495 } | |
496 /* Search for NSS shared DB (used by Chrome/Chromium on GNU/Linux) */ | |
497 if (TARGET_LINUX) | |
498 { | |
499 char *path, *fqpath, *sqlpath; | |
500 xasprintf(&path, "%s/%s", get_conf_basedir(), NSSSHARED); | |
501 if ((fqpath = port_realpath(path)) != NULL) | |
502 { | |
503 xasprintf(&sqlpath, "sql:%s", fqpath); | |
504 strv_append(&alldirs, sqlpath, strlen(sqlpath)); | |
505 free(sqlpath); | |
506 free(fqpath); | |
507 } | |
508 free(path); | |
509 } | |
510 return alldirs; | |
511 } | |
512 | |
513 #ifdef DEBUGOUTPUT | |
514 /** | |
515 * @brief list certificates from nss certificate store | |
516 * @param[in] confdir the directory with the certificate store | |
517 */ | |
518 static void | |
519 DEBUG_nss_list_certs (char *confdir) | |
520 { | |
521 CERTCertList *list; | |
522 CERTCertListNode *node; | |
523 char *name; | |
524 | |
525 if (NSS_Initialize(confdir, "", "", "secmod.db", NSS_INIT_READONLY) | |
526 == SECSuccess) | |
527 { | |
528 DEBUGPRINTF("Listing certs in \"%s\"\n", confdir); | |
529 list = PK11_ListCerts(PK11CertListAll, NULL); | |
530 for (node = CERT_LIST_HEAD(list); !CERT_LIST_END(node, list); | |
531 node = CERT_LIST_NEXT(node)) | |
532 { | |
533 name = node->appData; | |
534 | |
535 DEBUGPRINTF("Found certificate \"%s\"\n", name); | |
536 } | |
537 /* According to valgrind this leaks memory in the list. | |
538 We could not find API documentation to better free this | |
539 so we accept the leakage here in case of debug. */ | |
540 CERT_DestroyCertList(list); | |
541 NSS_Shutdown(); | |
542 } | |
543 else | |
544 { | |
545 DEBUGPRINTF("Could not open nss certificate store in %s!\n", confdir); | |
546 } | |
547 } | |
548 #endif | |
549 | |
550 /** | |
551 * @brief Create a string with the name for cert in SECItem. | |
552 * | |
553 * Should be freed by caller. | |
554 * @param[in] secitemp ponts to an SECItem holding the DER certificate. | |
555 * @returns a string of the from "CN of Subject - O of Subject" | |
556 */ | |
557 static char * | |
558 nss_cert_name(SECItem *secitemp) | |
559 { | |
560 char *cn_str, *o_str, *name; | |
561 size_t name_len; | |
562 cn_str = x509_parse_subject(secitemp->data, secitemp->len, CERT_OID_CN); | |
563 o_str = x509_parse_subject(secitemp->data, secitemp->len, CERT_OID_O); | |
564 if (!cn_str || !o_str) | |
565 { | |
566 ERRORPRINTF("FATAL: Could not parse certificate!"); | |
567 exit(ERR_INVALID_CERT); | |
568 } | |
569 name_len = strlen(cn_str) + strlen(o_str) + 4; | |
570 name = (char *)xmalloc(name_len); | |
571 snprintf(name, name_len, "%s - %s", cn_str, o_str); | |
572 free(cn_str); | |
573 free(o_str); | |
574 return name; | |
575 } | |
576 | |
577 /** | |
578 * @brief Convert a base64 encoded DER certificate to SECItem | |
579 * @param[in] b64 pointer to the base64 encoded certificate | |
580 * @param[in] b64len length of the base64 encoded certificate | |
581 * @param[out] secitem pointer to the SECItem in which to store the | |
582 * raw DER certifiacte. | |
583 * @returns true on success and false on failure | |
584 */ | |
585 static bool | |
586 base64_to_secitem(char *b64, size_t b64len, SECItem *secitem) | |
587 { | |
588 unsigned char *dercert = NULL; | |
589 size_t dercertlen; | |
590 | |
591 if ((str_base64_decode((char **)(&dercert), &dercertlen, | |
592 b64, b64len) == 0) && | |
593 (dercertlen > 0)) | |
594 { | |
595 secitem->data = dercert; | |
596 secitem->len = (unsigned int) dercertlen; | |
597 return true; | |
598 } | |
599 else | |
600 { | |
601 DEBUGPRINTF("Base64 decode failed for: %s\n", b64); | |
602 } | |
603 return false; | |
604 } | |
605 | |
606 /** | |
607 * @brief Store DER certificate in mozilla store. | |
608 * @param[in] pdir the mozilla profile directory with the certificate | |
609 * store to manipulate. | |
610 * @param[in] dercert pointer to a SECItem holding the DER certificate | |
611 * to install | |
612 * @returns true on success and false on failure | |
613 */ | |
614 static bool | |
615 import_cert(char *pdir, SECItem *dercert) | |
616 { | |
617 PK11SlotInfo *pk11slot = NULL; | |
618 CERTCertTrust *trust = NULL; | |
619 CERTCertificate *cert = NULL; | |
620 bool success = false; | |
621 char *cert_name = nss_cert_name(dercert); | |
622 | |
623 DEBUGPRINTF("INSTALLING cert: '%s' to: %s\n", cert_name, pdir); | |
624 pk11slot = PK11_GetInternalKeySlot(); | |
625 cert = CERT_DecodeCertFromPackage((char *)dercert->data, | |
626 (int)dercert->len); | |
627 trust = (CERTCertTrust *)xmalloc(sizeof(CERTCertTrust)); | |
628 CERT_DecodeTrustString(trust, "C,C,C"); | |
629 if (PK11_ImportCert(pk11slot, cert, CK_INVALID_HANDLE, | |
630 cert_name, PR_FALSE) == SECSuccess) | |
631 { | |
632 if(CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), cert, trust) == SECSuccess) | |
633 { | |
634 log_certificate_der (pdir, dercert->data, dercert->len, true); | |
635 success = true; | |
636 } | |
637 } | |
638 /* This could have happened on either the import cert or | |
639 the cert change trust. If Import Cert fails with that | |
640 error the certificate has in fact been added but with | |
641 random trist bits. See NSS Bug 595861. | |
642 Reference code can be found in gnome evolution under | |
643 smime/lib/e-cert-db.c */ | |
644 if(PORT_GetError() == SEC_ERROR_TOKEN_NOT_LOGGED_IN) | |
645 { | |
646 if (PK11_NeedUserInit (pk11slot)) | |
647 { | |
648 PK11_InitPin (pk11slot, "", ""); | |
649 } | |
650 if (PK11_Authenticate (pk11slot, PR_TRUE, NULL) != SECSuccess) | |
651 { | |
652 DEBUGPRINTF("Failed to authenticate.\n"); | |
653 } | |
654 else if(CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), cert, trust) == SECSuccess) | |
655 { | |
656 log_certificate_der (pdir, dercert->data, dercert->len, true); | |
657 success = true; | |
658 } | |
659 } | |
660 | |
661 if (!success) | |
662 { | |
663 DEBUGPRINTF("Failed to install certificate '%s' to '%s'!\n", cert_name, pdir); | |
664 ERRORPRINTF("Error installing certificate err: %i\n", PORT_GetError()); | |
665 } | |
666 CERT_DestroyCertificate (cert); | |
667 free(trust); | |
668 PK11_FreeSlot(pk11slot); | |
669 | |
670 free(cert_name); | |
671 return success; | |
672 } | |
673 | |
674 /** | |
675 * @brief Remove DER certificate from mozilla store. | |
676 * @param[in] pdir the mozilla profile directory with the certificate | |
677 * store to manipulate. | |
678 * @param[in] dercert pointer to a SECItem holding the DER certificate | |
679 * to remove | |
680 * @returns true on success and false on failure | |
681 */ | |
682 static bool | |
683 remove_cert(char *pdir, SECItem *dercert) | |
684 { | |
685 PK11SlotInfo *pk11slot = NULL; | |
686 bool success = false; | |
687 char *cert_name = nss_cert_name(dercert); | |
688 CERTCertificate *cert = NULL; | |
689 | |
690 DEBUGPRINTF("REMOVING cert: '%s' from: %s\n", cert_name, pdir); | |
691 if (NSS_Initialize(pdir, "", "", "secmod.db", 0) == SECSuccess) | |
692 { | |
693 pk11slot = PK11_GetInternalKeySlot(); | |
694 cert = PK11_FindCertFromDERCertItem(pk11slot, | |
695 dercert, NULL); | |
696 if (cert != NULL) | |
697 { | |
698 if (SEC_DeletePermCertificate(cert) == SECSuccess) | |
699 { | |
700 success = true; | |
701 log_certificate_der (pdir, dercert->data, dercert->len, false); | |
702 } | |
703 else | |
704 { | |
705 DEBUGPRINTF("Failed to remove certificate '%s' from '%s'!\n", cert_name, pdir); | |
706 } | |
707 CERT_DestroyCertificate(cert); | |
708 } | |
709 else | |
710 { | |
711 DEBUGPRINTF("Could not find Certificate '%s' in store '%s'.\n", cert_name, pdir); | |
712 } | |
713 PK11_FreeSlot(pk11slot); | |
714 NSS_Shutdown(); | |
715 } | |
716 else | |
717 { | |
718 DEBUGPRINTF("Could not open nss certificate store in %s!\n", pdir); | |
719 } | |
720 free(cert_name); | |
721 return success; | |
722 } | |
723 | |
724 /** | |
725 * @brief Apply a function to a list of certificates and profiles | |
726 * | |
727 * The function must have the signature: | |
728 * | |
729 * bool function(char *pdir, SECItem der_cert) | |
730 * | |
731 * where pdir is the path of an profile and der_cert is an raw DER | |
732 * formatted certificate. The function must return true on success | |
733 * and false on failure. | |
734 * | |
735 * This function is intended for use with the import_cert and | |
736 * remove_cert functions. | |
737 * | |
738 * @param[in] fn the function to apply | |
739 * @param[inout] certs a secitem list holding the certificates | |
740 * the list will be change (emptied)! | |
741 * @param[in] pdirs the NULL terminated list of profile directories | |
742 * @returns true on success and false on failure | |
743 */ | |
744 bool | |
745 apply_to_certs_and_profiles(bool fn(char *, SECItem *), | |
746 seciteml_t **certs, char **pdirs) | |
747 { | |
748 bool success = true; | |
749 | |
750 for (int i=0; pdirs[i] != NULL; i++) | |
751 { | |
752 seciteml_t *iter = *certs; | |
753 if (NSS_Initialize(pdirs[i], "", "", "secmod.db", 0) != SECSuccess) | |
754 { | |
755 DEBUGPRINTF("Could not open nss certificate store in %s!\n", pdirs[i]); | |
756 continue; | |
757 } | |
758 | |
759 while (iter != NULL && iter->item != NULL) | |
760 { | |
761 SECItem *cert = iter->item; | |
762 if (! (*fn)(pdirs[i], cert)) | |
763 success = false; | |
764 iter = iter->next; | |
765 } | |
766 NSS_Shutdown(); | |
767 } | |
768 | |
769 seciteml_free(certs); | |
770 | |
771 return success; | |
772 } | |
773 | |
774 /** | |
775 * @brief Parse IPC commands from standard input. | |
776 * | |
777 * Reads command lines (R: and I:) from standard input and puts the | |
778 * certificates to process in two SECItem lists holding the | |
779 * certificates in DER format. | |
780 * @param[inout] stream from standard input | |
781 * @param[inout] install_list list of SECItems with certifiactes to install | |
782 * @param[inout] remove_list list of SECItems with certifiactes to remove | |
783 */ | |
784 static void | |
785 parse_commands (FILE *stream, | |
786 seciteml_t **install_list, seciteml_t **remove_list) | |
787 { | |
788 char inpl[LINEBUFLEN]; | |
789 size_t inpllen; | |
790 bool parserr = true; | |
791 SECItem secitem; | |
792 | |
793 while ( fgets(inpl, LINEBUFLEN, stream) != NULL ) | |
794 { | |
795 inpllen = strnlen(inpl, LINEBUFLEN); | |
796 /* Validate input line: | |
797 * - must be (much) longer than 3 characters | |
798 * - must start with "*:" | |
799 */ | |
800 if ((inpllen > 3) && (inpl[1] == ':')) | |
801 /* Now parse Input */ | |
802 switch(inpl[0]) | |
803 { | |
804 case 'R': | |
805 parserr = true; | |
806 DEBUGPRINTF("Request to remove certificate: %s\n", &inpl[2]); | |
807 if (base64_to_secitem(&inpl[2], inpllen - 2, &secitem)) | |
808 { | |
809 seciteml_push(remove_list, &secitem); | |
810 parserr = false; | |
811 } | |
812 break; | |
813 case 'I': | |
814 parserr = true; | |
815 DEBUGPRINTF("Request to install certificate: %s\n", &inpl[2]); | |
816 if (base64_to_secitem(&inpl[2], inpllen - 2, &secitem)) | |
817 { | |
818 seciteml_push(install_list, &secitem); | |
819 parserr = false; | |
820 } | |
821 break; | |
822 default: | |
823 parserr = true; | |
824 } | |
825 else | |
826 { | |
827 parserr = true; | |
828 } | |
829 | |
830 if (parserr) | |
831 { | |
832 ERRORPRINTF("FATAL: Invalid input: %s\n", inpl); | |
833 exit(ERR_MOZ_INVALID_INPUT); | |
834 } | |
835 } | |
836 } | |
837 | |
838 #ifdef DO_RELEASE_BUILD | |
839 bool g_debug = false; | |
840 #else | |
841 bool g_debug = true; | |
842 #endif | |
843 | |
844 int | |
845 main (int argc, char **argv) | |
846 { | |
847 char **dbdirs; | |
848 seciteml_t *certs_to_remove = NULL; | |
849 seciteml_t *certs_to_add = NULL; | |
850 FILE *input_stream; | |
851 | |
852 switch (argc) | |
853 { | |
854 case 1: | |
855 DEBUGPRINTF("Opening STDIN for input...\n"); | |
856 input_stream = stdin; | |
857 break; | |
858 case 2: | |
859 if (strcmp(argv[1], "--debug") == 0) | |
860 { | |
861 g_debug = true; | |
862 DEBUGPRINTF("Opening STDIN for input...\n"); | |
863 input_stream = stdin; | |
864 break; | |
865 } | |
866 case 3: | |
867 DEBUGPRINTF("Opening %s for input...\n", argv[1]); | |
868 if ((input_stream = fopen(argv[1], "r")) == NULL) | |
869 { | |
870 ERRORPRINTF ("FATAL: Could not open %s for reading!\n", | |
871 argv[1]); | |
872 exit_code = ERR_MOZ_FAILED_TO_OPEN_INPUT; | |
873 goto exit; | |
874 } | |
875 if (argc == 3 && strcmp(argv[2], "--debug") == 0) | |
876 { | |
877 g_debug = true; | |
878 } | |
879 break; | |
880 default: | |
881 ERRORPRINTF("FATAL: Wrong number of arguments!\n"); | |
882 exit_code = ERR_MOZ_WRONG_ARGC; | |
883 goto exit; | |
884 } | |
885 | |
886 dbdirs = | |
887 get_all_nssdb_dirs(); | |
888 | |
889 if (dbdirs != NULL) | |
890 { | |
891 parse_commands(input_stream, &certs_to_add, &certs_to_remove); | |
892 | |
893 #ifdef DEBUGOUTPUT | |
894 DEBUGPRINTF("OLD List of installed certs:\n"); | |
895 for (int i=0; dbdirs[i] != NULL; i++) | |
896 DEBUG_nss_list_certs(dbdirs[i]); | |
897 #endif | |
898 | |
899 if (! apply_to_certs_and_profiles(remove_cert, &certs_to_remove, dbdirs)) | |
900 exit_code |= WARN_MOZ_COULD_NOT_REMOVE_CERT; | |
901 | |
902 if (! apply_to_certs_and_profiles(import_cert, &certs_to_add, dbdirs)) | |
903 exit_code |= WARN_MOZ_COULD_NOT_ADD_CERT; | |
904 | |
905 #ifdef DEBUGOUTPUT | |
906 DEBUGPRINTF("NEW List of installed certs:\n"); | |
907 for (int i=0; dbdirs[i] != NULL; i++) | |
908 DEBUG_nss_list_certs(dbdirs[i]); | |
909 #endif | |
910 | |
911 #ifndef WIN32 | |
912 if (is_elevated()) | |
913 { | |
914 make_defaults_readable(); | |
915 } | |
916 #endif | |
917 | |
918 strv_free(dbdirs); | |
919 } | |
920 | |
921 fclose(input_stream); | |
922 | |
923 exit: | |
924 exit(exit_code); | |
925 } |