# HG changeset patch # User Andre Heinecke # Date 1411377583 -7200 # Node ID e210ecc32d697d63266d70532281f51c9edab316 # Parent 7175d117e69a6d370ce45575845dc3806536fae5 (issue128) Rename mozilla process to trustbridge-nss-installer diff -r 7175d117e69a -r e210ecc32d69 cinst/CMakeLists.txt --- a/cinst/CMakeLists.txt Fri Sep 19 17:47:03 2014 +0200 +++ b/cinst/CMakeLists.txt Mon Sep 22 11:19:43 2014 +0200 @@ -56,47 +56,47 @@ endif() # ---------------------------------------------------------------------- -# Mozilla nss store specific certificate installer: -set(MOZILLA_SOURCES +# trustbridge-nss-installer nss store specific certificate installer: +set(trustbridge-nss-installer_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/nss-secitemlist.c - ${CMAKE_CURRENT_SOURCE_DIR}/mozilla.c + ${CMAKE_CURRENT_SOURCE_DIR}/nss-installer.c ) if(WIN32 OR NSS_FOUND) include_directories(${NSS_INCLUDE_DIRS}) - add_executable(mozilla ${MOZILLA_SOURCES}) - target_link_libraries(mozilla + add_executable(trustbridge-nss-installer ${trustbridge-nss-installer_SOURCES}) + target_link_libraries(trustbridge-nss-installer trustbridge_common ${POLARSSL_LIBRARIES} ${NSS_LIBRARIES} ${PROFILING_LIBS}) - set_target_properties(mozilla PROPERTIES COMPILE_FLAGS "-std=c99") - install(TARGETS mozilla DESTINATION bin) + set_target_properties(trustbridge-nss-installer PROPERTIES COMPILE_FLAGS "-std=c99") + install(TARGETS trustbridge-nss-installer DESTINATION bin) if (WIN32) add_custom_command( - TARGET mozilla + TARGET trustbridge-nss-installer POST_BUILD - COMMAND ${CMAKE_STRIP} mozilla.exe + COMMAND ${CMAKE_STRIP} trustbridge-nss-installer.exe ) if (NOT RELEASE_BUILD) add_custom_command( - TARGET mozilla + TARGET trustbridge-nss-installer POST_BUILD COMMAND ${OSSLSIGNCODE_EXECUTABLE} sign -certs ${CMAKE_SOURCE_DIR}/ui/tests/data/codesign/codesigning.pem -key ${CMAKE_SOURCE_DIR}/ui/tests/data/codesign/codesigning.key - -h sha256 -in ${CMAKE_CURRENT_BINARY_DIR}/mozilla.exe - -out ${CMAKE_CURRENT_BINARY_DIR}/mozilla-signed.exe && - mv ${CMAKE_CURRENT_BINARY_DIR}/mozilla-signed.exe ${CMAKE_CURRENT_BINARY_DIR}/mozilla.exe + -h sha256 -in ${CMAKE_CURRENT_BINARY_DIR}/trustbridge-nss-installer.exe + -out ${CMAKE_CURRENT_BINARY_DIR}/trustbridge-nss-installer-signed.exe && + mv ${CMAKE_CURRENT_BINARY_DIR}/trustbridge-nss-installer-signed.exe ${CMAKE_CURRENT_BINARY_DIR}/trustbridge-nss-installer.exe ) endif() else() add_custom_command( - TARGET mozilla + TARGET trustbridge-nss-installer POST_BUILD - COMMAND strip mozilla + COMMAND strip trustbridge-nss-installer ) endif() else() - message(STATUS "WARNING: Could not find nss. Mozilla cert installer will not be build!") + message(STATUS "WARNING: Could not find nss. trustbridge-nss-installer cert installer will not be build!") endif() diff -r 7175d117e69a -r e210ecc32d69 cinst/mozilla.c --- a/cinst/mozilla.c Fri Sep 19 17:47:03 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,925 +0,0 @@ -/* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik - * Software engineering by Intevation GmbH - * - * This file is Free Software under the GNU GPL (v>=2) - * and comes with ABSOLUTELY NO WARRANTY! - * See LICENSE.txt for details. - */ -/** - * @file - * @brief Mozilla installation process - * - * Reads from a file given on command line or stdin a list of - * instructions in the form: - * - * I:\
- * R:\ - * ... - * - * With one instruction per line. the maximum size of an input - * line is 9999 characters (including the \\r\\n) at the end of the line. - * - * Certificates marked with I: will be installed and the ones - * marked with R: will be searched and if available removed from - * the databases. - * - * This tool tries to find all NSS databases the user has - * access to and to execute the instructions on all of them. - * - * If the tool is executed with a UID of 0 or with admin privileges under - * windows it will not look into the user directories but instead try - * to write the system wide defaults. - * - * If there are other processes accessing the databases the caller - * has to ensure that those are terminated before this process is - * executed. - * - * If the same certificate is marked to be installed and to be removed - * in one call the behavior is undefined. This should be avoided and - * may lead to errors. - * - * Returns 0 on success (Even when no stores where found) an error value - * as defined in errorcodes.h otherwise. - * - * Success messages are written to stdout. Errors to stderr. For logging - * purposes each installation / removal of a certificate will be reported - * with the profile name that it modified. - * - * To get more verbose output add the --debug parameter - * as the last parameter on the command line. - * - */ - -/** - * @brief Needs to be defined to get strnlen() - */ -#define _POSIX_C_SOURCE 200809L - -/* REMOVEME: */ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DEBUGPREFIX "MOZ-" -#include "logging.h" - -#include "certhelp.h" -#include "errorcodes.h" -#include "portpath.h" -#include "strhelp.h" -#include "nss-secitemlist.h" -#include "util.h" - -#ifndef _WIN32 -#define CONFDIRS ".mozilla", ".thunderbird" -/* Default installation directory of ubuntu 14.4 is respected */ -#define MOZILLA_DEFAULTS "/usr/lib/thunderbird/defaults", "/usr/lib/firefox/browser/defaults" -#define MOZILLA_DBNAMES "cert8.db", "key3.db", "secmod.db" -#define NSSSHARED ".pki/nssdb" -#define NSSSHARED_GLOBAL "/etc/skel/.pki/nssdb" -#define TARGET_LINUX 1 -#define DIRSEP "/" -#else -#define MOZILLA_DEFAULTS "Mozilla Firefox\\browser\\defaults", "Mozilla Thunderbird\\defaults" -#define MOZILLA_DBNAMES NULL -#define CONFDIRS "Mozilla", "Thunderbird" -#define NSSSHARED "" -#define TARGET_LINUX NULL -#define DIRSEP "\\" -#endif - -/** - * @brief Length of string buffers used - * - * The maximal length of input is defined as 9999 (+ terminating \0). - * We use it for other other input puffers besides the IPC input, too. - * (One size fits all). - */ -#define LINEBUFLEN 10000 - -#ifdef _WIN32 -#define STRTOK_R strtok_s -#else -#define STRTOK_R strtok_r -#endif - -/** - * @brief Global Return Code - * - * This will be retuned by the programm and might be set to an - * error code on fatal errors and to and warning code on non-fatal - * errors. In case of mor than one warning the warning codes will be - * ORed together. - */ -int exit_code = 0; - -/** - * @brief Return configuration base directory. - * @returns A pointer to a string containing the path to the base - * directory holding the configuration directories for e.g. mozilla - * and thunderbird. - */ -static char * -get_conf_basedir() -{ - char *cdir, *envvar; - - if (TARGET_LINUX) - envvar = "HOME" ; - else - envvar = "APPDATA"; - - if ((cdir = getenv(envvar)) != NULL) - return cdir; - else - { - ERRORPRINTF("FATAL! No %s in environment.\n", envvar); - exit(ERR_MOZ_HOMELESS); - } -} - -/** - * @brief Get a list of all mozilla profile directories - * - * Parse the profiles.ini and extract all profile paths from that. - * The expected data is in the form: - * - * [Profile99]
- * IsRelative=1
- * Path=Example/foo.bar - * - * or
- * [Profile0]
- * IsRelative=0
- * Path=c:\\foo\\bar\\baz - * - * Mozilla also accepts the ini file on Windows even if it is UTF-16 - * encoded but never writes UTF-16 on its own. So currently we ignore - * this special case. - * - * @param[in] inifile_name path of the profile.ini to read. - * @return NULL terminated array of strings containing containing the - * absolute path of the profile directories. The array needs to - * be freed by the caller. - */ -static char ** -get_profile_dirs (char *inifile_name) -{ - char **dirs = NULL; - char *inifile_dirname; - FILE *inifile; - char line[LINEBUFLEN]; - char *key; - char *value; - char *path = NULL; - char *fqpath; - bool inprofile = false; - bool relative_path = false; - char *saveptr; - - if ((inifile = fopen(inifile_name, "r")) != NULL) - { - DEBUGPRINTF("Searching for profile paths in: '%s'\n", inifile_name); - - inifile_dirname = port_dirname(inifile_name); - while (fgets(line, LINEBUFLEN, inifile) != NULL) - { - /* Determine if we are in an profile section */ - if (str_starts_with(line, "[Profile")) - { - relative_path = false; - inprofile = true; - } - else if (line[0] == '[') - inprofile = false; - - /* If we are in a profile parse path related stuff */ - if (inprofile) - { - saveptr = NULL; - key = STRTOK_R(line, "=", &saveptr); - value = STRTOK_R(NULL, "=", &saveptr); - str_trim(&value); - if (str_equal(key, "Path")) - { - if (relative_path) - xasprintf(&path, "%s/%s", inifile_dirname, value); - else - xasprintf(&path, "%s", value); - if ((fqpath = port_realpath(path)) != NULL) - { - DEBUGPRINTF("Found profile path: '%s'\n", fqpath); - strv_append(&dirs, fqpath, strlen(fqpath)); - free (fqpath); - } - else - { - DEBUGPRINTF("WARN! Non existent profile path: '%s'\n", path); - exit_code |= WARN_MOZ_PROFILE_DOES_NOT_EXIST; - } - free(path); - } - else if (str_equal(key, "IsRelative") && - str_starts_with(value, "1")) - relative_path = true; - } - } - fclose(inifile); - } - else - { - DEBUGPRINTF("WARN! Could not open ini file: '%s'\n", inifile_name); - exit_code |= WARN_MOZ_FAILED_TO_OPEN_INI; - } - return dirs; -} - -/** - * @brief Search for mozilla profiles.ini files - * - * Use well known paths and heuristics to find the current users - * profiles.ini files on GNU/Linux and Windows systems. - * - * @return NULL terminated array of strings containing the absolute - * path of the profiles.ini files. The array needs to be freed by the - * caller. - */ -static char ** -get_profile_inis () -{ - char **inis = NULL; - char *mozpath, *fqpath, *subpath, *ppath; - DIR *mozdir; - struct dirent *mozdirent; - char *confbase = get_conf_basedir(); - const char *confdirs[] = { CONFDIRS, NULL }; - - for (int i=0; confdirs[i] != NULL; i++) - { - xasprintf(&mozpath,"%s/%s", confbase, confdirs[i]); - if ((mozdir = opendir(mozpath)) != NULL) - { - while ((mozdirent = readdir(mozdir)) != NULL) - { - xasprintf(&subpath, "%s/%s/%s", - confbase, - confdirs[i], - mozdirent->d_name); - if (port_isdir(subpath) - && (strcmp(mozdirent->d_name, "..") != 0)) - { - xasprintf(&ppath, "%s/%s/%s/%s", - confbase, - confdirs[i], - mozdirent->d_name, - "profiles.ini"); - DEBUGPRINTF("checking for %s...\n", ppath); - if ((fqpath = port_realpath(ppath)) != NULL) - { - strv_append(&inis, fqpath, strlen(fqpath)); - DEBUGPRINTF("Found mozilla ini file: '%s'\n", fqpath); - free(fqpath); - } - free(ppath); - } - free(subpath); - } - closedir(mozdir); - } - else - { - DEBUGPRINTF("Could not open %s/%s\n", confbase, confdirs[i]); - } - free(mozpath); - } - if (inis == NULL) - { - DEBUGPRINTF("No ini files found - will do nothing!\n"); - } - return inis; -} - - -/** @brief make the default nss databases readable. - * - * This uses the static paths definied in this code to ensure - * that only the defaults are touched. - * - */ -#ifndef WIN32 -static void -make_defaults_readable() -{ - const char *confdirs[] = { MOZILLA_DEFAULTS, NULL }; - const char *filenames[] = { MOZILLA_DBNAMES, NULL }; - - mode_t access_mask = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; - - for (int i=0; confdirs[i] != NULL; i++) - { - for (int j=0; filenames[j] != NULL; j++) - { - char *realpath = NULL, - *path = NULL; - xasprintf (&path, "%s/profile/%s", confdirs[i], filenames[j]); - realpath = port_realpath(path); - xfree(path); - if (!realpath) - { - syslog_error_printf("Failed to find %s \n", realpath); - continue; - } - if (chmod(realpath, access_mask)) - { - syslog_error_printf("Failed to set access_mask on file.\n"); - } - xfree (realpath); - } - } -} -#endif - -/** - * @brief Collect the default profile directories for mozilla software - * - * If the default directory is found but not the profiles subdirectory - * this will create the profiles subdirectory. - * - * @return NULL terminated array of strings containing the absolute path - * to the default profile directories. Needs to be freed by the caller. - */ -static char** -get_default_profile_dirs() -{ - char **retval = NULL; - - const char *confdirs[] = { MOZILLA_DEFAULTS, NULL }; - -#ifdef _WIN32 - char *program_files = get_program_files_folder(); - if (!program_files) - { - ERRORPRINTF ("Failed to look up program files folder.\n"); - return NULL; - } -#endif - - for (int i=0; confdirs[i] != NULL; i++) - { - char *realpath = NULL, - *profile_dir = NULL; -#ifndef _WIN32 - realpath = port_realpath(confdirs[i]); -#else - /* As on linux we only respect the default installation directory - mozilla firefox and thunderbird change their registry key with - each version as the key includes the version number. It would - be error prone to search the system for every instance. So we - only check the default installation directories. */ - xasprintf(&realpath, "%s" DIRSEP "%s", program_files, confdirs[i]); -#endif - if (realpath == NULL) - { - DEBUGPRINTF ("Did not find directory: '%s'\n", confdirs[i]); - continue; - } - xasprintf(&profile_dir, "%s" DIRSEP "profile", realpath); - xfree(realpath); - if (port_isdir(profile_dir)) - { - DEBUGPRINTF("Found default directory: '%s'\n", profile_dir); - /* All is well */ - strv_append (&retval, profile_dir, strlen(profile_dir)); - xfree(profile_dir); - profile_dir = NULL; - continue; - } - else - { - /* Create the directory */ - if (port_fileexits(profile_dir)) - { - DEBUGPRINTF ("Path: '%s' is not a directory but it exists. Skipping.\n", - profile_dir); - xfree(profile_dir); - profile_dir = NULL; - continue; - } - else - { - /* Lets create it */ - if (!port_mkdir_p(profile_dir, true)) - { - ERRORPRINTF ("Failed to create directory: '%s'\n", profile_dir); - xfree(profile_dir); - profile_dir = NULL; - continue; - } - strv_append (&retval, profile_dir, strlen(profile_dir)); - xfree(profile_dir); - profile_dir = NULL; - } - } - } -#ifdef WIN32 - xfree (program_files); -#endif - return retval; -} - -/** - * @brief Collect all mozilla profile directories of current user. - * @return NULL terminated array of strings containing the absolute - * path of the profile directories. The array needs to be freed by the - * caller. - */ -static char** -get_all_nssdb_dirs() -{ - char **mozinis, **pdirs; - char **alldirs = NULL; - - if (is_elevated()) - { -#ifndef _WIN32 - /* NSS Shared db does not exist under windows. */ - if (!port_mkdir_p(NSSSHARED_GLOBAL, false)) - { - ERRORPRINTF("Failed to create nssshared skeleton directory. \n"); - } - else - { - strv_append(&alldirs, "sql:" NSSSHARED_GLOBAL, strlen("sql:" NSSSHARED_GLOBAL)); - } -#endif - pdirs = get_default_profile_dirs(); - if (pdirs != NULL) - { - for (int i=0; pdirs[i] != NULL; i++) - { - strv_append(&alldirs, pdirs[i], strlen(pdirs[i])); - } - strv_free(pdirs); - } - return alldirs; - } - /* Search Mozilla/Firefox/Thunderbird profiles */ - if ((mozinis = get_profile_inis()) != NULL) - { - for (int i=0; mozinis[i] != NULL; i++) - { - pdirs = - get_profile_dirs(mozinis[i]); - if (pdirs != NULL) - { - for (int i=0; pdirs[i] != NULL; i++) - { - strv_append(&alldirs, pdirs[i], strlen(pdirs[i])); - } - strv_free(pdirs); - } - } - strv_free(mozinis); - } - /* Search for NSS shared DB (used by Chrome/Chromium on GNU/Linux) */ - if (TARGET_LINUX) - { - char *path, *fqpath, *sqlpath; - xasprintf(&path, "%s/%s", get_conf_basedir(), NSSSHARED); - if ((fqpath = port_realpath(path)) != NULL) - { - xasprintf(&sqlpath, "sql:%s", fqpath); - strv_append(&alldirs, sqlpath, strlen(sqlpath)); - free(sqlpath); - free(fqpath); - } - free(path); - } - return alldirs; -} - -#ifdef DEBUGOUTPUT -/** - * @brief list certificates from nss certificate store - * @param[in] confdir the directory with the certificate store - */ -static void -DEBUG_nss_list_certs (char *confdir) -{ - CERTCertList *list; - CERTCertListNode *node; - char *name; - - if (NSS_Initialize(confdir, "", "", "secmod.db", NSS_INIT_READONLY) - == SECSuccess) - { - DEBUGPRINTF("Listing certs in \"%s\"\n", confdir); - list = PK11_ListCerts(PK11CertListAll, NULL); - for (node = CERT_LIST_HEAD(list); !CERT_LIST_END(node, list); - node = CERT_LIST_NEXT(node)) - { - name = node->appData; - - DEBUGPRINTF("Found certificate \"%s\"\n", name); - } - /* According to valgrind this leaks memory in the list. - We could not find API documentation to better free this - so we accept the leakage here in case of debug. */ - CERT_DestroyCertList(list); - NSS_Shutdown(); - } - else - { - DEBUGPRINTF("Could not open nss certificate store in %s!\n", confdir); - } -} -#endif - -/** - * @brief Create a string with the name for cert in SECItem. - * - * Should be freed by caller. - * @param[in] secitemp ponts to an SECItem holding the DER certificate. - * @returns a string of the from "CN of Subject - O of Subject" - */ -static char * -nss_cert_name(SECItem *secitemp) -{ - char *cn_str, *o_str, *name; - size_t name_len; - cn_str = x509_parse_subject(secitemp->data, secitemp->len, CERT_OID_CN); - o_str = x509_parse_subject(secitemp->data, secitemp->len, CERT_OID_O); - if (!cn_str || !o_str) - { - ERRORPRINTF("FATAL: Could not parse certificate!"); - exit(ERR_INVALID_CERT); - } - name_len = strlen(cn_str) + strlen(o_str) + 4; - name = (char *)xmalloc(name_len); - snprintf(name, name_len, "%s - %s", cn_str, o_str); - free(cn_str); - free(o_str); - return name; -} - -/** - * @brief Convert a base64 encoded DER certificate to SECItem - * @param[in] b64 pointer to the base64 encoded certificate - * @param[in] b64len length of the base64 encoded certificate - * @param[out] secitem pointer to the SECItem in which to store the - * raw DER certifiacte. - * @returns true on success and false on failure - */ -static bool -base64_to_secitem(char *b64, size_t b64len, SECItem *secitem) -{ - unsigned char *dercert = NULL; - size_t dercertlen; - - if ((str_base64_decode((char **)(&dercert), &dercertlen, - b64, b64len) == 0) && - (dercertlen > 0)) - { - secitem->data = dercert; - secitem->len = (unsigned int) dercertlen; - return true; - } - else - { - DEBUGPRINTF("Base64 decode failed for: %s\n", b64); - } - return false; -} - -/** - * @brief Store DER certificate in mozilla store. - * @param[in] pdir the mozilla profile directory with the certificate - * store to manipulate. - * @param[in] dercert pointer to a SECItem holding the DER certificate - * to install - * @returns true on success and false on failure - */ -static bool -import_cert(char *pdir, SECItem *dercert) -{ - PK11SlotInfo *pk11slot = NULL; - CERTCertTrust *trust = NULL; - CERTCertificate *cert = NULL; - bool success = false; - char *cert_name = nss_cert_name(dercert); - - DEBUGPRINTF("INSTALLING cert: '%s' to: %s\n", cert_name, pdir); - pk11slot = PK11_GetInternalKeySlot(); - cert = CERT_DecodeCertFromPackage((char *)dercert->data, - (int)dercert->len); - trust = (CERTCertTrust *)xmalloc(sizeof(CERTCertTrust)); - CERT_DecodeTrustString(trust, "C,C,C"); - if (PK11_ImportCert(pk11slot, cert, CK_INVALID_HANDLE, - cert_name, PR_FALSE) == SECSuccess) - { - if(CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), cert, trust) == SECSuccess) - { - log_certificate_der (pdir, dercert->data, dercert->len, true); - success = true; - } - } - /* This could have happened on either the import cert or - the cert change trust. If Import Cert fails with that - error the certificate has in fact been added but with - random trist bits. See NSS Bug 595861. - Reference code can be found in gnome evolution under - smime/lib/e-cert-db.c */ - if(PORT_GetError() == SEC_ERROR_TOKEN_NOT_LOGGED_IN) - { - if (PK11_NeedUserInit (pk11slot)) - { - PK11_InitPin (pk11slot, "", ""); - } - if (PK11_Authenticate (pk11slot, PR_TRUE, NULL) != SECSuccess) - { - DEBUGPRINTF("Failed to authenticate.\n"); - } - else if(CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), cert, trust) == SECSuccess) - { - log_certificate_der (pdir, dercert->data, dercert->len, true); - success = true; - } - } - - if (!success) - { - DEBUGPRINTF("Failed to install certificate '%s' to '%s'!\n", cert_name, pdir); - ERRORPRINTF("Error installing certificate err: %i\n", PORT_GetError()); - } - CERT_DestroyCertificate (cert); - free(trust); - PK11_FreeSlot(pk11slot); - - free(cert_name); - return success; -} - -/** - * @brief Remove DER certificate from mozilla store. - * @param[in] pdir the mozilla profile directory with the certificate - * store to manipulate. - * @param[in] dercert pointer to a SECItem holding the DER certificate - * to remove - * @returns true on success and false on failure - */ -static bool -remove_cert(char *pdir, SECItem *dercert) -{ - PK11SlotInfo *pk11slot = NULL; - bool success = false; - char *cert_name = nss_cert_name(dercert); - CERTCertificate *cert = NULL; - - DEBUGPRINTF("REMOVING cert: '%s' from: %s\n", cert_name, pdir); - if (NSS_Initialize(pdir, "", "", "secmod.db", 0) == SECSuccess) - { - pk11slot = PK11_GetInternalKeySlot(); - cert = PK11_FindCertFromDERCertItem(pk11slot, - dercert, NULL); - if (cert != NULL) - { - if (SEC_DeletePermCertificate(cert) == SECSuccess) - { - success = true; - log_certificate_der (pdir, dercert->data, dercert->len, false); - } - else - { - DEBUGPRINTF("Failed to remove certificate '%s' from '%s'!\n", cert_name, pdir); - } - CERT_DestroyCertificate(cert); - } - else - { - DEBUGPRINTF("Could not find Certificate '%s' in store '%s'.\n", cert_name, pdir); - } - PK11_FreeSlot(pk11slot); - NSS_Shutdown(); - } - else - { - DEBUGPRINTF("Could not open nss certificate store in %s!\n", pdir); - } - free(cert_name); - return success; -} - -/** - * @brief Apply a function to a list of certificates and profiles - * - * The function must have the signature: - * - * bool function(char *pdir, SECItem der_cert) - * - * where pdir is the path of an profile and der_cert is an raw DER - * formatted certificate. The function must return true on success - * and false on failure. - * - * This function is intended for use with the import_cert and - * remove_cert functions. - * - * @param[in] fn the function to apply - * @param[inout] certs a secitem list holding the certificates - * the list will be change (emptied)! - * @param[in] pdirs the NULL terminated list of profile directories - * @returns true on success and false on failure - */ -bool -apply_to_certs_and_profiles(bool fn(char *, SECItem *), - seciteml_t **certs, char **pdirs) -{ - bool success = true; - - for (int i=0; pdirs[i] != NULL; i++) - { - seciteml_t *iter = *certs; - if (NSS_Initialize(pdirs[i], "", "", "secmod.db", 0) != SECSuccess) - { - DEBUGPRINTF("Could not open nss certificate store in %s!\n", pdirs[i]); - continue; - } - - while (iter != NULL && iter->item != NULL) - { - SECItem *cert = iter->item; - if (! (*fn)(pdirs[i], cert)) - success = false; - iter = iter->next; - } - NSS_Shutdown(); - } - - seciteml_free(certs); - - return success; -} - -/** - * @brief Parse IPC commands from standard input. - * - * Reads command lines (R: and I:) from standard input and puts the - * certificates to process in two SECItem lists holding the - * certificates in DER format. - * @param[inout] stream from standard input - * @param[inout] install_list list of SECItems with certifiactes to install - * @param[inout] remove_list list of SECItems with certifiactes to remove - */ -static void -parse_commands (FILE *stream, - seciteml_t **install_list, seciteml_t **remove_list) -{ - char inpl[LINEBUFLEN]; - size_t inpllen; - bool parserr = true; - SECItem secitem; - - while ( fgets(inpl, LINEBUFLEN, stream) != NULL ) - { - inpllen = strnlen(inpl, LINEBUFLEN); - /* Validate input line: - * - must be (much) longer than 3 characters - * - must start with "*:" - */ - if ((inpllen > 3) && (inpl[1] == ':')) - /* Now parse Input */ - switch(inpl[0]) - { - case 'R': - parserr = true; - DEBUGPRINTF("Request to remove certificate: %s\n", &inpl[2]); - if (base64_to_secitem(&inpl[2], inpllen - 2, &secitem)) - { - seciteml_push(remove_list, &secitem); - parserr = false; - } - break; - case 'I': - parserr = true; - DEBUGPRINTF("Request to install certificate: %s\n", &inpl[2]); - if (base64_to_secitem(&inpl[2], inpllen - 2, &secitem)) - { - seciteml_push(install_list, &secitem); - parserr = false; - } - break; - default: - parserr = true; - } - else - { - parserr = true; - } - - if (parserr) - { - ERRORPRINTF("FATAL: Invalid input: %s\n", inpl); - exit(ERR_MOZ_INVALID_INPUT); - } - } -} - -#ifdef DO_RELEASE_BUILD -bool g_debug = false; -#else -bool g_debug = true; -#endif - -int -main (int argc, char **argv) -{ - char **dbdirs; - seciteml_t *certs_to_remove = NULL; - seciteml_t *certs_to_add = NULL; - FILE *input_stream; - - switch (argc) - { - case 1: - DEBUGPRINTF("Opening STDIN for input...\n"); - input_stream = stdin; - break; - case 2: - if (strcmp(argv[1], "--debug") == 0) - { - g_debug = true; - DEBUGPRINTF("Opening STDIN for input...\n"); - input_stream = stdin; - break; - } - case 3: - DEBUGPRINTF("Opening %s for input...\n", argv[1]); - if ((input_stream = fopen(argv[1], "r")) == NULL) - { - ERRORPRINTF ("FATAL: Could not open %s for reading!\n", - argv[1]); - exit_code = ERR_MOZ_FAILED_TO_OPEN_INPUT; - goto exit; - } - if (argc == 3 && strcmp(argv[2], "--debug") == 0) - { - g_debug = true; - } - break; - default: - ERRORPRINTF("FATAL: Wrong number of arguments!\n"); - exit_code = ERR_MOZ_WRONG_ARGC; - goto exit; - } - - dbdirs = - get_all_nssdb_dirs(); - - if (dbdirs != NULL) - { - parse_commands(input_stream, &certs_to_add, &certs_to_remove); - -#ifdef DEBUGOUTPUT - DEBUGPRINTF("OLD List of installed certs:\n"); - for (int i=0; dbdirs[i] != NULL; i++) - DEBUG_nss_list_certs(dbdirs[i]); -#endif - - if (! apply_to_certs_and_profiles(remove_cert, &certs_to_remove, dbdirs)) - exit_code |= WARN_MOZ_COULD_NOT_REMOVE_CERT; - - if (! apply_to_certs_and_profiles(import_cert, &certs_to_add, dbdirs)) - exit_code |= WARN_MOZ_COULD_NOT_ADD_CERT; - -#ifdef DEBUGOUTPUT - DEBUGPRINTF("NEW List of installed certs:\n"); - for (int i=0; dbdirs[i] != NULL; i++) - DEBUG_nss_list_certs(dbdirs[i]); -#endif - -#ifndef WIN32 - if (is_elevated()) - { - make_defaults_readable(); - } -#endif - - strv_free(dbdirs); - } - - fclose(input_stream); - -exit: - exit(exit_code); -} diff -r 7175d117e69a -r e210ecc32d69 cinst/nss-installer.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cinst/nss-installer.c Mon Sep 22 11:19:43 2014 +0200 @@ -0,0 +1,925 @@ +/* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik + * Software engineering by Intevation GmbH + * + * This file is Free Software under the GNU GPL (v>=2) + * and comes with ABSOLUTELY NO WARRANTY! + * See LICENSE.txt for details. + */ +/** + * @file + * @brief NSS store certificate installation process + * + * Reads from a file given on command line or stdin a list of + * instructions in the form: + * + * I:\
+ * R:\ + * ... + * + * With one instruction per line. the maximum size of an input + * line is 9999 characters (including the \\r\\n) at the end of the line. + * + * Certificates marked with I: will be installed and the ones + * marked with R: will be searched and if available removed from + * the databases. + * + * This tool tries to find all NSS databases the user has + * access to and to execute the instructions on all of them. + * + * If the tool is executed with a UID of 0 or with admin privileges under + * windows it will not look into the user directories but instead try + * to write the system wide defaults. + * + * If there are other processes accessing the databases the caller + * has to ensure that those are terminated before this process is + * executed. + * + * If the same certificate is marked to be installed and to be removed + * in one call the behavior is undefined. This should be avoided and + * may lead to errors. + * + * Returns 0 on success (Even when no stores where found) an error value + * as defined in errorcodes.h otherwise. + * + * Success messages are written to stdout. Errors to stderr. For logging + * purposes each installation / removal of a certificate will be reported + * with the profile name that it modified. + * + * To get more verbose output add the --debug parameter + * as the last parameter on the command line. + * + */ + +/** + * @brief Needs to be defined to get strnlen() + */ +#define _POSIX_C_SOURCE 200809L + +/* REMOVEME: */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUGPREFIX "MOZ-" +#include "logging.h" + +#include "certhelp.h" +#include "errorcodes.h" +#include "portpath.h" +#include "strhelp.h" +#include "nss-secitemlist.h" +#include "util.h" + +#ifndef _WIN32 +#define CONFDIRS ".mozilla", ".thunderbird" +/* Default installation directory of ubuntu 14.4 is respected */ +#define MOZILLA_DEFAULTS "/usr/lib/thunderbird/defaults", "/usr/lib/firefox/browser/defaults" +#define MOZILLA_DBNAMES "cert8.db", "key3.db", "secmod.db" +#define NSSSHARED ".pki/nssdb" +#define NSSSHARED_GLOBAL "/etc/skel/.pki/nssdb" +#define TARGET_LINUX 1 +#define DIRSEP "/" +#else +#define MOZILLA_DEFAULTS "Mozilla Firefox\\browser\\defaults", "Mozilla Thunderbird\\defaults" +#define MOZILLA_DBNAMES NULL +#define CONFDIRS "Mozilla", "Thunderbird" +#define NSSSHARED "" +#define TARGET_LINUX NULL +#define DIRSEP "\\" +#endif + +/** + * @brief Length of string buffers used + * + * The maximal length of input is defined as 9999 (+ terminating \0). + * We use it for other other input puffers besides the IPC input, too. + * (One size fits all). + */ +#define LINEBUFLEN 10000 + +#ifdef _WIN32 +#define STRTOK_R strtok_s +#else +#define STRTOK_R strtok_r +#endif + +/** + * @brief Global Return Code + * + * This will be retuned by the programm and might be set to an + * error code on fatal errors and to and warning code on non-fatal + * errors. In case of mor than one warning the warning codes will be + * ORed together. + */ +int exit_code = 0; + +/** + * @brief Return configuration base directory. + * @returns A pointer to a string containing the path to the base + * directory holding the configuration directories for e.g. mozilla + * and thunderbird. + */ +static char * +get_conf_basedir() +{ + char *cdir, *envvar; + + if (TARGET_LINUX) + envvar = "HOME" ; + else + envvar = "APPDATA"; + + if ((cdir = getenv(envvar)) != NULL) + return cdir; + else + { + ERRORPRINTF("FATAL! No %s in environment.\n", envvar); + exit(ERR_MOZ_HOMELESS); + } +} + +/** + * @brief Get a list of all mozilla profile directories + * + * Parse the profiles.ini and extract all profile paths from that. + * The expected data is in the form: + * + * [Profile99]
+ * IsRelative=1
+ * Path=Example/foo.bar + * + * or
+ * [Profile0]
+ * IsRelative=0
+ * Path=c:\\foo\\bar\\baz + * + * Mozilla also accepts the ini file on Windows even if it is UTF-16 + * encoded but never writes UTF-16 on its own. So currently we ignore + * this special case. + * + * @param[in] inifile_name path of the profile.ini to read. + * @return NULL terminated array of strings containing containing the + * absolute path of the profile directories. The array needs to + * be freed by the caller. + */ +static char ** +get_profile_dirs (char *inifile_name) +{ + char **dirs = NULL; + char *inifile_dirname; + FILE *inifile; + char line[LINEBUFLEN]; + char *key; + char *value; + char *path = NULL; + char *fqpath; + bool inprofile = false; + bool relative_path = false; + char *saveptr; + + if ((inifile = fopen(inifile_name, "r")) != NULL) + { + DEBUGPRINTF("Searching for profile paths in: '%s'\n", inifile_name); + + inifile_dirname = port_dirname(inifile_name); + while (fgets(line, LINEBUFLEN, inifile) != NULL) + { + /* Determine if we are in an profile section */ + if (str_starts_with(line, "[Profile")) + { + relative_path = false; + inprofile = true; + } + else if (line[0] == '[') + inprofile = false; + + /* If we are in a profile parse path related stuff */ + if (inprofile) + { + saveptr = NULL; + key = STRTOK_R(line, "=", &saveptr); + value = STRTOK_R(NULL, "=", &saveptr); + str_trim(&value); + if (str_equal(key, "Path")) + { + if (relative_path) + xasprintf(&path, "%s/%s", inifile_dirname, value); + else + xasprintf(&path, "%s", value); + if ((fqpath = port_realpath(path)) != NULL) + { + DEBUGPRINTF("Found profile path: '%s'\n", fqpath); + strv_append(&dirs, fqpath, strlen(fqpath)); + free (fqpath); + } + else + { + DEBUGPRINTF("WARN! Non existent profile path: '%s'\n", path); + exit_code |= WARN_MOZ_PROFILE_DOES_NOT_EXIST; + } + free(path); + } + else if (str_equal(key, "IsRelative") && + str_starts_with(value, "1")) + relative_path = true; + } + } + fclose(inifile); + } + else + { + DEBUGPRINTF("WARN! Could not open ini file: '%s'\n", inifile_name); + exit_code |= WARN_MOZ_FAILED_TO_OPEN_INI; + } + return dirs; +} + +/** + * @brief Search for mozilla profiles.ini files + * + * Use well known paths and heuristics to find the current users + * profiles.ini files on GNU/Linux and Windows systems. + * + * @return NULL terminated array of strings containing the absolute + * path of the profiles.ini files. The array needs to be freed by the + * caller. + */ +static char ** +get_profile_inis () +{ + char **inis = NULL; + char *mozpath, *fqpath, *subpath, *ppath; + DIR *mozdir; + struct dirent *mozdirent; + char *confbase = get_conf_basedir(); + const char *confdirs[] = { CONFDIRS, NULL }; + + for (int i=0; confdirs[i] != NULL; i++) + { + xasprintf(&mozpath,"%s/%s", confbase, confdirs[i]); + if ((mozdir = opendir(mozpath)) != NULL) + { + while ((mozdirent = readdir(mozdir)) != NULL) + { + xasprintf(&subpath, "%s/%s/%s", + confbase, + confdirs[i], + mozdirent->d_name); + if (port_isdir(subpath) + && (strcmp(mozdirent->d_name, "..") != 0)) + { + xasprintf(&ppath, "%s/%s/%s/%s", + confbase, + confdirs[i], + mozdirent->d_name, + "profiles.ini"); + DEBUGPRINTF("checking for %s...\n", ppath); + if ((fqpath = port_realpath(ppath)) != NULL) + { + strv_append(&inis, fqpath, strlen(fqpath)); + DEBUGPRINTF("Found mozilla ini file: '%s'\n", fqpath); + free(fqpath); + } + free(ppath); + } + free(subpath); + } + closedir(mozdir); + } + else + { + DEBUGPRINTF("Could not open %s/%s\n", confbase, confdirs[i]); + } + free(mozpath); + } + if (inis == NULL) + { + DEBUGPRINTF("No ini files found - will do nothing!\n"); + } + return inis; +} + + +/** @brief make the default nss databases readable. + * + * This uses the static paths definied in this code to ensure + * that only the defaults are touched. + * + */ +#ifndef WIN32 +static void +make_defaults_readable() +{ + const char *confdirs[] = { MOZILLA_DEFAULTS, NULL }; + const char *filenames[] = { MOZILLA_DBNAMES, NULL }; + + mode_t access_mask = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + + for (int i=0; confdirs[i] != NULL; i++) + { + for (int j=0; filenames[j] != NULL; j++) + { + char *realpath = NULL, + *path = NULL; + xasprintf (&path, "%s/profile/%s", confdirs[i], filenames[j]); + realpath = port_realpath(path); + xfree(path); + if (!realpath) + { + syslog_error_printf("Failed to find %s \n", realpath); + continue; + } + if (chmod(realpath, access_mask)) + { + syslog_error_printf("Failed to set access_mask on file.\n"); + } + xfree (realpath); + } + } +} +#endif + +/** + * @brief Collect the default profile directories for mozilla software + * + * If the default directory is found but not the profiles subdirectory + * this will create the profiles subdirectory. + * + * @return NULL terminated array of strings containing the absolute path + * to the default profile directories. Needs to be freed by the caller. + */ +static char** +get_default_profile_dirs() +{ + char **retval = NULL; + + const char *confdirs[] = { MOZILLA_DEFAULTS, NULL }; + +#ifdef _WIN32 + char *program_files = get_program_files_folder(); + if (!program_files) + { + ERRORPRINTF ("Failed to look up program files folder.\n"); + return NULL; + } +#endif + + for (int i=0; confdirs[i] != NULL; i++) + { + char *realpath = NULL, + *profile_dir = NULL; +#ifndef _WIN32 + realpath = port_realpath(confdirs[i]); +#else + /* As on linux we only respect the default installation directory + mozilla firefox and thunderbird change their registry key with + each version as the key includes the version number. It would + be error prone to search the system for every instance. So we + only check the default installation directories. */ + xasprintf(&realpath, "%s" DIRSEP "%s", program_files, confdirs[i]); +#endif + if (realpath == NULL) + { + DEBUGPRINTF ("Did not find directory: '%s'\n", confdirs[i]); + continue; + } + xasprintf(&profile_dir, "%s" DIRSEP "profile", realpath); + xfree(realpath); + if (port_isdir(profile_dir)) + { + DEBUGPRINTF("Found default directory: '%s'\n", profile_dir); + /* All is well */ + strv_append (&retval, profile_dir, strlen(profile_dir)); + xfree(profile_dir); + profile_dir = NULL; + continue; + } + else + { + /* Create the directory */ + if (port_fileexits(profile_dir)) + { + DEBUGPRINTF ("Path: '%s' is not a directory but it exists. Skipping.\n", + profile_dir); + xfree(profile_dir); + profile_dir = NULL; + continue; + } + else + { + /* Lets create it */ + if (!port_mkdir_p(profile_dir, true)) + { + ERRORPRINTF ("Failed to create directory: '%s'\n", profile_dir); + xfree(profile_dir); + profile_dir = NULL; + continue; + } + strv_append (&retval, profile_dir, strlen(profile_dir)); + xfree(profile_dir); + profile_dir = NULL; + } + } + } +#ifdef WIN32 + xfree (program_files); +#endif + return retval; +} + +/** + * @brief Collect all mozilla profile directories of current user. + * @return NULL terminated array of strings containing the absolute + * path of the profile directories. The array needs to be freed by the + * caller. + */ +static char** +get_all_nssdb_dirs() +{ + char **mozinis, **pdirs; + char **alldirs = NULL; + + if (is_elevated()) + { +#ifndef _WIN32 + /* NSS Shared db does not exist under windows. */ + if (!port_mkdir_p(NSSSHARED_GLOBAL, false)) + { + ERRORPRINTF("Failed to create nssshared skeleton directory. \n"); + } + else + { + strv_append(&alldirs, "sql:" NSSSHARED_GLOBAL, strlen("sql:" NSSSHARED_GLOBAL)); + } +#endif + pdirs = get_default_profile_dirs(); + if (pdirs != NULL) + { + for (int i=0; pdirs[i] != NULL; i++) + { + strv_append(&alldirs, pdirs[i], strlen(pdirs[i])); + } + strv_free(pdirs); + } + return alldirs; + } + /* Search Mozilla/Firefox/Thunderbird profiles */ + if ((mozinis = get_profile_inis()) != NULL) + { + for (int i=0; mozinis[i] != NULL; i++) + { + pdirs = + get_profile_dirs(mozinis[i]); + if (pdirs != NULL) + { + for (int i=0; pdirs[i] != NULL; i++) + { + strv_append(&alldirs, pdirs[i], strlen(pdirs[i])); + } + strv_free(pdirs); + } + } + strv_free(mozinis); + } + /* Search for NSS shared DB (used by Chrome/Chromium on GNU/Linux) */ + if (TARGET_LINUX) + { + char *path, *fqpath, *sqlpath; + xasprintf(&path, "%s/%s", get_conf_basedir(), NSSSHARED); + if ((fqpath = port_realpath(path)) != NULL) + { + xasprintf(&sqlpath, "sql:%s", fqpath); + strv_append(&alldirs, sqlpath, strlen(sqlpath)); + free(sqlpath); + free(fqpath); + } + free(path); + } + return alldirs; +} + +#ifdef DEBUGOUTPUT +/** + * @brief list certificates from nss certificate store + * @param[in] confdir the directory with the certificate store + */ +static void +DEBUG_nss_list_certs (char *confdir) +{ + CERTCertList *list; + CERTCertListNode *node; + char *name; + + if (NSS_Initialize(confdir, "", "", "secmod.db", NSS_INIT_READONLY) + == SECSuccess) + { + DEBUGPRINTF("Listing certs in \"%s\"\n", confdir); + list = PK11_ListCerts(PK11CertListAll, NULL); + for (node = CERT_LIST_HEAD(list); !CERT_LIST_END(node, list); + node = CERT_LIST_NEXT(node)) + { + name = node->appData; + + DEBUGPRINTF("Found certificate \"%s\"\n", name); + } + /* According to valgrind this leaks memory in the list. + We could not find API documentation to better free this + so we accept the leakage here in case of debug. */ + CERT_DestroyCertList(list); + NSS_Shutdown(); + } + else + { + DEBUGPRINTF("Could not open nss certificate store in %s!\n", confdir); + } +} +#endif + +/** + * @brief Create a string with the name for cert in SECItem. + * + * Should be freed by caller. + * @param[in] secitemp ponts to an SECItem holding the DER certificate. + * @returns a string of the from "CN of Subject - O of Subject" + */ +static char * +nss_cert_name(SECItem *secitemp) +{ + char *cn_str, *o_str, *name; + size_t name_len; + cn_str = x509_parse_subject(secitemp->data, secitemp->len, CERT_OID_CN); + o_str = x509_parse_subject(secitemp->data, secitemp->len, CERT_OID_O); + if (!cn_str || !o_str) + { + ERRORPRINTF("FATAL: Could not parse certificate!"); + exit(ERR_INVALID_CERT); + } + name_len = strlen(cn_str) + strlen(o_str) + 4; + name = (char *)xmalloc(name_len); + snprintf(name, name_len, "%s - %s", cn_str, o_str); + free(cn_str); + free(o_str); + return name; +} + +/** + * @brief Convert a base64 encoded DER certificate to SECItem + * @param[in] b64 pointer to the base64 encoded certificate + * @param[in] b64len length of the base64 encoded certificate + * @param[out] secitem pointer to the SECItem in which to store the + * raw DER certifiacte. + * @returns true on success and false on failure + */ +static bool +base64_to_secitem(char *b64, size_t b64len, SECItem *secitem) +{ + unsigned char *dercert = NULL; + size_t dercertlen; + + if ((str_base64_decode((char **)(&dercert), &dercertlen, + b64, b64len) == 0) && + (dercertlen > 0)) + { + secitem->data = dercert; + secitem->len = (unsigned int) dercertlen; + return true; + } + else + { + DEBUGPRINTF("Base64 decode failed for: %s\n", b64); + } + return false; +} + +/** + * @brief Store DER certificate in mozilla store. + * @param[in] pdir the mozilla profile directory with the certificate + * store to manipulate. + * @param[in] dercert pointer to a SECItem holding the DER certificate + * to install + * @returns true on success and false on failure + */ +static bool +import_cert(char *pdir, SECItem *dercert) +{ + PK11SlotInfo *pk11slot = NULL; + CERTCertTrust *trust = NULL; + CERTCertificate *cert = NULL; + bool success = false; + char *cert_name = nss_cert_name(dercert); + + DEBUGPRINTF("INSTALLING cert: '%s' to: %s\n", cert_name, pdir); + pk11slot = PK11_GetInternalKeySlot(); + cert = CERT_DecodeCertFromPackage((char *)dercert->data, + (int)dercert->len); + trust = (CERTCertTrust *)xmalloc(sizeof(CERTCertTrust)); + CERT_DecodeTrustString(trust, "C,C,C"); + if (PK11_ImportCert(pk11slot, cert, CK_INVALID_HANDLE, + cert_name, PR_FALSE) == SECSuccess) + { + if(CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), cert, trust) == SECSuccess) + { + log_certificate_der (pdir, dercert->data, dercert->len, true); + success = true; + } + } + /* This could have happened on either the import cert or + the cert change trust. If Import Cert fails with that + error the certificate has in fact been added but with + random trist bits. See NSS Bug 595861. + Reference code can be found in gnome evolution under + smime/lib/e-cert-db.c */ + if(PORT_GetError() == SEC_ERROR_TOKEN_NOT_LOGGED_IN) + { + if (PK11_NeedUserInit (pk11slot)) + { + PK11_InitPin (pk11slot, "", ""); + } + if (PK11_Authenticate (pk11slot, PR_TRUE, NULL) != SECSuccess) + { + DEBUGPRINTF("Failed to authenticate.\n"); + } + else if(CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), cert, trust) == SECSuccess) + { + log_certificate_der (pdir, dercert->data, dercert->len, true); + success = true; + } + } + + if (!success) + { + DEBUGPRINTF("Failed to install certificate '%s' to '%s'!\n", cert_name, pdir); + ERRORPRINTF("Error installing certificate err: %i\n", PORT_GetError()); + } + CERT_DestroyCertificate (cert); + free(trust); + PK11_FreeSlot(pk11slot); + + free(cert_name); + return success; +} + +/** + * @brief Remove DER certificate from mozilla store. + * @param[in] pdir the mozilla profile directory with the certificate + * store to manipulate. + * @param[in] dercert pointer to a SECItem holding the DER certificate + * to remove + * @returns true on success and false on failure + */ +static bool +remove_cert(char *pdir, SECItem *dercert) +{ + PK11SlotInfo *pk11slot = NULL; + bool success = false; + char *cert_name = nss_cert_name(dercert); + CERTCertificate *cert = NULL; + + DEBUGPRINTF("REMOVING cert: '%s' from: %s\n", cert_name, pdir); + if (NSS_Initialize(pdir, "", "", "secmod.db", 0) == SECSuccess) + { + pk11slot = PK11_GetInternalKeySlot(); + cert = PK11_FindCertFromDERCertItem(pk11slot, + dercert, NULL); + if (cert != NULL) + { + if (SEC_DeletePermCertificate(cert) == SECSuccess) + { + success = true; + log_certificate_der (pdir, dercert->data, dercert->len, false); + } + else + { + DEBUGPRINTF("Failed to remove certificate '%s' from '%s'!\n", cert_name, pdir); + } + CERT_DestroyCertificate(cert); + } + else + { + DEBUGPRINTF("Could not find Certificate '%s' in store '%s'.\n", cert_name, pdir); + } + PK11_FreeSlot(pk11slot); + NSS_Shutdown(); + } + else + { + DEBUGPRINTF("Could not open nss certificate store in %s!\n", pdir); + } + free(cert_name); + return success; +} + +/** + * @brief Apply a function to a list of certificates and profiles + * + * The function must have the signature: + * + * bool function(char *pdir, SECItem der_cert) + * + * where pdir is the path of an profile and der_cert is an raw DER + * formatted certificate. The function must return true on success + * and false on failure. + * + * This function is intended for use with the import_cert and + * remove_cert functions. + * + * @param[in] fn the function to apply + * @param[inout] certs a secitem list holding the certificates + * the list will be change (emptied)! + * @param[in] pdirs the NULL terminated list of profile directories + * @returns true on success and false on failure + */ +bool +apply_to_certs_and_profiles(bool fn(char *, SECItem *), + seciteml_t **certs, char **pdirs) +{ + bool success = true; + + for (int i=0; pdirs[i] != NULL; i++) + { + seciteml_t *iter = *certs; + if (NSS_Initialize(pdirs[i], "", "", "secmod.db", 0) != SECSuccess) + { + DEBUGPRINTF("Could not open nss certificate store in %s!\n", pdirs[i]); + continue; + } + + while (iter != NULL && iter->item != NULL) + { + SECItem *cert = iter->item; + if (! (*fn)(pdirs[i], cert)) + success = false; + iter = iter->next; + } + NSS_Shutdown(); + } + + seciteml_free(certs); + + return success; +} + +/** + * @brief Parse IPC commands from standard input. + * + * Reads command lines (R: and I:) from standard input and puts the + * certificates to process in two SECItem lists holding the + * certificates in DER format. + * @param[inout] stream from standard input + * @param[inout] install_list list of SECItems with certifiactes to install + * @param[inout] remove_list list of SECItems with certifiactes to remove + */ +static void +parse_commands (FILE *stream, + seciteml_t **install_list, seciteml_t **remove_list) +{ + char inpl[LINEBUFLEN]; + size_t inpllen; + bool parserr = true; + SECItem secitem; + + while ( fgets(inpl, LINEBUFLEN, stream) != NULL ) + { + inpllen = strnlen(inpl, LINEBUFLEN); + /* Validate input line: + * - must be (much) longer than 3 characters + * - must start with "*:" + */ + if ((inpllen > 3) && (inpl[1] == ':')) + /* Now parse Input */ + switch(inpl[0]) + { + case 'R': + parserr = true; + DEBUGPRINTF("Request to remove certificate: %s\n", &inpl[2]); + if (base64_to_secitem(&inpl[2], inpllen - 2, &secitem)) + { + seciteml_push(remove_list, &secitem); + parserr = false; + } + break; + case 'I': + parserr = true; + DEBUGPRINTF("Request to install certificate: %s\n", &inpl[2]); + if (base64_to_secitem(&inpl[2], inpllen - 2, &secitem)) + { + seciteml_push(install_list, &secitem); + parserr = false; + } + break; + default: + parserr = true; + } + else + { + parserr = true; + } + + if (parserr) + { + ERRORPRINTF("FATAL: Invalid input: %s\n", inpl); + exit(ERR_MOZ_INVALID_INPUT); + } + } +} + +#ifdef DO_RELEASE_BUILD +bool g_debug = false; +#else +bool g_debug = true; +#endif + +int +main (int argc, char **argv) +{ + char **dbdirs; + seciteml_t *certs_to_remove = NULL; + seciteml_t *certs_to_add = NULL; + FILE *input_stream; + + switch (argc) + { + case 1: + DEBUGPRINTF("Opening STDIN for input...\n"); + input_stream = stdin; + break; + case 2: + if (strcmp(argv[1], "--debug") == 0) + { + g_debug = true; + DEBUGPRINTF("Opening STDIN for input...\n"); + input_stream = stdin; + break; + } + case 3: + DEBUGPRINTF("Opening %s for input...\n", argv[1]); + if ((input_stream = fopen(argv[1], "r")) == NULL) + { + ERRORPRINTF ("FATAL: Could not open %s for reading!\n", + argv[1]); + exit_code = ERR_MOZ_FAILED_TO_OPEN_INPUT; + goto exit; + } + if (argc == 3 && strcmp(argv[2], "--debug") == 0) + { + g_debug = true; + } + break; + default: + ERRORPRINTF("FATAL: Wrong number of arguments!\n"); + exit_code = ERR_MOZ_WRONG_ARGC; + goto exit; + } + + dbdirs = + get_all_nssdb_dirs(); + + if (dbdirs != NULL) + { + parse_commands(input_stream, &certs_to_add, &certs_to_remove); + +#ifdef DEBUGOUTPUT + DEBUGPRINTF("OLD List of installed certs:\n"); + for (int i=0; dbdirs[i] != NULL; i++) + DEBUG_nss_list_certs(dbdirs[i]); +#endif + + if (! apply_to_certs_and_profiles(remove_cert, &certs_to_remove, dbdirs)) + exit_code |= WARN_MOZ_COULD_NOT_REMOVE_CERT; + + if (! apply_to_certs_and_profiles(import_cert, &certs_to_add, dbdirs)) + exit_code |= WARN_MOZ_COULD_NOT_ADD_CERT; + +#ifdef DEBUGOUTPUT + DEBUGPRINTF("NEW List of installed certs:\n"); + for (int i=0; dbdirs[i] != NULL; i++) + DEBUG_nss_list_certs(dbdirs[i]); +#endif + +#ifndef WIN32 + if (is_elevated()) + { + make_defaults_readable(); + } +#endif + + strv_free(dbdirs); + } + + fclose(input_stream); + +exit: + exit(exit_code); +} diff -r 7175d117e69a -r e210ecc32d69 cinst/nssstore_linux.c --- a/cinst/nssstore_linux.c Fri Sep 19 17:47:03 2014 +0200 +++ b/cinst/nssstore_linux.c Mon Sep 22 11:19:43 2014 +0200 @@ -27,7 +27,7 @@ #include "strhelp.h" #include "util.h" -#define NSS_PROCESS_NAME "mozilla" +#define NSS_PROCESS_NAME "trustbridge-nss-installer" /**@brief Start the process to install / remove diff -r 7175d117e69a -r e210ecc32d69 cinst/nssstore_win.c --- a/cinst/nssstore_win.c Fri Sep 19 17:47:03 2014 +0200 +++ b/cinst/nssstore_win.c Mon Sep 22 11:19:43 2014 +0200 @@ -11,11 +11,11 @@ @brief Windows implementation of nssstore process control. The windows process will write an instructions file for - the mozilla process into the current users temp directory + the nss-installer process into the current users temp directory (%APPDATA%/Local/Temp/) and start the NSS installation process to exectute those instructions. If the current process is elevated the NSS process is run with a restricted token. - The execution of the mozilla process is not monitored. + The execution of the nss-installer process is not monitored. You have to refer to the system log to check which certificates were installed / removed by it. @@ -57,7 +57,7 @@ #endif /**@def The name of the nss installation process */ -#define NSS_APP_NAME L"mozilla.exe" +#define NSS_APP_NAME L"trustbridge-nss-installer.exe" #ifndef SELECTION_FILE_NAME #define SELECTION_FILE_NAME L"currently_selected.txt" diff -r 7175d117e69a -r e210ecc32d69 common/errorcodes.h --- a/common/errorcodes.h Fri Sep 19 17:47:03 2014 +0200 +++ b/common/errorcodes.h Mon Sep 22 11:19:43 2014 +0200 @@ -32,7 +32,7 @@ #define ERR_INVALID_PARAMS 11 /*********************************************************************** - * mozilla specific errors and warnings + * nss-installer specific errors and warnings * errors range from 0x0081 to 0x08F * warnings from 0x0091 to 0x0098 * Warnings might be ORed together ... diff -r 7175d117e69a -r e210ecc32d69 packaging/create-dist-package.sh.in --- a/packaging/create-dist-package.sh.in Fri Sep 19 17:47:03 2014 +0200 +++ b/packaging/create-dist-package.sh.in Mon Sep 22 11:19:43 2014 +0200 @@ -48,7 +48,7 @@ $TMPDIR/linux/TrustBridge-@PROJECT_VERSION@-i386.sh cp @CMAKE_SOURCE_DIR@/build-windows/ui/trustbridge.exe $TMPDIR/windows cp @CMAKE_SOURCE_DIR@/build-windows/cinst/cinst.exe $TMPDIR/windows -cp @CMAKE_SOURCE_DIR@/build-windows/cinst/mozilla.exe $TMPDIR/windows +cp @CMAKE_SOURCE_DIR@/build-windows/cinst/trustbridge-nss-installer.exe $TMPDIR/windows cp -r @CMAKE_SOURCE_DIR@/packaging/resources $TMPDIR/resources cp @CMAKE_SOURCE_DIR@/build-windows/packaging/DesktopShellRun.dll $TMPDIR/resources cp -r @CMAKE_BINARY_DIR@/manuals/help-manual/html $TMPDIR/windows/doc diff -r 7175d117e69a -r e210ecc32d69 packaging/filelist.nsh --- a/packaging/filelist.nsh Fri Sep 19 17:47:03 2014 +0200 +++ b/packaging/filelist.nsh Mon Sep 22 11:19:43 2014 +0200 @@ -10,5 +10,5 @@ File "${files_dir}${path_sep}cinst.exe" File "${files_dir}${path_sep}trustbridge.exe" -File "${files_dir}${path_sep}mozilla.exe" +File "${files_dir}${path_sep}trustbridge-nss-installer.exe" File /r /x .buildinfo "${files_dir}${path_sep}doc" diff -r 7175d117e69a -r e210ecc32d69 packaging/linux-createpackage.sh.in --- a/packaging/linux-createpackage.sh.in Fri Sep 19 17:47:03 2014 +0200 +++ b/packaging/linux-createpackage.sh.in Mon Sep 22 11:19:43 2014 +0200 @@ -42,7 +42,7 @@ UNINSTALLER="$TMPDIR/bin/trustbridge-deinstall.sh" EXEFILES=("@CMAKE_BINARY_DIR@/cinst/cinst" - "@CMAKE_BINARY_DIR@/cinst/mozilla" + "@CMAKE_BINARY_DIR@/cinst/trustbridge-nss-installer" "@CMAKE_BINARY_DIR@/ui/trustbridge" "@CMAKE_BINARY_DIR@/ui/trustbridge-tray-starter.sh" "$UNINSTALLER") diff -r 7175d117e69a -r e210ecc32d69 packaging/win-createpackage.sh.in --- a/packaging/win-createpackage.sh.in Fri Sep 19 17:47:03 2014 +0200 +++ b/packaging/win-createpackage.sh.in Mon Sep 22 11:19:43 2014 +0200 @@ -13,7 +13,7 @@ TMPDIR=$(mktemp -d) TMPINST=$(mktemp) -EXEFILES=$(find . -name cinst.exe -o -name trustbridge.exe -o -name mozilla.exe) +EXEFILES=$(find . -name cinst.exe -o -name trustbridge.exe -o -name trustbridge-nss-installer.exe) HELPDIR=@CMAKE_BINARY_DIR@/manuals/help-manual/html cp $EXEFILES $TMPDIR diff -r 7175d117e69a -r e210ecc32d69 ui/tests/CMakeLists.txt --- a/ui/tests/CMakeLists.txt Fri Sep 19 17:47:03 2014 +0200 +++ b/ui/tests/CMakeLists.txt Mon Sep 22 11:19:43 2014 +0200 @@ -151,15 +151,15 @@ target_link_libraries(nsstest -luserenv) endif() - add_dependencies(nsstest mozilla) + add_dependencies(nsstest trustbridge-nss-installer) - # NSS code searches the mozilla process in the same path as the caller + # NSS code searches the trustbridge-nss-installer process in the same path as the caller if (WIN32) - file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/mozilla.exe" link) - file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/../../cinst/mozilla.exe" target) + file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/trustbridge-nss-installer.exe" link) + file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/../../cinst/trustbridge-nss-installer.exe" target) else() - file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/mozilla" link) - file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/../../cinst/mozilla" target) + file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/trustbridge-nss-installer" link) + file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/../../cinst/trustbridge-nss-installer" target) endif() if (NOT EXISTS ${link})