aheinecke@404: /* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik aheinecke@404: * Software engineering by Intevation GmbH aheinecke@404: * aheinecke@404: * This file is Free Software under the GNU GPL (v>=2) aheinecke@404: * and comes with ABSOLUTELY NO WARRANTY! aheinecke@404: * See LICENSE.txt for details. aheinecke@404: */ wilde@121: /** wilde@121: * @file andre@1175: * @brief NSS store certificate installation process aheinecke@99: * wilde@315: * Reads from a file given on command line or stdin a list of wilde@315: * instructions in the form: aheinecke@99: * emanuel@1053: * I:\
emanuel@1053: * R:\ aheinecke@99: * ... aheinecke@99: * aheinecke@238: * With one instruction per line. the maximum size of an input emanuel@1053: * line is 9999 characters (including the \\r\\n) at the end of the line. aheinecke@99: * aheinecke@99: * Certificates marked with I: will be installed and the ones aheinecke@99: * marked with R: will be searched and if available removed from aheinecke@99: * the databases. aheinecke@99: * aheinecke@99: * This tool tries to find all NSS databases the user has aheinecke@99: * access to and to execute the instructions on all of them. aheinecke@99: * andre@975: * If the tool is executed with a UID of 0 or with admin privileges under andre@975: * windows it will not look into the user directories but instead try andre@975: * to write the system wide defaults. andre@975: * aheinecke@99: * If there are other processes accessing the databases the caller aheinecke@99: * has to ensure that those are terminated before this process is aheinecke@99: * executed. aheinecke@99: * aheinecke@238: * If the same certificate is marked to be installed and to be removed aheinecke@238: * in one call the behavior is undefined. This should be avoided and aheinecke@238: * may lead to errors. aheinecke@238: * aheinecke@99: * Returns 0 on success (Even when no stores where found) an error value aheinecke@99: * as defined in errorcodes.h otherwise. aheinecke@99: * aheinecke@99: * Success messages are written to stdout. Errors to stderr. For logging aheinecke@99: * purposes each installation / removal of a certificate will be reported aheinecke@99: * with the profile name that it modified. aheinecke@99: * andre@1060: * To get more verbose output add the --debug parameter andre@1060: * as the last parameter on the command line. andre@1060: * aheinecke@99: */ aheinecke@99: wilde@235: /** andre@975: * @brief Needs to be defined to get strnlen() wilde@235: */ wilde@235: #define _POSIX_C_SOURCE 200809L wilde@235: wilde@235: /* REMOVEME: */ wilde@235: #include wilde@235: wilde@269: #include wilde@269: #include wilde@269: #include wilde@173: #include wilde@224: #include wilde@224: #include andre@1012: #include wilde@119: #include wilde@119: #include wilde@119: #include wilde@119: #include wilde@173: #include andre@989: #include wilde@119: wilde@230: #define DEBUGPREFIX "MOZ-" aheinecke@252: #include "logging.h" wilde@230: wilde@261: #include "certhelp.h" wilde@226: #include "errorcodes.h" wilde@226: #include "portpath.h" wilde@226: #include "strhelp.h" wilde@244: #include "nss-secitemlist.h" andre@975: #include "util.h" wilde@228: wilde@113: #ifndef _WIN32 wilde@197: #define CONFDIRS ".mozilla", ".thunderbird" andre@975: /* Default installation directory of ubuntu 14.4 is respected */ andre@989: #define MOZILLA_DEFAULTS "/usr/lib/thunderbird/defaults", "/usr/lib/firefox/browser/defaults" andre@989: #define MOZILLA_DBNAMES "cert8.db", "key3.db", "secmod.db" wilde@308: #define NSSSHARED ".pki/nssdb" andre@988: #define NSSSHARED_GLOBAL "/etc/skel/.pki/nssdb" wilde@223: #define TARGET_LINUX 1 andre@985: #define DIRSEP "/" wilde@113: #else andre@985: #define MOZILLA_DEFAULTS "Mozilla Firefox\\browser\\defaults", "Mozilla Thunderbird\\defaults" andre@989: #define MOZILLA_DBNAMES NULL wilde@197: #define CONFDIRS "Mozilla", "Thunderbird" wilde@311: #define NSSSHARED "" andre@989: #define TARGET_LINUX NULL andre@985: #define DIRSEP "\\" wilde@113: #endif wilde@113: wilde@229: /** wilde@229: * @brief Length of string buffers used wilde@229: * wilde@229: * The maximal length of input is defined as 9999 (+ terminating \0). wilde@229: * We use it for other other input puffers besides the IPC input, too. wilde@229: * (One size fits all). wilde@229: */ wilde@229: #define LINEBUFLEN 10000 wilde@147: andre@909: #ifdef _WIN32 andre@909: #define STRTOK_R strtok_s andre@909: #else andre@909: #define STRTOK_R strtok_r andre@909: #endif andre@909: wilde@119: /** wilde@119: * @brief Global Return Code wilde@119: * wilde@119: * This will be retuned by the programm and might be set to an wilde@119: * error code on fatal errors and to and warning code on non-fatal wilde@119: * errors. In case of mor than one warning the warning codes will be wilde@119: * ORed together. wilde@119: */ wilde@317: int exit_code = 0; aheinecke@44: aheinecke@44: /** wilde@194: * @brief Return configuration base directory. wilde@194: * @returns A pointer to a string containing the path to the base wilde@194: * directory holding the configuration directories for e.g. mozilla wilde@194: * and thunderbird. wilde@180: */ wilde@180: static char * wilde@194: get_conf_basedir() wilde@180: { wilde@194: char *cdir, *envvar; wilde@180: wilde@223: if (TARGET_LINUX) wilde@194: envvar = "HOME" ; wilde@180: else wilde@194: envvar = "APPDATA"; wilde@180: wilde@194: if ((cdir = getenv(envvar)) != NULL) wilde@194: return cdir; wilde@194: else wilde@180: { andre@1060: ERRORPRINTF("FATAL! No %s in environment.\n", envvar); wilde@180: exit(ERR_MOZ_HOMELESS); wilde@180: } wilde@180: } wilde@180: wilde@180: /** wilde@119: * @brief Get a list of all mozilla profile directories aheinecke@44: * wilde@232: * Parse the profiles.ini and extract all profile paths from that. wilde@232: * The expected data is in the form: wilde@232: * emanuel@1053: * [Profile99]
emanuel@1053: * IsRelative=1
emanuel@1053: * Path=Example/foo.bar wilde@232: * emanuel@1053: * or
emanuel@1053: * [Profile0]
emanuel@1053: * IsRelative=0
emanuel@1053: * Path=c:\\foo\\bar\\baz wilde@232: * wilde@232: * Mozilla also accepts the ini file on Windows even if it is UTF-16 wilde@232: * encoded but never writes UTF-16 on its own. So currently we ignore wilde@232: * this special case. aheinecke@44: * wilde@121: * @param[in] inifile_name path of the profile.ini to read. aheinecke@44: * @return NULL terminated array of strings containing containing the aheinecke@44: * absolute path of the profile directories. The array needs to aheinecke@44: * be freed by the caller. aheinecke@44: */ wilde@119: static char ** wilde@119: get_profile_dirs (char *inifile_name) wilde@119: { wilde@119: char **dirs = NULL; wilde@147: char *inifile_dirname; wilde@119: FILE *inifile; wilde@147: char line[LINEBUFLEN]; wilde@147: char *key; wilde@147: char *value; wilde@320: char *path = NULL; wilde@147: char *fqpath; wilde@119: bool inprofile = false; wilde@147: bool relative_path = false; andre@909: char *saveptr; aheinecke@44: wilde@119: if ((inifile = fopen(inifile_name, "r")) != NULL) wilde@119: { wilde@228: DEBUGPRINTF("Searching for profile paths in: '%s'\n", inifile_name); wilde@175: wilde@147: inifile_dirname = port_dirname(inifile_name); wilde@147: while (fgets(line, LINEBUFLEN, inifile) != NULL) wilde@119: { wilde@147: /* Determine if we are in an profile section */ wilde@147: if (str_starts_with(line, "[Profile")) wilde@147: { wilde@147: relative_path = false; wilde@147: inprofile = true; wilde@147: } wilde@119: else if (line[0] == '[') wilde@119: inprofile = false; wilde@147: wilde@147: /* If we are in a profile parse path related stuff */ wilde@147: if (inprofile) wilde@147: { andre@909: saveptr = NULL; andre@909: key = STRTOK_R(line, "=", &saveptr); andre@909: value = STRTOK_R(NULL, "=", &saveptr); wilde@147: str_trim(&value); wilde@147: if (str_equal(key, "Path")) wilde@147: { wilde@147: if (relative_path) wilde@320: xasprintf(&path, "%s/%s", inifile_dirname, value); wilde@147: else andre@975: xasprintf(&path, "%s", value); wilde@147: if ((fqpath = port_realpath(path)) != NULL) wilde@147: { wilde@228: DEBUGPRINTF("Found profile path: '%s'\n", fqpath); wilde@147: strv_append(&dirs, fqpath, strlen(fqpath)); wilde@147: free (fqpath); wilde@147: } wilde@147: else wilde@175: { wilde@228: DEBUGPRINTF("WARN! Non existent profile path: '%s'\n", path); wilde@317: exit_code |= WARN_MOZ_PROFILE_DOES_NOT_EXIST; wilde@175: } wilde@320: free(path); wilde@147: } wilde@147: else if (str_equal(key, "IsRelative") && wilde@147: str_starts_with(value, "1")) wilde@147: relative_path = true; wilde@147: } wilde@119: } bernhard@179: fclose(inifile); wilde@119: } wilde@119: else wilde@119: { wilde@228: DEBUGPRINTF("WARN! Could not open ini file: '%s'\n", inifile_name); wilde@317: exit_code |= WARN_MOZ_FAILED_TO_OPEN_INI; wilde@119: } wilde@119: return dirs; wilde@119: } aheinecke@44: wilde@173: /** wilde@173: * @brief Search for mozilla profiles.ini files wilde@173: * wilde@173: * Use well known paths and heuristics to find the current users wilde@173: * profiles.ini files on GNU/Linux and Windows systems. wilde@173: * wilde@173: * @return NULL terminated array of strings containing the absolute wilde@173: * path of the profiles.ini files. The array needs to be freed by the wilde@173: * caller. wilde@173: */ wilde@173: static char ** wilde@173: get_profile_inis () wilde@173: { wilde@173: char **inis = NULL; wilde@320: char *mozpath, *fqpath, *subpath, *ppath; wilde@173: DIR *mozdir; wilde@173: struct dirent *mozdirent; wilde@194: char *confbase = get_conf_basedir(); wilde@197: const char *confdirs[] = { CONFDIRS, NULL }; wilde@173: wilde@197: for (int i=0; confdirs[i] != NULL; i++) wilde@173: { wilde@320: xasprintf(&mozpath,"%s/%s", confbase, confdirs[i]); wilde@320: if ((mozdir = opendir(mozpath)) != NULL) wilde@180: { wilde@197: while ((mozdirent = readdir(mozdir)) != NULL) wilde@180: { wilde@320: xasprintf(&subpath, "%s/%s/%s", wilde@320: confbase, wilde@320: confdirs[i], wilde@320: mozdirent->d_name); wilde@320: if (port_isdir(subpath) wilde@197: && (strcmp(mozdirent->d_name, "..") != 0)) wilde@180: { wilde@320: xasprintf(&ppath, "%s/%s/%s/%s", wilde@320: confbase, wilde@320: confdirs[i], wilde@320: mozdirent->d_name, wilde@320: "profiles.ini"); wilde@320: DEBUGPRINTF("checking for %s...\n", ppath); wilde@320: if ((fqpath = port_realpath(ppath)) != NULL) wilde@197: { wilde@197: strv_append(&inis, fqpath, strlen(fqpath)); wilde@228: DEBUGPRINTF("Found mozilla ini file: '%s'\n", fqpath); wilde@197: free(fqpath); wilde@197: } wilde@320: free(ppath); wilde@180: } wilde@320: free(subpath); wilde@180: } wilde@197: closedir(mozdir); wilde@180: } wilde@197: else wilde@197: { wilde@228: DEBUGPRINTF("Could not open %s/%s\n", confbase, confdirs[i]); wilde@197: } wilde@320: free(mozpath); wilde@180: } wilde@197: if (inis == NULL) wilde@180: { wilde@228: DEBUGPRINTF("No ini files found - will do nothing!\n"); wilde@173: } wilde@173: return inis; wilde@173: } wilde@173: andre@989: andre@989: /** @brief make the default nss databases readable. andre@989: * andre@989: * This uses the static paths definied in this code to ensure andre@989: * that only the defaults are touched. andre@989: * andre@989: */ andre@989: #ifndef WIN32 andre@989: static void andre@989: make_defaults_readable() andre@989: { andre@989: const char *confdirs[] = { MOZILLA_DEFAULTS, NULL }; andre@989: const char *filenames[] = { MOZILLA_DBNAMES, NULL }; andre@989: andre@989: mode_t access_mask = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; andre@989: andre@989: for (int i=0; confdirs[i] != NULL; i++) andre@989: { andre@989: for (int j=0; filenames[j] != NULL; j++) andre@989: { andre@989: char *realpath = NULL, andre@989: *path = NULL; andre@989: xasprintf (&path, "%s/profile/%s", confdirs[i], filenames[j]); andre@989: realpath = port_realpath(path); andre@989: xfree(path); andre@989: if (!realpath) andre@989: { andre@989: syslog_error_printf("Failed to find %s \n", realpath); andre@989: continue; andre@989: } andre@989: if (chmod(realpath, access_mask)) andre@989: { andre@989: syslog_error_printf("Failed to set access_mask on file.\n"); andre@989: } andre@989: xfree (realpath); andre@989: } andre@989: } andre@989: } andre@989: #endif andre@989: wilde@223: /** andre@975: * @brief Collect the default profile directories for mozilla software andre@975: * andre@975: * If the default directory is found but not the profiles subdirectory andre@975: * this will create the profiles subdirectory. andre@975: * andre@975: * @return NULL terminated array of strings containing the absolute path andre@975: * to the default profile directories. Needs to be freed by the caller. andre@975: */ andre@975: static char** andre@975: get_default_profile_dirs() andre@975: { andre@975: char **retval = NULL; andre@975: andre@975: const char *confdirs[] = { MOZILLA_DEFAULTS, NULL }; andre@975: andre@985: #ifdef _WIN32 andre@985: char *program_files = get_program_files_folder(); andre@985: if (!program_files) andre@985: { andre@985: ERRORPRINTF ("Failed to look up program files folder.\n"); andre@985: return NULL; andre@985: } andre@985: #endif andre@985: andre@975: for (int i=0; confdirs[i] != NULL; i++) andre@975: { andre@985: char *realpath = NULL, andre@985: *profile_dir = NULL; andre@985: #ifndef _WIN32 andre@985: realpath = port_realpath(confdirs[i]); andre@985: #else andre@985: /* As on linux we only respect the default installation directory andre@985: mozilla firefox and thunderbird change their registry key with andre@985: each version as the key includes the version number. It would andre@985: be error prone to search the system for every instance. So we andre@985: only check the default installation directories. */ andre@985: xasprintf(&realpath, "%s" DIRSEP "%s", program_files, confdirs[i]); andre@985: #endif andre@975: if (realpath == NULL) andre@975: { andre@975: DEBUGPRINTF ("Did not find directory: '%s'\n", confdirs[i]); andre@975: continue; andre@975: } andre@985: xasprintf(&profile_dir, "%s" DIRSEP "profile", realpath); andre@985: xfree(realpath); andre@975: if (port_isdir(profile_dir)) andre@975: { andre@975: DEBUGPRINTF("Found default directory: '%s'\n", profile_dir); andre@975: /* All is well */ andre@975: strv_append (&retval, profile_dir, strlen(profile_dir)); andre@975: xfree(profile_dir); andre@975: profile_dir = NULL; andre@975: continue; andre@975: } andre@975: else andre@975: { andre@975: /* Create the directory */ andre@975: if (port_fileexits(profile_dir)) andre@975: { andre@975: DEBUGPRINTF ("Path: '%s' is not a directory but it exists. Skipping.\n", andre@975: profile_dir); andre@975: xfree(profile_dir); andre@975: profile_dir = NULL; andre@975: continue; andre@975: } andre@975: else andre@975: { andre@975: /* Lets create it */ andre@1070: if (!port_mkdir_p(profile_dir, true)) andre@975: { andre@975: ERRORPRINTF ("Failed to create directory: '%s'\n", profile_dir); andre@975: xfree(profile_dir); andre@975: profile_dir = NULL; andre@975: continue; andre@975: } andre@975: strv_append (&retval, profile_dir, strlen(profile_dir)); andre@975: xfree(profile_dir); andre@975: profile_dir = NULL; andre@975: } andre@975: } andre@975: } andre@986: #ifdef WIN32 andre@985: xfree (program_files); andre@986: #endif andre@975: return retval; andre@975: } andre@975: andre@975: /** wilde@231: * @brief Collect all mozilla profile directories of current user. wilde@231: * @return NULL terminated array of strings containing the absolute wilde@231: * path of the profile directories. The array needs to be freed by the wilde@231: * caller. wilde@231: */ wilde@231: static char** wilde@308: get_all_nssdb_dirs() wilde@231: { wilde@231: char **mozinis, **pdirs; wilde@231: char **alldirs = NULL; andre@975: andre@975: if (is_elevated()) andre@975: { andre@975: #ifndef _WIN32 andre@975: /* NSS Shared db does not exist under windows. */ andre@1070: if (!port_mkdir_p(NSSSHARED_GLOBAL, false)) andre@988: { andre@988: ERRORPRINTF("Failed to create nssshared skeleton directory. \n"); andre@988: } andre@988: else andre@988: { andre@988: strv_append(&alldirs, "sql:" NSSSHARED_GLOBAL, strlen("sql:" NSSSHARED_GLOBAL)); andre@988: } andre@975: #endif andre@975: pdirs = get_default_profile_dirs(); andre@975: if (pdirs != NULL) andre@975: { andre@975: for (int i=0; pdirs[i] != NULL; i++) andre@975: { andre@975: strv_append(&alldirs, pdirs[i], strlen(pdirs[i])); andre@975: } andre@975: strv_free(pdirs); andre@975: } andre@975: return alldirs; andre@975: } wilde@308: /* Search Mozilla/Firefox/Thunderbird profiles */ wilde@231: if ((mozinis = get_profile_inis()) != NULL) wilde@231: { wilde@231: for (int i=0; mozinis[i] != NULL; i++) wilde@231: { wilde@231: pdirs = wilde@231: get_profile_dirs(mozinis[i]); wilde@231: if (pdirs != NULL) wilde@231: { wilde@231: for (int i=0; pdirs[i] != NULL; i++) wilde@231: { wilde@231: strv_append(&alldirs, pdirs[i], strlen(pdirs[i])); wilde@231: } wilde@231: strv_free(pdirs); wilde@231: } wilde@231: } wilde@231: strv_free(mozinis); wilde@231: } wilde@308: /* Search for NSS shared DB (used by Chrome/Chromium on GNU/Linux) */ wilde@308: if (TARGET_LINUX) wilde@308: { wilde@320: char *path, *fqpath, *sqlpath; wilde@320: xasprintf(&path, "%s/%s", get_conf_basedir(), NSSSHARED); wilde@320: if ((fqpath = port_realpath(path)) != NULL) wilde@308: { wilde@320: xasprintf(&sqlpath, "sql:%s", fqpath); wilde@320: strv_append(&alldirs, sqlpath, strlen(sqlpath)); wilde@320: free(sqlpath); wilde@308: free(fqpath); wilde@308: } wilde@320: free(path); wilde@308: } wilde@231: return alldirs; wilde@231: } wilde@231: wilde@281: #ifdef DEBUGOUTPUT wilde@231: /** wilde@223: * @brief list certificates from nss certificate store wilde@223: * @param[in] confdir the directory with the certificate store wilde@223: */ wilde@223: static void wilde@281: DEBUG_nss_list_certs (char *confdir) wilde@223: { wilde@223: CERTCertList *list; wilde@223: CERTCertListNode *node; wilde@223: char *name; wilde@224: wilde@223: if (NSS_Initialize(confdir, "", "", "secmod.db", NSS_INIT_READONLY) wilde@223: == SECSuccess) wilde@223: { wilde@283: DEBUGPRINTF("Listing certs in \"%s\"\n", confdir); wilde@223: list = PK11_ListCerts(PK11CertListAll, NULL); wilde@223: for (node = CERT_LIST_HEAD(list); !CERT_LIST_END(node, list); andre@905: node = CERT_LIST_NEXT(node)) andre@905: { andre@905: name = node->appData; wilde@223: andre@905: DEBUGPRINTF("Found certificate \"%s\"\n", name); andre@905: } andre@945: /* According to valgrind this leaks memory in the list. andre@945: We could not find API documentation to better free this andre@945: so we accept the leakage here in case of debug. */ wilde@223: CERT_DestroyCertList(list); wilde@223: NSS_Shutdown(); wilde@223: } wilde@223: else wilde@281: { wilde@281: DEBUGPRINTF("Could not open nss certificate store in %s!\n", confdir); wilde@281: } wilde@223: } wilde@281: #endif wilde@223: wilde@261: /** wilde@261: * @brief Create a string with the name for cert in SECItem. wilde@261: * wilde@261: * Should be freed by caller. wilde@261: * @param[in] secitemp ponts to an SECItem holding the DER certificate. emanuel@1053: * @returns a string of the from "CN of Subject - O of Subject" wilde@261: */ wilde@261: static char * wilde@261: nss_cert_name(SECItem *secitemp) andre@390: { andre@390: char *cn_str, *o_str, *name; wilde@261: size_t name_len; wilde@261: cn_str = x509_parse_subject(secitemp->data, secitemp->len, CERT_OID_CN); wilde@261: o_str = x509_parse_subject(secitemp->data, secitemp->len, CERT_OID_O); aheinecke@332: if (!cn_str || !o_str) aheinecke@332: { andre@1060: ERRORPRINTF("FATAL: Could not parse certificate!"); aheinecke@332: exit(ERR_INVALID_CERT); aheinecke@332: } wilde@261: name_len = strlen(cn_str) + strlen(o_str) + 4; wilde@261: name = (char *)xmalloc(name_len); wilde@261: snprintf(name, name_len, "%s - %s", cn_str, o_str); wilde@261: free(cn_str); wilde@261: free(o_str); wilde@261: return name; wilde@261: } wilde@261: wilde@276: /** wilde@276: * @brief Convert a base64 encoded DER certificate to SECItem wilde@276: * @param[in] b64 pointer to the base64 encoded certificate wilde@276: * @param[in] b64len length of the base64 encoded certificate wilde@276: * @param[out] secitem pointer to the SECItem in which to store the wilde@276: * raw DER certifiacte. wilde@276: * @returns true on success and false on failure wilde@276: */ wilde@244: static bool wilde@244: base64_to_secitem(char *b64, size_t b64len, SECItem *secitem) wilde@244: { wilde@244: unsigned char *dercert = NULL; wilde@244: size_t dercertlen; wilde@244: wilde@245: if ((str_base64_decode((char **)(&dercert), &dercertlen, wilde@245: b64, b64len) == 0) && wilde@245: (dercertlen > 0)) wilde@244: { wilde@244: secitem->data = dercert; wilde@246: secitem->len = (unsigned int) dercertlen; wilde@244: return true; wilde@244: } wilde@244: else wilde@281: { wilde@281: DEBUGPRINTF("Base64 decode failed for: %s\n", b64); wilde@281: } wilde@261: return false; wilde@244: } wilde@244: wilde@244: /** wilde@277: * @brief Store DER certificate in mozilla store. wilde@277: * @param[in] pdir the mozilla profile directory with the certificate wilde@277: * store to manipulate. wilde@277: * @param[in] dercert pointer to a SECItem holding the DER certificate wilde@277: * to install wilde@277: * @returns true on success and false on failure wilde@277: */ wilde@277: static bool wilde@277: import_cert(char *pdir, SECItem *dercert) wilde@277: { wilde@277: PK11SlotInfo *pk11slot = NULL; wilde@309: CERTCertTrust *trust = NULL; wilde@309: CERTCertificate *cert = NULL; wilde@277: bool success = false; wilde@277: char *cert_name = nss_cert_name(dercert); wilde@277: wilde@277: DEBUGPRINTF("INSTALLING cert: '%s' to: %s\n", cert_name, pdir); aheinecke@493: pk11slot = PK11_GetInternalKeySlot(); aheinecke@493: cert = CERT_DecodeCertFromPackage((char *)dercert->data, aheinecke@493: (int)dercert->len); aheinecke@493: trust = (CERTCertTrust *)xmalloc(sizeof(CERTCertTrust)); aheinecke@595: CERT_DecodeTrustString(trust, "C,C,C"); andre@1012: if (PK11_ImportCert(pk11slot, cert, CK_INVALID_HANDLE, andre@1012: cert_name, PR_FALSE) == SECSuccess) wilde@277: { andre@1012: if(CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), cert, trust) == SECSuccess) andre@1012: { andre@1012: log_certificate_der (pdir, dercert->data, dercert->len, true); andre@1012: success = true; andre@1012: } wilde@277: } andre@1012: /* This could have happened on either the import cert or andre@1012: the cert change trust. If Import Cert fails with that andre@1012: error the certificate has in fact been added but with andre@1012: random trist bits. See NSS Bug 595861. andre@1012: Reference code can be found in gnome evolution under andre@1012: smime/lib/e-cert-db.c */ andre@1012: if(PORT_GetError() == SEC_ERROR_TOKEN_NOT_LOGGED_IN) andre@1012: { andre@1012: if (PK11_NeedUserInit (pk11slot)) andre@1012: { andre@1012: PK11_InitPin (pk11slot, "", ""); andre@1012: } andre@1012: if (PK11_Authenticate (pk11slot, PR_TRUE, NULL) != SECSuccess) andre@1012: { andre@1012: DEBUGPRINTF("Failed to authenticate.\n"); andre@1012: } andre@1012: else if(CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), cert, trust) == SECSuccess) andre@1012: { andre@1012: log_certificate_der (pdir, dercert->data, dercert->len, true); andre@1012: success = true; andre@1012: } andre@1012: } andre@1012: andre@1012: if (!success) wilde@277: { aheinecke@493: DEBUGPRINTF("Failed to install certificate '%s' to '%s'!\n", cert_name, pdir); aheinecke@493: ERRORPRINTF("Error installing certificate err: %i\n", PORT_GetError()); wilde@277: } aheinecke@493: CERT_DestroyCertificate (cert); aheinecke@493: free(trust); aheinecke@493: PK11_FreeSlot(pk11slot); wilde@277: wilde@277: free(cert_name); wilde@277: return success; wilde@277: } wilde@277: wilde@277: /** wilde@277: * @brief Remove DER certificate from mozilla store. wilde@277: * @param[in] pdir the mozilla profile directory with the certificate wilde@277: * store to manipulate. wilde@277: * @param[in] dercert pointer to a SECItem holding the DER certificate wilde@277: * to remove wilde@277: * @returns true on success and false on failure wilde@277: */ wilde@277: static bool wilde@277: remove_cert(char *pdir, SECItem *dercert) wilde@277: { wilde@277: PK11SlotInfo *pk11slot = NULL; wilde@277: bool success = false; wilde@277: char *cert_name = nss_cert_name(dercert); wilde@277: CERTCertificate *cert = NULL; wilde@277: wilde@277: DEBUGPRINTF("REMOVING cert: '%s' from: %s\n", cert_name, pdir); wilde@277: if (NSS_Initialize(pdir, "", "", "secmod.db", 0) == SECSuccess) wilde@277: { wilde@277: pk11slot = PK11_GetInternalKeySlot(); wilde@277: cert = PK11_FindCertFromDERCertItem(pk11slot, wilde@277: dercert, NULL); wilde@277: if (cert != NULL) wilde@277: { wilde@277: if (SEC_DeletePermCertificate(cert) == SECSuccess) wilde@277: { wilde@277: success = true; andre@625: log_certificate_der (pdir, dercert->data, dercert->len, false); wilde@277: } wilde@277: else wilde@277: { wilde@277: DEBUGPRINTF("Failed to remove certificate '%s' from '%s'!\n", cert_name, pdir); wilde@277: } wilde@277: CERT_DestroyCertificate(cert); wilde@277: } wilde@277: else wilde@277: { wilde@277: DEBUGPRINTF("Could not find Certificate '%s' in store '%s'.\n", cert_name, pdir); wilde@277: } wilde@277: PK11_FreeSlot(pk11slot); wilde@277: NSS_Shutdown(); wilde@277: } wilde@277: else wilde@277: { wilde@277: DEBUGPRINTF("Could not open nss certificate store in %s!\n", pdir); wilde@277: } wilde@277: free(cert_name); wilde@277: return success; wilde@277: } wilde@277: wilde@277: /** wilde@279: * @brief Apply a function to a list of certificates and profiles wilde@279: * wilde@279: * The function must have the signature: wilde@279: * wilde@279: * bool function(char *pdir, SECItem der_cert) wilde@279: * wilde@279: * where pdir is the path of an profile and der_cert is an raw DER wilde@279: * formatted certificate. The function must return true on success wilde@279: * and false on failure. wilde@279: * andre@625: * This function is intended for use with the import_cert and wilde@279: * remove_cert functions. wilde@279: * wilde@279: * @param[in] fn the function to apply wilde@279: * @param[inout] certs a secitem list holding the certificates wilde@279: * the list will be change (emptied)! wilde@279: * @param[in] pdirs the NULL terminated list of profile directories wilde@279: * @returns true on success and false on failure wilde@279: */ wilde@279: bool wilde@279: apply_to_certs_and_profiles(bool fn(char *, SECItem *), wilde@279: seciteml_t **certs, char **pdirs) wilde@279: { wilde@279: bool success = true; wilde@280: aheinecke@493: for (int i=0; pdirs[i] != NULL; i++) wilde@279: { aheinecke@493: seciteml_t *iter = *certs; aheinecke@493: if (NSS_Initialize(pdirs[i], "", "", "secmod.db", 0) != SECSuccess) wilde@279: { aheinecke@493: DEBUGPRINTF("Could not open nss certificate store in %s!\n", pdirs[i]); aheinecke@493: continue; aheinecke@493: } aheinecke@493: aheinecke@493: while (iter != NULL && iter->item != NULL) aheinecke@493: { aheinecke@493: SECItem *cert = iter->item; wilde@279: if (! (*fn)(pdirs[i], cert)) wilde@279: success = false; aheinecke@493: iter = iter->next; wilde@279: } aheinecke@493: NSS_Shutdown(); wilde@279: } wilde@280: aheinecke@564: seciteml_free(certs); aheinecke@564: wilde@279: return success; wilde@279: } wilde@279: wilde@279: /** wilde@244: * @brief Parse IPC commands from standard input. wilde@244: * wilde@244: * Reads command lines (R: and I:) from standard input and puts the wilde@244: * certificates to process in two SECItem lists holding the wilde@244: * certificates in DER format. emanuel@1053: * @param[inout] stream from standard input wilde@244: * @param[inout] install_list list of SECItems with certifiactes to install wilde@244: * @param[inout] remove_list list of SECItems with certifiactes to remove wilde@244: */ wilde@244: static void wilde@315: parse_commands (FILE *stream, wilde@315: seciteml_t **install_list, seciteml_t **remove_list) wilde@244: { wilde@244: char inpl[LINEBUFLEN]; wilde@244: size_t inpllen; wilde@244: bool parserr = true; wilde@244: SECItem secitem; wilde@244: wilde@315: while ( fgets(inpl, LINEBUFLEN, stream) != NULL ) wilde@244: { wilde@244: inpllen = strnlen(inpl, LINEBUFLEN); wilde@244: /* Validate input line: wilde@244: * - must be (much) longer than 3 characters wilde@244: * - must start with "*:" wilde@244: */ wilde@244: if ((inpllen > 3) && (inpl[1] == ':')) wilde@244: /* Now parse Input */ wilde@244: switch(inpl[0]) wilde@244: { wilde@244: case 'R': wilde@244: parserr = true; wilde@244: DEBUGPRINTF("Request to remove certificate: %s\n", &inpl[2]); wilde@244: if (base64_to_secitem(&inpl[2], inpllen - 2, &secitem)) wilde@244: { wilde@244: seciteml_push(remove_list, &secitem); wilde@244: parserr = false; wilde@244: } wilde@244: break; wilde@244: case 'I': wilde@244: parserr = true; wilde@244: DEBUGPRINTF("Request to install certificate: %s\n", &inpl[2]); wilde@244: if (base64_to_secitem(&inpl[2], inpllen - 2, &secitem)) wilde@244: { wilde@244: seciteml_push(install_list, &secitem); wilde@244: parserr = false; wilde@244: } wilde@244: break; wilde@244: default: wilde@244: parserr = true; wilde@244: } wilde@244: else wilde@244: { wilde@244: parserr = true; wilde@244: } wilde@244: wilde@244: if (parserr) wilde@244: { andre@1060: ERRORPRINTF("FATAL: Invalid input: %s\n", inpl); wilde@244: exit(ERR_MOZ_INVALID_INPUT); wilde@244: } wilde@244: } wilde@244: } wilde@244: andre@1315: #ifdef IS_TAG_BUILD andre@1060: bool g_debug = false; andre@1072: #else andre@1072: bool g_debug = true; andre@1072: #endif andre@1060: wilde@113: int wilde@315: main (int argc, char **argv) wilde@113: { wilde@308: char **dbdirs; wilde@244: seciteml_t *certs_to_remove = NULL; wilde@244: seciteml_t *certs_to_add = NULL; wilde@315: FILE *input_stream; wilde@315: wilde@315: switch (argc) wilde@315: { wilde@315: case 1: wilde@322: DEBUGPRINTF("Opening STDIN for input...\n"); wilde@315: input_stream = stdin; wilde@315: break; wilde@315: case 2: andre@1060: if (strcmp(argv[1], "--debug") == 0) andre@1060: { andre@1060: g_debug = true; andre@1060: DEBUGPRINTF("Opening STDIN for input...\n"); andre@1060: input_stream = stdin; andre@1060: break; andre@1060: } andre@1060: case 3: wilde@315: DEBUGPRINTF("Opening %s for input...\n", argv[1]); wilde@315: if ((input_stream = fopen(argv[1], "r")) == NULL) wilde@315: { andre@1060: ERRORPRINTF ("FATAL: Could not open %s for reading!\n", andre@1060: argv[1]); wilde@317: exit_code = ERR_MOZ_FAILED_TO_OPEN_INPUT; wilde@315: goto exit; wilde@315: } andre@1060: if (argc == 3 && strcmp(argv[2], "--debug") == 0) andre@1060: { andre@1060: g_debug = true; andre@1060: } wilde@315: break; wilde@315: default: andre@1060: ERRORPRINTF("FATAL: Wrong number of arguments!\n"); wilde@317: exit_code = ERR_MOZ_WRONG_ARGC; wilde@315: goto exit; wilde@315: } wilde@244: wilde@308: dbdirs = wilde@308: get_all_nssdb_dirs(); wilde@235: wilde@308: if (dbdirs != NULL) wilde@231: { wilde@315: parse_commands(input_stream, &certs_to_add, &certs_to_remove); wilde@244: wilde@281: #ifdef DEBUGOUTPUT wilde@284: DEBUGPRINTF("OLD List of installed certs:\n"); wilde@308: for (int i=0; dbdirs[i] != NULL; i++) wilde@308: DEBUG_nss_list_certs(dbdirs[i]); wilde@281: #endif wilde@263: wilde@308: if (! apply_to_certs_and_profiles(remove_cert, &certs_to_remove, dbdirs)) wilde@317: exit_code |= WARN_MOZ_COULD_NOT_REMOVE_CERT; wilde@280: wilde@308: if (! apply_to_certs_and_profiles(import_cert, &certs_to_add, dbdirs)) wilde@317: exit_code |= WARN_MOZ_COULD_NOT_ADD_CERT; wilde@280: wilde@281: #ifdef DEBUGOUTPUT wilde@284: DEBUGPRINTF("NEW List of installed certs:\n"); wilde@308: for (int i=0; dbdirs[i] != NULL; i++) wilde@308: DEBUG_nss_list_certs(dbdirs[i]); wilde@281: #endif wilde@280: andre@989: #ifndef WIN32 andre@989: if (is_elevated()) andre@989: { andre@989: make_defaults_readable(); andre@989: } andre@989: #endif andre@989: wilde@308: strv_free(dbdirs); wilde@231: } wilde@315: andre@905: fclose(input_stream); wilde@315: andre@905: exit: wilde@317: exit(exit_code); aheinecke@44: }