/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "seccomon.h" #include "prio.h" #include "prprf.h" #include "plhash.h" #include "prenv.h" /* * The following provides a default example for operating systems to set up * and manage applications loading NSS on their OS globally. * * This code hooks in to the system pkcs11.txt, which controls all the loading * of pkcs11 modules common to all applications. */ #ifndef LINUX #error __FILE__ only builds on Linux. #endif #include #include #include static int testdir(char *dir) { struct stat buf; memset(&buf, 0, sizeof(buf)); if (stat(dir, &buf) < 0) { return 0; } return S_ISDIR(buf.st_mode); } /** * Append given @dir to @path and creates the directory with mode @mode. * Returns 0 if successful, -1 otherwise. * Assumes that the allocation for @path has sufficient space for @dir * to be added. */ static int appendDirAndCreate(char *path, char *dir, mode_t mode) { PORT_Strcat(path, dir); if (!testdir(path)) { if (mkdir(path, mode)) { return -1; } } return 0; } #define XDG_NSS_USER_PATH1 "/.local" #define XDG_NSS_USER_PATH2 "/share" #define XDG_NSS_USER_PATH3 "/pki" #define NSS_USER_PATH1 "/.pki" #define NSS_USER_PATH2 "/nssdb" /** * Return the path to user's NSS database. * We search in the following dirs in order: * (1) $HOME/.pki/nssdb; * (2) $XDG_DATA_HOME/pki/nssdb if XDG_DATA_HOME is set; * (3) $HOME/.local/share/pki/nssdb (default XDG_DATA_HOME value). * If (1) does not exist, then the returned dir will be set to either * (2) or (3), depending if XDG_DATA_HOME is set. */ char * getUserDB(void) { char *userdir = PR_GetEnvSecure("HOME"); char *nssdir = NULL; if (userdir == NULL) { return NULL; } nssdir = PORT_Alloc(strlen(userdir) + sizeof(NSS_USER_PATH1) + sizeof(NSS_USER_PATH2)); PORT_Strcpy(nssdir, userdir); PORT_Strcat(nssdir, NSS_USER_PATH1 NSS_USER_PATH2); if (testdir(nssdir)) { /* $HOME/.pki/nssdb exists */ return nssdir; } else { /* either $HOME/.pki or $HOME/.pki/nssdb does not exist */ PORT_Free(nssdir); } int size = 0; char *xdguserdatadir = PR_GetEnvSecure("XDG_DATA_HOME"); if (xdguserdatadir) { size = strlen(xdguserdatadir); } else { size = strlen(userdir) + sizeof(XDG_NSS_USER_PATH1) + sizeof(XDG_NSS_USER_PATH2); } size += sizeof(XDG_NSS_USER_PATH3) + sizeof(NSS_USER_PATH2); nssdir = PORT_Alloc(size); if (nssdir == NULL) { return NULL; } if (xdguserdatadir) { PORT_Strcpy(nssdir, xdguserdatadir); if (!testdir(nssdir)) { PORT_Free(nssdir); return NULL; } } else { PORT_Strcpy(nssdir, userdir); if (appendDirAndCreate(nssdir, XDG_NSS_USER_PATH1, 0755) || appendDirAndCreate(nssdir, XDG_NSS_USER_PATH2, 0755)) { PORT_Free(nssdir); return NULL; } } /* ${XDG_DATA_HOME:-$HOME/.local/share}/pki/nssdb */ if (appendDirAndCreate(nssdir, XDG_NSS_USER_PATH3, 0760) || appendDirAndCreate(nssdir, NSS_USER_PATH2, 0760)) { PORT_Free(nssdir); return NULL; } return nssdir; } #define NSS_DEFAULT_SYSTEM "/etc/pki/nssdb" static char * getSystemDB(void) { return PORT_Strdup(NSS_DEFAULT_SYSTEM); } static PRBool userIsRoot() { /* this works for linux and all unixes that we know off though it isn't stated as such in POSIX documentation */ return getuid() == 0; } static PRBool userCanModifySystemDB() { return (access(NSS_DEFAULT_SYSTEM, W_OK) == 0); } #ifndef NSS_FIPS_DISABLED static PRBool getFIPSEnv(void) { char *fipsEnv = PR_GetEnvSecure("NSS_FIPS"); if (!fipsEnv) { return PR_FALSE; } if ((strcasecmp(fipsEnv, "fips") == 0) || (strcasecmp(fipsEnv, "true") == 0) || (strcasecmp(fipsEnv, "on") == 0) || (strcasecmp(fipsEnv, "1") == 0)) { return PR_TRUE; } return PR_FALSE; } #endif /* NSS_FIPS_DISABLED */ static PRBool getFIPSMode(void) { #ifndef NSS_FIPS_DISABLED FILE *f; char d; size_t size; f = fopen("/proc/sys/crypto/fips_enabled", "r"); if (!f) { /* if we don't have a proc flag, fall back to the * environment variable */ return getFIPSEnv(); } size = fread(&d, 1, 1, f); fclose(f); if (size != 1) return PR_FALSE; if (d != '1') return PR_FALSE; return PR_TRUE; #else return PR_FALSE; #endif /* NSS_FIPS_DISABLED */ } #define NSS_DEFAULT_FLAGS "flags=readonly" /* configuration flags according to * https://developer.mozilla.org/en/PKCS11_Module_Specs * As stated there the slotParams start with a slot name which is a slotID * Slots 1 through 3 are reserved for the nss internal modules as follows: * 1 for crypto operations slot non-fips, * 2 for the key slot, and * 3 for the crypto operations slot fips */ #define CIPHER_ORDER_FLAGS "cipherOrder=100" #define SLOT_FLAGS \ "[slotFlags=RSA,RC4,RC2,DES,DH,SHA1,MD5,MD2,SSL,TLS,AES,RANDOM" \ " askpw=any timeout=30 ]" static const char *nssDefaultFlags = CIPHER_ORDER_FLAGS " slotParams={0x00000001=" SLOT_FLAGS " } "; static const char *nssDefaultFIPSFlags = CIPHER_ORDER_FLAGS " slotParams={0x00000003=" SLOT_FLAGS " } "; /* * This function builds the list of databases and modules to load, and sets * their configuration. For the sample we have a fixed set. * 1. We load the user's home nss database. * 2. We load the user's custom PKCS #11 modules. * 3. We load the system nss database readonly. * * Any space allocated in get_list must be freed in release_list. * This function can use whatever information is available to the application. * it is running in the process of the application for which it is making * decisions, so it's possible to acquire the application name as part of * the decision making process. * */ static char ** get_list(char *filename, char *stripped_parameters) { char **module_list = PORT_ZNewArray(char *, 5); char *userdb, *sysdb; int isFIPS = getFIPSMode(); const char *nssflags = isFIPS ? nssDefaultFIPSFlags : nssDefaultFlags; int next = 0; /* can't get any space */ if (module_list == NULL) { return NULL; } sysdb = getSystemDB(); userdb = getUserDB(); /* Don't open root's user DB */ if (userdb != NULL && !userIsRoot()) { /* return a list of databases to open. First the user Database */ module_list[next++] = PR_smprintf( "library= " "module=\"NSS User database\" " "parameters=\"configdir='sql:%s' %s tokenDescription='NSS user database'\" " "NSS=\"trustOrder=75 %sflags=internal%s\"", userdb, stripped_parameters, nssflags, isFIPS ? ",FIPS" : ""); /* now open the user's defined PKCS #11 modules */ /* skip the local user DB entry */ module_list[next++] = PR_smprintf( "library= " "module=\"NSS User database\" " "parameters=\"configdir='sql:%s' %s\" " "NSS=\"flags=internal,moduleDBOnly,defaultModDB,skipFirst\"", userdb, stripped_parameters); } /* now the system database (always read only unless it's root) */ if (sysdb) { const char *readonly = userCanModifySystemDB() ? "" : "flags=readonly"; module_list[next++] = PR_smprintf( "library= " "module=\"NSS system database\" " "parameters=\"configdir='sql:%s' tokenDescription='NSS system database' %s\" " "NSS=\"trustOrder=80 %sflags=internal,critical\"", sysdb, readonly, nssflags); } /* that was the last module */ module_list[next] = 0; PORT_Free(userdb); PORT_Free(sysdb); return module_list; } static char ** release_list(char **arg) { static char *success = "Success"; int next; for (next = 0; arg[next]; next++) { free(arg[next]); } PORT_Free(arg); return &success; } #include "utilpars.h" #define TARGET_SPEC_COPY(new, start, end) \ if (end > start) { \ int _cnt = end - start; \ PORT_Memcpy(new, start, _cnt); \ new += _cnt; \ } /* * According the strcpy man page: * * The strings may not overlap, and the destination string dest must be * large enough to receive the copy. * * This implementation allows target to overlap with src. * It does not allow the src to overlap the target. * example: overlapstrcpy(string, string+4) is fine * overlapstrcpy(string+4, string) is not. */ static void overlapstrcpy(char *target, char *src) { while (*src) { *target++ = *src++; } *target = 0; } /* determine what options the user was trying to open this database with */ /* filename is the directory pointed to by configdir= */ /* stripped is the rest of the parameters with configdir= stripped out */ static SECStatus parse_parameters(const char *parameters, char **filename, char **stripped) { const char *sourcePrev; const char *sourceCurr; char *targetCurr; char *newStripped; *filename = NULL; *stripped = NULL; newStripped = PORT_Alloc(PORT_Strlen(parameters) + 2); targetCurr = newStripped; sourcePrev = parameters; sourceCurr = NSSUTIL_ArgStrip(parameters); TARGET_SPEC_COPY(targetCurr, sourcePrev, sourceCurr); while (*sourceCurr) { int next; sourcePrev = sourceCurr; NSSUTIL_HANDLE_STRING_ARG(sourceCurr, *filename, "configdir=", sourcePrev = sourceCurr;) NSSUTIL_HANDLE_FINAL_ARG(sourceCurr); TARGET_SPEC_COPY(targetCurr, sourcePrev, sourceCurr); } *targetCurr = 0; if (*filename == NULL) { PORT_Free(newStripped); return SECFailure; } /* strip off any directives from the filename */ if (strncmp("sql:", *filename, 4) == 0) { overlapstrcpy(*filename, (*filename) + 4); } else if (strncmp("dbm:", *filename, 4) == 0) { overlapstrcpy(*filename, (*filename) + 4); } else if (strncmp("extern:", *filename, 7) == 0) { overlapstrcpy(*filename, (*filename) + 7); } *stripped = newStripped; return SECSuccess; } /* entry point */ char ** NSS_ReturnModuleSpecData(unsigned long function, char *parameters, void *args) { char *filename = NULL; char *stripped = NULL; char **retString = NULL; SECStatus rv; rv = parse_parameters(parameters, &filename, &stripped); if (rv != SECSuccess) { /* use defaults */ filename = getSystemDB(); if (!filename) { return NULL; } stripped = PORT_Strdup(NSS_DEFAULT_FLAGS); if (!stripped) { free(filename); return NULL; } } switch (function) { case SECMOD_MODULE_DB_FUNCTION_FIND: retString = get_list(filename, stripped); break; case SECMOD_MODULE_DB_FUNCTION_RELEASE: retString = release_list((char **)args); break; /* can't add or delete from this module DB */ case SECMOD_MODULE_DB_FUNCTION_ADD: case SECMOD_MODULE_DB_FUNCTION_DEL: retString = NULL; break; default: retString = NULL; break; } PORT_Free(filename); PORT_Free(stripped); return retString; }