diff --git a/automation/abi-check/expected-report-libnssutil3.so.txt b/automation/abi-check/expected-report-libnssutil3.so.txt index e69de29bb2..06d2389db4 100644 --- a/automation/abi-check/expected-report-libnssutil3.so.txt +++ b/automation/abi-check/expected-report-libnssutil3.so.txt @@ -0,0 +1,5 @@ + +1 Added function: + + 'function char* NSSUTIL_AddNSSFlagToModuleSpec(char*, char*)' {NSSUTIL_AddNSSFlagToModuleSpec@@NSSUTIL_3.39} + diff --git a/automation/taskcluster/graph/src/extend.js b/automation/taskcluster/graph/src/extend.js index 5305325c52..37e716d9b3 100644 --- a/automation/taskcluster/graph/src/extend.js +++ b/automation/taskcluster/graph/src/extend.js @@ -928,6 +928,9 @@ function scheduleTests(task_build, task_cert, test_base) { queue.scheduleTask(merge(no_cert_base, { name: "SDR tests", symbol: "SDR", tests: "sdr" })); + queue.scheduleTask(merge(no_cert_base, { + name: "Policy tests", symbol: "Policy", tests: "policy" + })); // Schedule tests that need certificates. let cert_base = merge(test_base, {parent: task_cert}); diff --git a/automation/taskcluster/graph/src/try_syntax.js b/automation/taskcluster/graph/src/try_syntax.js index 214793bd5d..92cf9bb00b 100644 --- a/automation/taskcluster/graph/src/try_syntax.js +++ b/automation/taskcluster/graph/src/try_syntax.js @@ -37,7 +37,7 @@ function parseOptions(opts) { let aliases = {"gtests": "gtest"}; let allUnitTests = ["bogo", "crmf", "chains", "cipher", "db", "ec", "fips", "gtest", "interop", "lowhash", "merge", "sdr", "smime", "tools", - "ssl", "mpi", "scert", "spki"]; + "ssl", "mpi", "scert", "spki", "policy"]; let unittests = intersect(opts.unittests.split(/\s*,\s*/).map(t => { return aliases[t] || t; }), allUnitTests); diff --git a/cmd/manifest.mn b/cmd/manifest.mn index 567c6bb9d9..be53d3c4eb 100644 --- a/cmd/manifest.mn +++ b/cmd/manifest.mn @@ -47,6 +47,7 @@ NSS_SRCDIRS = \ listsuites \ makepqg \ multinit \ + nss-policy-check \ ocspclnt \ ocspresp \ oidcalc \ diff --git a/cmd/nss-policy-check/Makefile b/cmd/nss-policy-check/Makefile new file mode 100644 index 0000000000..6e1d4ecdfc --- /dev/null +++ b/cmd/nss-policy-check/Makefile @@ -0,0 +1,47 @@ +#! gmake +# +# 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/. + +####################################################################### +# (1) Include initial platform-independent assignments (MANDATORY). # +####################################################################### + +include manifest.mn + +####################################################################### +# (2) Include "global" configuration information. (OPTIONAL) # +####################################################################### + +include $(CORE_DEPTH)/coreconf/config.mk + +####################################################################### +# (3) Include "component" configuration information. (OPTIONAL) # +####################################################################### + +####################################################################### +# (4) Include "local" platform-dependent assignments (OPTIONAL). # +####################################################################### + +include ../platlibs.mk + +####################################################################### +# (5) Execute "global" rules. (OPTIONAL) # +####################################################################### + +include $(CORE_DEPTH)/coreconf/rules.mk + +####################################################################### +# (6) Execute "component" rules. (OPTIONAL) # +####################################################################### + + + +####################################################################### +# (7) Execute "local" rules. (OPTIONAL). # +####################################################################### + + +include ../platrules.mk + diff --git a/cmd/nss-policy-check/manifest.mn b/cmd/nss-policy-check/manifest.mn new file mode 100644 index 0000000000..8fb9abf000 --- /dev/null +++ b/cmd/nss-policy-check/manifest.mn @@ -0,0 +1,15 @@ +# +# 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/. + +CORE_DEPTH = ../.. + +MODULE = nss + +CSRCS = nss-policy-check.c + +REQUIRES = seccmd + +PROGRAM = nss-policy-check + diff --git a/cmd/nss-policy-check/nss-policy-check.c b/cmd/nss-policy-check/nss-policy-check.c new file mode 100644 index 0000000000..6b4e2bdaf2 --- /dev/null +++ b/cmd/nss-policy-check/nss-policy-check.c @@ -0,0 +1,205 @@ +/* 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/. */ + +/* This program can be used to check the validity of a NSS crypto policy + * configuration file, specified using a config= line. + * + * Exit codes: + * failure: 2 + * warning: 1 + * success: 0 + */ + +#include +#include +#include +#include "utilparst.h" +#include "nss.h" +#include "secport.h" +#include "secutil.h" +#include "secmod.h" +#include "ssl.h" +#include "prenv.h" + +const char *sWarn = "WARN"; +const char *sInfo = "INFO"; + +void +get_tls_info(SSLProtocolVariant protocolVariant, const char *display) +{ + SSLVersionRange vrange_supported, vrange_enabled; + unsigned num_enabled = 0; + PRBool failed = PR_FALSE; + + /* We assume SSL v2 is inactive, and therefore SSL_VersionRangeGetDefault + * gives complete information. */ + if ((SSL_VersionRangeGetSupported(protocolVariant, &vrange_supported) != SECSuccess) || + (SSL_VersionRangeGetDefault(protocolVariant, &vrange_enabled) != SECSuccess) || + !vrange_enabled.min || + !vrange_enabled.max || + vrange_enabled.max < vrange_supported.min || + vrange_enabled.min > vrange_supported.max) { + failed = PR_TRUE; + } else { + if (vrange_enabled.min < vrange_supported.min) { + vrange_enabled.min = vrange_supported.min; + } + if (vrange_enabled.max > vrange_supported.max) { + vrange_enabled.max = vrange_supported.max; + } + if (vrange_enabled.min > vrange_enabled.max) { + failed = PR_TRUE; + } + } + if (failed) { + num_enabled = 0; + } else { + num_enabled = vrange_enabled.max - vrange_enabled.min + 1; + } + fprintf(stderr, "NSS-POLICY-%s: NUMBER-OF-%s-VERSIONS: %u\n", + num_enabled ? sInfo : sWarn, display, num_enabled); + if (!num_enabled) { + PR_SetEnv("NSS_POLICY_WARN=1"); + } +} + +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif + +int +main(int argc, char **argv) +{ + const PRUint16 *cipherSuites = SSL_ImplementedCiphers; + int i; + SECStatus rv; + SECMODModule *module = NULL; + char path[PATH_MAX]; + const char *filename; + char moduleSpec[1024 + PATH_MAX]; + unsigned num_enabled = 0; + int result = 0; + int fullPathLen; + + if (argc != 2) { + fprintf(stderr, "Syntax: nss-policy-check \n"); + result = 2; + goto loser; + } + + fullPathLen = strlen(argv[1]); + + if (!fullPathLen || PR_Access(argv[1], PR_ACCESS_READ_OK) != PR_SUCCESS) { + fprintf(stderr, "Error: cannot read file %s\n", argv[1]); + result = 2; + goto loser; + } + + if (fullPathLen >= PATH_MAX) { + fprintf(stderr, "Error: filename parameter is too long\n"); + result = 2; + goto loser; + } + + path[0] = 0; + filename = argv[1] + fullPathLen - 1; + while ((filename > argv[1]) && (*filename != NSSUTIL_PATH_SEPARATOR[0])) { + filename--; + } + + if (filename == argv[1]) { + PORT_Strcpy(path, "."); + } else { + filename++; /* Go past the path separator. */ + PORT_Strncat(path, argv[1], (filename - argv[1])); + } + + PR_SetEnv("NSS_IGNORE_SYSTEM_POLICY=1"); + rv = NSS_NoDB_Init(NULL); + if (rv != SECSuccess) { + fprintf(stderr, "NSS_Init failed: %s\n", PORT_ErrorToString(PR_GetError())); + result = 2; + goto loser; + } + + PR_SetEnv("NSS_POLICY_LOADED=0"); + PR_SetEnv("NSS_POLICY_FAIL=0"); + PR_SetEnv("NSS_POLICY_WARN=0"); + + sprintf(moduleSpec, + "name=\"Policy File\" " + "parameters=\"configdir='sql:%s' " + "secmod='%s' " + "flags=readOnly,noCertDB,forceSecmodChoice,forceOpen\" " + "NSS=\"flags=internal,moduleDB,skipFirst,moduleDBOnly,critical,printPolicyFeedback\"", + path, filename); + + module = SECMOD_LoadModule(moduleSpec, NULL, PR_TRUE); + if (!module || !module->loaded || atoi(PR_GetEnvSecure("NSS_POLICY_LOADED")) != 1) { + fprintf(stderr, "Error: failed to load policy file\n"); + result = 2; + goto loser; + } + + rv = SSL_OptionSetDefault(SSL_SECURITY, PR_TRUE); + if (rv != SECSuccess) { + fprintf(stderr, "enable SSL_SECURITY failed: %s\n", PORT_ErrorToString(PR_GetError())); + result = 2; + goto loser; + } + + for (i = 0; i < SSL_NumImplementedCiphers; i++) { + PRUint16 suite = cipherSuites[i]; + PRBool enabled; + SSLCipherSuiteInfo info; + + rv = SSL_CipherPrefGetDefault(suite, &enabled); + if (rv != SECSuccess) { + fprintf(stderr, + "SSL_CipherPrefGetDefault didn't like value 0x%04x (i = %d): %s\n", + suite, i, PORT_ErrorToString(PR_GetError())); + continue; + } + rv = SSL_GetCipherSuiteInfo(suite, &info, (int)(sizeof info)); + if (rv != SECSuccess) { + fprintf(stderr, + "SSL_GetCipherSuiteInfo didn't like value 0x%04x (i = %d): %s\n", + suite, i, PORT_ErrorToString(PR_GetError())); + continue; + } + if (enabled) { + ++num_enabled; + fprintf(stderr, "NSS-POLICY-INFO: ciphersuite %s is enabled\n", info.cipherSuiteName); + } + } + fprintf(stderr, "NSS-POLICY-%s: NUMBER-OF-CIPHERSUITES: %u\n", num_enabled ? sInfo : sWarn, num_enabled); + if (!num_enabled) { + PR_SetEnv("NSS_POLICY_WARN=1"); + } + + get_tls_info(ssl_variant_stream, "TLS"); + get_tls_info(ssl_variant_datagram, "DTLS"); + + if (atoi(PR_GetEnvSecure("NSS_POLICY_FAIL")) != 0) { + result = 2; + } else if (atoi(PR_GetEnvSecure("NSS_POLICY_WARN")) != 0) { + result = 1; + } + +loser: + if (module) { + SECMOD_DestroyModule(module); + } + rv = NSS_Shutdown(); + if (rv != SECSuccess) { + fprintf(stderr, "NSS_Shutdown failed: %s\n", PORT_ErrorToString(PR_GetError())); + result = 2; + } + if (result == 2) { + fprintf(stderr, "NSS-POLICY-FAIL\n"); + } else if (result == 1) { + fprintf(stderr, "NSS-POLICY-WARN\n"); + } + return result; +} diff --git a/cmd/nss-policy-check/nss-policy-check.gyp b/cmd/nss-policy-check/nss-policy-check.gyp new file mode 100644 index 0000000000..877a5bc06e --- /dev/null +++ b/cmd/nss-policy-check/nss-policy-check.gyp @@ -0,0 +1,24 @@ +# 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/. +{ + 'includes': [ + '../../coreconf/config.gypi', + '../../cmd/platlibs.gypi' + ], + 'targets': [ + { + 'target_name': 'nss-policy-check', + 'type': 'executable', + 'sources': [ + 'nss-policy-check.c' + ], + 'dependencies': [ + '<(DEPTH)/exports.gyp:nss_exports' + ] + } + ], + 'variables': { + 'module': 'nss' + } +} \ No newline at end of file diff --git a/lib/nss/nssinit.c b/lib/nss/nssinit.c index dd7806dec2..989572948c 100644 --- a/lib/nss/nssinit.c +++ b/lib/nss/nssinit.c @@ -54,7 +54,7 @@ nss_mktemp(char *path) #define NSS_MAX_FLAG_SIZE sizeof("readOnly") + sizeof("noCertDB") + \ sizeof("noModDB") + sizeof("forceOpen") + sizeof("passwordRequired") + \ - sizeof("optimizeSpace") + sizeof("optimizeSpace") + sizeof("printPolicyFeedback") #define NSS_DEFAULT_MOD_NAME "NSS Internal Module" static char * diff --git a/lib/pk11wrap/pk11pars.c b/lib/pk11wrap/pk11pars.c index 899a0809c3..db60f7c9dd 100644 --- a/lib/pk11wrap/pk11pars.c +++ b/lib/pk11wrap/pk11pars.c @@ -194,7 +194,7 @@ typedef struct { * This table should be merged with the SECOID table. */ #define CIPHER_NAME(x) x, (sizeof(x) - 1) -static const oidValDef algOptList[] = { +static const oidValDef curveOptList[] = { /* Curves */ { CIPHER_NAME("PRIME192V1"), SEC_OID_ANSIX962_EC_PRIME192V1, NSS_USE_ALG_IN_SSL_KX | NSS_USE_ALG_IN_CERT_SIGNATURE }, @@ -316,7 +316,9 @@ static const oidValDef algOptList[] = { NSS_USE_ALG_IN_SSL_KX | NSS_USE_ALG_IN_CERT_SIGNATURE }, { CIPHER_NAME("SECT571R1"), SEC_OID_SECG_EC_SECT571R1, NSS_USE_ALG_IN_SSL_KX | NSS_USE_ALG_IN_CERT_SIGNATURE }, +}; +static const oidValDef hashOptList[] = { /* Hashes */ { CIPHER_NAME("MD2"), SEC_OID_MD2, NSS_USE_ALG_IN_SSL_KX | NSS_USE_ALG_IN_CERT_SIGNATURE }, @@ -334,7 +336,9 @@ static const oidValDef algOptList[] = { NSS_USE_ALG_IN_SSL_KX | NSS_USE_ALG_IN_CERT_SIGNATURE }, { CIPHER_NAME("SHA512"), SEC_OID_SHA512, NSS_USE_ALG_IN_SSL_KX | NSS_USE_ALG_IN_CERT_SIGNATURE }, +}; +static const oidValDef macOptList[] = { /* MACs */ { CIPHER_NAME("HMAC-SHA1"), SEC_OID_HMAC_SHA1, NSS_USE_ALG_IN_SSL }, { CIPHER_NAME("HMAC-SHA224"), SEC_OID_HMAC_SHA224, NSS_USE_ALG_IN_SSL }, @@ -342,7 +346,9 @@ static const oidValDef algOptList[] = { { CIPHER_NAME("HMAC-SHA384"), SEC_OID_HMAC_SHA384, NSS_USE_ALG_IN_SSL }, { CIPHER_NAME("HMAC-SHA512"), SEC_OID_HMAC_SHA512, NSS_USE_ALG_IN_SSL }, { CIPHER_NAME("HMAC-MD5"), SEC_OID_HMAC_MD5, NSS_USE_ALG_IN_SSL }, +}; +static const oidValDef cipherOptList[] = { /* Ciphers */ { CIPHER_NAME("AES128-CBC"), SEC_OID_AES_128_CBC, NSS_USE_ALG_IN_SSL }, { CIPHER_NAME("AES192-CBC"), SEC_OID_AES_192_CBC, NSS_USE_ALG_IN_SSL }, @@ -362,7 +368,9 @@ static const oidValDef algOptList[] = { { CIPHER_NAME("RC2"), SEC_OID_RC2_CBC, NSS_USE_ALG_IN_SSL }, { CIPHER_NAME("RC4"), SEC_OID_RC4, NSS_USE_ALG_IN_SSL }, { CIPHER_NAME("IDEA"), SEC_OID_IDEA_CBC, NSS_USE_ALG_IN_SSL }, +}; +static const oidValDef kxOptList[] = { /* Key exchange */ { CIPHER_NAME("RSA"), SEC_OID_TLS_RSA, NSS_USE_ALG_IN_SSL_KX }, { CIPHER_NAME("RSA-EXPORT"), SEC_OID_TLS_RSA_EXPORT, NSS_USE_ALG_IN_SSL_KX }, @@ -376,6 +384,20 @@ static const oidValDef algOptList[] = { { CIPHER_NAME("ECDH-RSA"), SEC_OID_TLS_ECDH_RSA, NSS_USE_ALG_IN_SSL_KX }, }; +typedef struct { + const oidValDef *list; + PRUint32 entries; + const char *description; +} algListsDef; + +static const algListsDef algOptLists[] = { + { curveOptList, PR_ARRAY_SIZE(curveOptList), "ECC" }, + { hashOptList, PR_ARRAY_SIZE(hashOptList), "HASH" }, + { macOptList, PR_ARRAY_SIZE(macOptList), "MAC" }, + { cipherOptList, PR_ARRAY_SIZE(cipherOptList), "CIPHER" }, + { kxOptList, PR_ARRAY_SIZE(kxOptList), "OTHER-KX" }, +}; + static const optionFreeDef sslOptList[] = { /* Versions */ { CIPHER_NAME("SSL2.0"), 0x002 }, @@ -447,7 +469,8 @@ secmod_ArgGetSubValue(const char *cipher, char sep1, char sep2, } static PRUint32 -secmod_parsePolicyValue(const char *policyFlags, int policyLength) +secmod_parsePolicyValue(const char *policyFlags, int policyLength, + PRBool printPolicyFeedback) { const char *flag, *currentString; PRUint32 flags = 0; @@ -456,6 +479,7 @@ secmod_parsePolicyValue(const char *policyFlags, int policyLength) for (currentString = policyFlags; currentString && currentString < policyFlags + policyLength;) { int length; + PRBool unknown = PR_TRUE; flag = secmod_ArgGetSubValue(currentString, ',', ':', &length, ¤tString); if (length == 0) { @@ -467,41 +491,49 @@ secmod_parsePolicyValue(const char *policyFlags, int policyLength) if ((policy->name_size == length) && PORT_Strncasecmp(policy->name, flag, name_size) == 0) { flags |= policy->flag; + unknown = PR_FALSE; break; } } + if (unknown && printPolicyFeedback) { + PR_SetEnv("NSS_POLICY_FAIL=1"); + fprintf(stderr, "NSS-POLICY-FAIL %.*s: unknown value: %.*s\n", + policyLength, policyFlags, length, flag); + } } return flags; } /* allow symbolic names for values. The only ones currently defines or * SSL protocol versions. */ -static PRInt32 -secmod_getPolicyOptValue(const char *policyValue, int policyValueLength) +static SECStatus +secmod_getPolicyOptValue(const char *policyValue, int policyValueLength, + PRInt32 *result) { PRInt32 val = atoi(policyValue); int i; if ((val != 0) || (*policyValue == '0')) { - return val; + *result = val; + return SECSuccess; } for (i = 0; i < PR_ARRAY_SIZE(sslOptList); i++) { if (policyValueLength == sslOptList[i].name_size && PORT_Strncasecmp(sslOptList[i].name, policyValue, sslOptList[i].name_size) == 0) { - val = sslOptList[i].option; - break; + *result = sslOptList[i].option; + return SECSuccess; } } - return val; + return SECFailure; } static SECStatus -secmod_applyCryptoPolicy(const char *policyString, - PRBool allow) +secmod_applyCryptoPolicy(const char *policyString, PRBool allow, + PRBool printPolicyFeedback) { const char *cipher, *currentString; - unsigned i; + unsigned i, j; SECStatus rv = SECSuccess; PRBool unknown; @@ -526,56 +558,63 @@ secmod_applyCryptoPolicy(const char *policyString, /* disable or enable all options by default */ PRUint32 value = 0; if (newValue) { - value = secmod_parsePolicyValue(&cipher[3] + 1, length - 3 - 1); + value = secmod_parsePolicyValue(&cipher[3] + 1, length - 3 - 1, printPolicyFeedback); } - for (i = 0; i < PR_ARRAY_SIZE(algOptList); i++) { - PRUint32 enable, disable; - if (!newValue) { - value = algOptList[i].val; + for (i = 0; i < PR_ARRAY_SIZE(algOptLists); i++) { + const algListsDef *algOptList = &algOptLists[i]; + for (j = 0; j < algOptList->entries; j++) { + PRUint32 enable, disable; + if (!newValue) { + value = algOptList->list[j].val; + } + if (allow) { + enable = value; + disable = 0; + } else { + enable = 0; + disable = value; + } + NSS_SetAlgorithmPolicy(algOptList->list[j].oid, enable, disable); } - if (allow) { - enable = value; - disable = 0; - } else { - enable = 0; - disable = value; - } - NSS_SetAlgorithmPolicy(algOptList[i].oid, enable, disable); } continue; } - for (i = 0; i < PR_ARRAY_SIZE(algOptList); i++) { - const oidValDef *algOpt = &algOptList[i]; - unsigned name_size = algOpt->name_size; - PRBool newOption = PR_FALSE; + for (i = 0; i < PR_ARRAY_SIZE(algOptLists); i++) { + const algListsDef *algOptList = &algOptLists[i]; + for (j = 0; j < algOptList->entries; j++) { + const oidValDef *algOpt = &algOptList->list[j]; + unsigned name_size = algOpt->name_size; + PRBool newOption = PR_FALSE; - if ((length >= name_size) && (cipher[name_size] == '/')) { - newOption = PR_TRUE; - } - if ((newOption || algOpt->name_size == length) && - PORT_Strncasecmp(algOpt->name, cipher, name_size) == 0) { - PRUint32 value = algOpt->val; - PRUint32 enable, disable; - if (newOption) { - value = secmod_parsePolicyValue(&cipher[name_size] + 1, - length - name_size - 1); + if ((length >= name_size) && (cipher[name_size] == '/')) { + newOption = PR_TRUE; } - if (allow) { - enable = value; - disable = 0; - } else { - enable = 0; - disable = value; - } - rv = NSS_SetAlgorithmPolicy(algOpt->oid, enable, disable); - if (rv != SECSuccess) { - /* could not enable option */ - /* NSS_SetAlgorithPolicy should have set the error code */ - return SECFailure; + if ((newOption || algOpt->name_size == length) && + PORT_Strncasecmp(algOpt->name, cipher, name_size) == 0) { + PRUint32 value = algOpt->val; + PRUint32 enable, disable; + if (newOption) { + value = secmod_parsePolicyValue(&cipher[name_size] + 1, + length - name_size - 1, + printPolicyFeedback); + } + if (allow) { + enable = value; + disable = 0; + } else { + enable = 0; + disable = value; + } + rv = NSS_SetAlgorithmPolicy(algOpt->oid, enable, disable); + if (rv != SECSuccess) { + /* could not enable option */ + /* NSS_SetAlgorithPolicy should have set the error code */ + return SECFailure; + } + unknown = PR_FALSE; + break; } - unknown = PR_FALSE; - break; } } if (!unknown) { @@ -588,9 +627,19 @@ secmod_applyCryptoPolicy(const char *policyString, if ((length > name_size) && cipher[name_size] == '=' && PORT_Strncasecmp(freeOpt->name, cipher, name_size) == 0) { - PRInt32 val = secmod_getPolicyOptValue(&cipher[name_size + 1], - length - name_size - 1); - + PRInt32 val; + const char *policyValue = &cipher[name_size + 1]; + int policyValueLength = length - name_size - 1; + rv = secmod_getPolicyOptValue(policyValue, policyValueLength, + &val); + if (rv != SECSuccess) { + if (printPolicyFeedback) { + PR_SetEnv("NSS_POLICY_FAIL=1"); + fprintf(stderr, "NSS-POLICY-FAIL %.*s: unknown value: %.*s\n", + length, cipher, policyValueLength, policyValue); + } + return SECFailure; + } rv = NSS_OptionSet(freeOpt->option, val); if (rv != SECSuccess) { /* could not enable option */ @@ -603,12 +652,83 @@ secmod_applyCryptoPolicy(const char *policyString, break; } } + + if (unknown && printPolicyFeedback) { + PR_SetEnv("NSS_POLICY_FAIL=1"); + fprintf(stderr, "NSS-POLICY-FAIL %s: unknown identifier: %.*s\n", + allow ? "allow" : "disallow", length, cipher); + } } return rv; } +static void +secmod_sanityCheckCryptoPolicy(void) +{ + unsigned i, j; + SECStatus rv = SECSuccess; + unsigned num_kx_enabled = 0; + unsigned num_ssl_enabled = 0; + unsigned num_sig_enabled = 0; + unsigned enabledCount[PR_ARRAY_SIZE(algOptLists)]; + const char *sWarn = "WARN"; + const char *sInfo = "INFO"; + PRBool haveWarning = PR_FALSE; + + for (i = 0; i < PR_ARRAY_SIZE(algOptLists); i++) { + const algListsDef *algOptList = &algOptLists[i]; + enabledCount[i] = 0; + for (j = 0; j < algOptList->entries; j++) { + const oidValDef *algOpt = &algOptList->list[j]; + PRUint32 value; + PRBool anyEnabled = PR_FALSE; + rv = NSS_GetAlgorithmPolicy(algOpt->oid, &value); + if (rv != SECSuccess) { + PR_SetEnv("NSS_POLICY_FAIL=1"); + fprintf(stderr, "NSS-POLICY-FAIL: internal failure with NSS_GetAlgorithmPolicy at %u\n", i); + return; + } + + if ((algOpt->val & NSS_USE_ALG_IN_SSL_KX) && (value & NSS_USE_ALG_IN_SSL_KX)) { + ++num_kx_enabled; + anyEnabled = PR_TRUE; + fprintf(stderr, "NSS-POLICY-INFO: %s is enabled for KX\n", algOpt->name); + } + if ((algOpt->val & NSS_USE_ALG_IN_SSL) && (value & NSS_USE_ALG_IN_SSL)) { + ++num_ssl_enabled; + anyEnabled = PR_TRUE; + fprintf(stderr, "NSS-POLICY-INFO: %s is enabled for SSL\n", algOpt->name); + } + if ((algOpt->val & NSS_USE_ALG_IN_CERT_SIGNATURE) && (value & NSS_USE_ALG_IN_CERT_SIGNATURE)) { + ++num_sig_enabled; + anyEnabled = PR_TRUE; + fprintf(stderr, "NSS-POLICY-INFO: %s is enabled for CERT-SIGNATURE\n", algOpt->name); + } + if (anyEnabled) { + ++enabledCount[i]; + } + } + } + fprintf(stderr, "NSS-POLICY-%s: NUMBER-OF-SSL-ALG-KX: %u\n", num_kx_enabled ? sInfo : sWarn, num_kx_enabled); + fprintf(stderr, "NSS-POLICY-%s: NUMBER-OF-SSL-ALG: %u\n", num_ssl_enabled ? sInfo : sWarn, num_ssl_enabled); + fprintf(stderr, "NSS-POLICY-%s: NUMBER-OF-CERT-SIG: %u\n", num_sig_enabled ? sInfo : sWarn, num_sig_enabled); + if (!num_kx_enabled || !num_ssl_enabled || !num_sig_enabled) { + haveWarning = PR_TRUE; + } + for (i = 0; i < PR_ARRAY_SIZE(algOptLists); i++) { + const algListsDef *algOptList = &algOptLists[i]; + fprintf(stderr, "NSS-POLICY-%s: NUMBER-OF-%s: %u\n", enabledCount[i] ? sInfo : sWarn, algOptList->description, enabledCount[i]); + if (!enabledCount[i]) { + haveWarning = PR_TRUE; + } + } + if (haveWarning) { + PR_SetEnv("NSS_POLICY_WARN=1"); + } +} + static SECStatus -secmod_parseCryptoPolicy(const char *policyConfig) +secmod_parseCryptoPolicy(const char *policyConfig, PRBool printPolicyFeedback) { char *disallow, *allow; SECStatus rv; @@ -623,16 +743,26 @@ secmod_parseCryptoPolicy(const char *policyConfig) return rv; } disallow = NSSUTIL_ArgGetParamValue("disallow", policyConfig); - rv = secmod_applyCryptoPolicy(disallow, PR_FALSE); + rv = secmod_applyCryptoPolicy(disallow, PR_FALSE, printPolicyFeedback); if (disallow) PORT_Free(disallow); if (rv != SECSuccess) { return rv; } allow = NSSUTIL_ArgGetParamValue("allow", policyConfig); - rv = secmod_applyCryptoPolicy(allow, PR_TRUE); + rv = secmod_applyCryptoPolicy(allow, PR_TRUE, printPolicyFeedback); if (allow) PORT_Free(allow); + if (rv != SECSuccess) { + return rv; + } + if (printPolicyFeedback) { + /* This helps to distinguish configurations that don't contain any + * policy config= statement. */ + PR_SetEnv("NSS_POLICY_LOADED=1"); + fprintf(stderr, "NSS-POLICY-INFO: LOADED-SUCCESSFULLY\n"); + secmod_sanityCheckCryptoPolicy(); + } return rv; } @@ -649,11 +779,16 @@ SECMOD_CreateModuleEx(const char *library, const char *moduleName, char *slotParams, *ciphers; /* pk11pars.h still does not have const char * interfaces */ char *nssc = (char *)nss; + PRBool printPolicyFeedback = NSSUTIL_ArgHasFlag("flags", "printPolicyFeedback", nssc); - rv = secmod_parseCryptoPolicy(config); + rv = secmod_parseCryptoPolicy(config, printPolicyFeedback); /* do not load the module if policy parsing fails */ if (rv != SECSuccess) { + if (printPolicyFeedback) { + PR_SetEnv("NSS_POLICY_FAIL=1"); + fprintf(stderr, "NSS-POLICY-FAIL: policy config parsing failed, not loading module %s\n", moduleName); + } return NULL; } @@ -1647,6 +1782,7 @@ SECMOD_LoadModule(char *modulespec, SECMODModule *parent, PRBool recurse) SECMODModule *module = NULL; SECMODModule *oldModule = NULL; SECStatus rv; + PRBool forwardPolicyFeedback = PR_FALSE; /* initialize the underlying module structures */ SECMOD_Init(); @@ -1659,6 +1795,7 @@ SECMOD_LoadModule(char *modulespec, SECMODModule *parent, PRBool recurse) } module = SECMOD_CreateModuleEx(library, moduleName, parameters, nss, config); + forwardPolicyFeedback = NSSUTIL_ArgHasFlag("flags", "printPolicyFeedback", nss); if (library) PORT_Free(library); if (moduleName) @@ -1721,7 +1858,15 @@ SECMOD_LoadModule(char *modulespec, SECMODModule *parent, PRBool recurse) rv = SECFailure; break; } - child = SECMOD_LoadModule(*index, module, PR_TRUE); + if (!forwardPolicyFeedback) { + child = SECMOD_LoadModule(*index, module, PR_TRUE); + } else { + /* Add printPolicyFeedback to the nss flags */ + char *specWithForwards = + NSSUTIL_AddNSSFlagToModuleSpec(*index, "printPolicyFeedback"); + child = SECMOD_LoadModule(specWithForwards, module, PR_TRUE); + PORT_Free(specWithForwards); + } if (!child) break; if (child->isCritical && !child->loaded) { diff --git a/lib/util/nssutil.def b/lib/util/nssutil.def index 26e438ba6a..8c233f7d36 100644 --- a/lib/util/nssutil.def +++ b/lib/util/nssutil.def @@ -328,3 +328,9 @@ SECITEM_MakeItem; ;+ local: ;+ *; ;+}; +;+NSSUTIL_3.39 { # NSS Utilities 3.39 release +;+ global: +NSSUTIL_AddNSSFlagToModuleSpec; +;+ local: +;+ *; +;+}; diff --git a/lib/util/utilpars.c b/lib/util/utilpars.c index e7435bfcc3..f9b807f7ed 100644 --- a/lib/util/utilpars.c +++ b/lib/util/utilpars.c @@ -913,6 +913,92 @@ NSSUTIL_MkModuleSpec(char *dllName, char *commonName, char *parameters, return NSSUTIL_MkModuleSpecEx(dllName, commonName, parameters, NSS, NULL); } +/************************************************************************ + * add a single flag to the Flags= section inside the spec's NSS= section */ +char * +NSSUTIL_AddNSSFlagToModuleSpec(char *spec, char *addFlag) +{ + const char *prefix = "flags="; + const size_t prefixLen = strlen(prefix); + char *lib = NULL, *name = NULL, *param = NULL, *nss = NULL, *conf = NULL; + char *nss2 = NULL, *result = NULL; + SECStatus rv; + + rv = NSSUTIL_ArgParseModuleSpecEx(spec, &lib, &name, ¶m, &nss, &conf); + if (rv != SECSuccess) { + return NULL; + } + + if (nss && NSSUTIL_ArgHasFlag("flags", addFlag, nss)) { + /* It's already there, nothing to do! */ + PORT_Free(lib); + PORT_Free(name); + PORT_Free(param); + PORT_Free(nss); + PORT_Free(conf); + return PORT_Strdup(spec); + } + + if (!nss || !strlen(nss)) { + nss2 = PORT_Alloc(prefixLen + strlen(addFlag) + 1); + PORT_Strcpy(nss2, prefix); + PORT_Strcat(nss2, addFlag); + } else { + const char *iNss = nss; + PRBool alreadyAdded = PR_FALSE; + size_t maxSize = strlen(nss) + strlen(addFlag) + prefixLen + 2; /* space and null terminator */ + nss2 = PORT_Alloc(maxSize); + *nss2 = 0; + while (*iNss) { + iNss = NSSUTIL_ArgStrip(iNss); + if (PORT_Strncasecmp(iNss, prefix, prefixLen) == 0) { + /* We found an existing Flags= section. */ + char *oldFlags; + const char *valPtr; + int valSize; + valPtr = iNss + prefixLen; + oldFlags = NSSUTIL_ArgFetchValue(valPtr, &valSize); + iNss = valPtr + valSize; + PORT_Strcat(nss2, prefix); + PORT_Strcat(nss2, oldFlags); + PORT_Strcat(nss2, ","); + PORT_Strcat(nss2, addFlag); + PORT_Strcat(nss2, " "); + PORT_Free(oldFlags); + alreadyAdded = PR_TRUE; + iNss = NSSUTIL_ArgStrip(iNss); + PORT_Strcat(nss2, iNss); /* remainder of input */ + break; + } else { + /* Append this other name=value pair and continue. */ + const char *startOfNext = NSSUTIL_ArgSkipParameter(iNss); + PORT_Strncat(nss2, iNss, (startOfNext - iNss)); + if (nss2[strlen(nss2) - 1] != ' ') { + PORT_Strcat(nss2, " "); + } + iNss = startOfNext; + } + iNss = NSSUTIL_ArgStrip(iNss); + } + if (!alreadyAdded) { + /* nss wasn't empty, and it didn't contain a Flags section. We can + * assume that other content from nss has already been added to + * nss2, which means we already have a trailing space separator. */ + PORT_Strcat(nss2, prefix); + PORT_Strcat(nss2, addFlag); + } + } + + result = NSSUTIL_MkModuleSpecEx(lib, name, param, nss2, conf); + PORT_Free(lib); + PORT_Free(name); + PORT_Free(param); + PORT_Free(nss); + PORT_Free(nss2); + PORT_Free(conf); + return result; +} + #define NSSUTIL_ARG_FORTEZZA_FLAG "FORTEZZA" /****************************************************************************** * Parse the cipher flags from the NSS parameter diff --git a/lib/util/utilpars.h b/lib/util/utilpars.h index 1b0b1ff1ce..289fdca975 100644 --- a/lib/util/utilpars.h +++ b/lib/util/utilpars.h @@ -46,6 +46,7 @@ char *NSSUTIL_MkModuleSpec(char *dllName, char *commonName, char *parameters, char *NSS); char *NSSUTIL_MkModuleSpecEx(char *dllName, char *commonName, char *parameters, char *NSS, char *config); +char *NSSUTIL_AddNSSFlagToModuleSpec(char *spec, char *addFlag); void NSSUTIL_ArgParseCipherFlags(unsigned long *newCiphers, const char *cipherList); char *NSSUTIL_MkNSSString(char **slotStrings, int slotCount, PRBool internal, diff --git a/mach b/mach index f9fbccd234..178cfeb741 100755 --- a/mach +++ b/mach @@ -247,7 +247,7 @@ def parse_arguments(): tests = [ "cipher", "lowhash", "chains", "cert", "dbtests", "tools", "fips", "sdr", "crmf", "smime", "ssl", "ocsp", "merge", "pkits", "ec", - "gtests", "ssl_gtests", "bogo", "interop" + "gtests", "ssl_gtests", "bogo", "interop", "policy" ] parser_test.add_argument( 'test', choices=tests, help="Available tests", action=testAction) diff --git a/nss.gyp b/nss.gyp index 36b0dd9744..3ec33fd5c0 100644 --- a/nss.gyp +++ b/nss.gyp @@ -135,6 +135,7 @@ 'cmd/listsuites/listsuites.gyp:listsuites', 'cmd/makepqg/makepqg.gyp:makepqg', 'cmd/multinit/multinit.gyp:multinit', + 'cmd/nss-policy-check/nss-policy-check.gyp:nss-policy-check', 'cmd/ocspclnt/ocspclnt.gyp:ocspclnt', 'cmd/ocspresp/ocspresp.gyp:ocspresp', 'cmd/oidcalc/oidcalc.gyp:oidcalc', diff --git a/readme.md b/readme.md index 17b99e805c..67eca19fb2 100644 --- a/readme.md +++ b/readme.md @@ -97,7 +97,7 @@ e.g. `NSS_TESTS=ssl_gtests ./all.sh` or by changing into the according directory and running the bash script there `cd ssl_gtests && ./ssl_gtests.sh`. The following tests are available: - cipher lowhash libpkix cert dbtests tools fips sdr crmf smime ssl ocsp merge pkits chains ec gtests ssl_gtests bogo + cipher lowhash libpkix cert dbtests tools fips sdr crmf smime ssl ocsp merge pkits chains ec gtests ssl_gtests bogo policy To make tests run faster it's recommended to set `NSS_CYCLES=standard` to run only the standard cycle. diff --git a/tests/all.sh b/tests/all.sh index f8a777fb3b..bc2e148036 100755 --- a/tests/all.sh +++ b/tests/all.sh @@ -37,6 +37,7 @@ # memleak.sh - memory leak testing (optional) # ssl_gtests.sh- Gtest based unit tests for ssl # gtests.sh - Gtest based unit tests for everything else +# policy.sh - Crypto Policy tests # bogo.sh - Bogo interop tests (disabled by default) # https://boringssl.googlesource.com/boringssl/+/master/ssl/test/PORTING.md # interop.sh - Interoperability tests (disabled by default) @@ -300,7 +301,7 @@ if [ $NO_INIT_SUPPORT -eq 0 ]; then RUN_FIPS="fips" fi -tests="cipher lowhash libpkix cert dbtests tools $RUN_FIPS sdr crmf smime ssl ocsp merge pkits ec gtests ssl_gtests" +tests="cipher lowhash libpkix cert dbtests tools $RUN_FIPS sdr crmf smime ssl ocsp merge pkits ec gtests ssl_gtests policy" # Don't run chains tests when we have a gyp build. if [ "$OBJDIR" != "Debug" -a "$OBJDIR" != "Release" ]; then tests="$tests chains" diff --git a/tests/policy/crypto-policy.txt b/tests/policy/crypto-policy.txt new file mode 100644 index 0000000000..9a8c0cd1b5 --- /dev/null +++ b/tests/policy/crypto-policy.txt @@ -0,0 +1,19 @@ +# col 1: expected return value of nss-policy-check +# col 2: policy config statement, using _ instead of space +# col 3: an extended regular expression, expected to match the output +# col 4: description of the test +# +0 disallow=ALL_allow=HMAC-SHA256:HMAC-SHA1:HMAC-SHA384:HMAC-SHA512:SECP256R1:SECP384R1:SECP521R1:aes256-gcm:chacha20-poly1305:aes256-cbc:camellia256-cbc:aes128-gcm:aes128-cbc:camellia128-cbc:SHA256:SHA384:SHA512:SHA1:ECDHE-RSA:ECDHE-ECDSA:RSA:DHE-RSA:tls-version-min=tls1.0:dtls-version-min=dtls1.0:DH-MIN=1023:DSA-MIN=2048:RSA-MIN=2048 NSS-POLICY-INFO.*LOADED-SUCCESSFULLY Standard policy +0 disallow=ALL_allow=HMAC-SHA1:HMAC-SHA256:HMAC-SHA384:HMAC-SHA512:SECP256R1:SECP384R1:SECP521R1:aes256-gcm:chacha20-poly1305:aes256-cbc:camellia256-cbc:aes128-gcm:aes128-cbc:camellia128-cbc:des-ede3-cbc:rc4:SHA256:SHA384:SHA512:SHA1:ECDHE-RSA:ECDHE-ECDSA:RSA:DHE-RSA:DHE-DSS:tls-version-min=tls1.0:dtls-version-min=tls1.0:DH-MIN=1023:DSA-MIN=1023:RSA-MIN=1023 NSS-POLICY-INFO.*LOADED-SUCCESSFULLY Legacy policy +0 disallow=ALL_allow=HMAC-SHA256:HMAC-SHA384:HMAC-SHA512:SECP384R1:SECP521R1:aes256-gcm:chacha20-poly1305:SHA384:SHA512:ECDHE-RSA:ECDHE-ECDSA:RSA:DHE-RSA:tls-version-min=tls1.2:dtls-version-min=dtls1.2:DH-MIN=3072:DSA-MIN=3072:RSA-MIN=3072 NSS-POLICY-INFO.*LOADED-SUCCESSFULLY Reduced policy +2 disallow=ALL_allow=dtls-version-min=:dtls-version-max= NSS-POLICY-FAIL Missing value +2 disallow=ALL_allow=RSA-MIN=whatever NSS-POLICY-FAIL Invalid value +2 disallow=ALL_allow=flower NSS-POLICY-FAIL Invalid identifier +1 disallow=all NSS-POLICY-WARN.*NUMBER-OF-CERT-SIG disallow all +1 disallow=ALL_allow=HMAC-SHA256:HMAC-SHA384:HMAC-SHA512:SECP384R1:SECP521R1:aes256-gcm:chacha20-poly1305:ECDHE-RSA:ECDHE-ECDSA:RSA:DHE-RSA:tls-version-min=tls1.2:dtls-version-min=dtls1.2:DH-MIN=3072:DSA-MIN=3072:RSA-MIN=3072 NSS-POLICY-WARN.*NUMBER-OF-HASH No Hashes +1 disallow=ALL_allow=tls-version-min=0:tls-version-max=0 NSS-POLICY-WARN.*NUMBER-OF-TLS-VERSIONS All TLS versions disabled +1 disallow=ALL_allow=dtls-version-min=0:dtls-version-max=0 NSS-POLICY-WARN.*NUMBER-OF-DTLS-VERSIONS All DTLS versions disabled +1 disallow=ALL_allow=tls-version-min=tls1.2:tls-version-max=tls1.1 NSS-POLICY-WARN.*NUMBER-OF-TLS-VERSIONS Invalid range of TLS versions +1 disallow=ALL_allow=dtls-version-min=tls1.2:dtls-version-max=tls1.1 NSS-POLICY-WARN.*NUMBER-OF-DTLS-VERSIONS Invalid range of DTLS versions +1 disallow=ALL_allow=tls-version-min=tls1.1:tls-version-max=tls1.2 NSS-POLICY-INFO.*NUMBER-OF-TLS-VERSIONS Valid range of TLS versions +1 disallow=ALL_allow=dtls-version-min=tls1.1:dtls-version-max=tls1.2 NSS-POLICY-INFO.*NUMBER-OF-DTLS-VERSIONS Valid range of DTLS versions diff --git a/tests/policy/policy.sh b/tests/policy/policy.sh new file mode 100644 index 0000000000..228c982a5a --- /dev/null +++ b/tests/policy/policy.sh @@ -0,0 +1,58 @@ +#! /bin/bash +# +# 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/. + +######################################################################## +# +# mozilla/security/nss/tests/policy/policy.sh +# +# Script to test NSS crypto policy code +# +######################################################################## + +ignore_blank_lines() +{ + LC_ALL=C grep -v '^[[:space:]]*\(#\|$\)' "$1" +} + +policy_run_tests() +{ + html_head "CRYPTO-POLICY" + + POLICY_INPUT=${QADIR}/policy/crypto-policy.txt + + ignore_blank_lines ${POLICY_INPUT} | \ + while read value policy match testname + do + echo "$SCRIPTNAME: running \"$testname\" ----------------------------" + policy=`echo ${policy} | sed -e 's;_; ;g'` + match=`echo ${match} | sed -e 's;_; ;g'` + POLICY_FILE="${TMP}/nss-policy" + + echo "$SCRIPTNAME: policy: \"$policy\"" + + cat > "$POLICY_FILE" << ++EOF++ +library= +name=Policy +NSS=flags=policyOnly,moduleDB +++EOF++ + echo "config=\"${policy}\"" >> "$POLICY_FILE" + echo "" >> "$POLICY_FILE" + + nss-policy-check "$POLICY_FILE" >${TMP}/$HOST.tmp.$$ 2>&1 + ret=$? + cat ${TMP}/$HOST.tmp.$$ + + html_msg $ret $value "\"${testname}\"" \ + "produced a returncode of $ret, expected is $value" + + egrep "${match}" ${TMP}/$HOST.tmp.$$ + ret=$? + html_msg $ret 0 "\"${testname}\" output is expected to match \"${match}\"" + + done +} + +policy_run_tests