| /* 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 <unistd.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| 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; |
| } |