Blame SOURCES/0003-Issue-3584-Fix-PBKDF2_SHA256-hashing-in-FIPS-mode-49.patch

b2bc38
From 957ffd53b041c19d27753a028e6f514dcc75dfbd Mon Sep 17 00:00:00 2001
b2bc38
From: Simon Pichugin <spichugi@redhat.com>
b2bc38
Date: Tue, 26 Oct 2021 15:51:24 -0700
b2bc38
Subject: [PATCH 03/12] Issue 3584 - Fix PBKDF2_SHA256 hashing in FIPS mode
b2bc38
 (#4949)
b2bc38
b2bc38
Issue Description: Use PK11_Decrypt function to get hash data
b2bc38
because PK11_ExtractKeyValue function is forbidden in FIPS mode.
b2bc38
We can't extract keys while in FIPS mode. But we use PK11_ExtractKeyValue
b2bc38
for hashes, and it's not forbidden.
b2bc38
b2bc38
We can't use OpenSSL's PBKDF2-SHA256 implementation right now because
b2bc38
we need to support an upgrade procedure while in FIPS mode (update
b2bc38
hash on bind). For that, we should fix existing PBKDF2 usage, and we can
b2bc38
switch to OpenSSL's PBKDF2-SHA256 in the following versions.
b2bc38
b2bc38
Fix Description: Use PK11_Decrypt function to get the data.
b2bc38
b2bc38
Enable TLS on all CI test topologies while in FIPS because without
b2bc38
that we don't set up the NSS database correctly.
b2bc38
b2bc38
Add PBKDF2-SHA256 (OpenSSL) to ldif templates, so the password scheme is
b2bc38
discoverable by internal functions.
b2bc38
b2bc38
https://github.com/389ds/389-ds-base/issues/3584
b2bc38
b2bc38
Reviewed by: @progier389, @mreynolds389, @Firstyear, @tbordaz (Thanks!!)
b2bc38
---
b2bc38
 .../healthcheck/health_security_test.py       | 10 ---
b2bc38
 ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c  | 62 ++++++++++++++++---
b2bc38
 ldap/servers/slapd/main.c                     | 12 ++++
b2bc38
 src/lib389/lib389/__init__.py                 |  4 ++
b2bc38
 src/lib389/lib389/topologies.py               |  6 +-
b2bc38
 src/lib389/lib389/utils.py                    | 13 ++++
b2bc38
 6 files changed, 86 insertions(+), 21 deletions(-)
b2bc38
b2bc38
diff --git a/dirsrvtests/tests/suites/healthcheck/health_security_test.py b/dirsrvtests/tests/suites/healthcheck/health_security_test.py
b2bc38
index 6c0d27aaa..c1dc7938c 100644
b2bc38
--- a/dirsrvtests/tests/suites/healthcheck/health_security_test.py
b2bc38
+++ b/dirsrvtests/tests/suites/healthcheck/health_security_test.py
b2bc38
@@ -40,16 +40,6 @@ else:
b2bc38
 log = logging.getLogger(__name__)
b2bc38
 
b2bc38
 
b2bc38
-def is_fips():
b2bc38
-    if os.path.exists('/proc/sys/crypto/fips_enabled'):
b2bc38
-        with open('/proc/sys/crypto/fips_enabled', 'r') as f:
b2bc38
-            state = f.readline().strip()
b2bc38
-            if state == '1':
b2bc38
-                return True
b2bc38
-            else:
b2bc38
-                return False
b2bc38
-
b2bc38
-
b2bc38
 def run_healthcheck_and_flush_log(topology, instance, searched_code, json, searched_code2=None):
b2bc38
     args = FakeArgs()
b2bc38
     args.instance = instance.serverid
b2bc38
diff --git a/ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c b/ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c
b2bc38
index d310dc792..dcac4fcdd 100644
b2bc38
--- a/ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c
b2bc38
+++ b/ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c
b2bc38
@@ -91,10 +91,11 @@ pbkdf2_sha256_extract(char *hash_in, SECItem *salt, uint32_t *iterations)
b2bc38
 SECStatus
b2bc38
 pbkdf2_sha256_hash(char *hash_out, size_t hash_out_len, SECItem *pwd, SECItem *salt, uint32_t iterations)
b2bc38
 {
b2bc38
-    SECItem *result = NULL;
b2bc38
     SECAlgorithmID *algid = NULL;
b2bc38
     PK11SlotInfo *slot = NULL;
b2bc38
     PK11SymKey *symkey = NULL;
b2bc38
+    SECItem *wrapKeyData = NULL;
b2bc38
+    SECStatus rv = SECFailure;
b2bc38
 
b2bc38
     /* We assume that NSS is already started. */
b2bc38
     algid = PK11_CreatePBEV2AlgorithmID(SEC_OID_PKCS5_PBKDF2, SEC_OID_HMAC_SHA256, SEC_OID_HMAC_SHA256, hash_out_len, iterations, salt);
b2bc38
@@ -104,7 +105,6 @@ pbkdf2_sha256_hash(char *hash_out, size_t hash_out_len, SECItem *pwd, SECItem *s
b2bc38
         slot = PK11_GetBestSlotMultiple(mechanism_array, 2, NULL);
b2bc38
         if (slot != NULL) {
b2bc38
             symkey = PK11_PBEKeyGen(slot, algid, pwd, PR_FALSE, NULL);
b2bc38
-            PK11_FreeSlot(slot);
b2bc38
             if (symkey == NULL) {
b2bc38
                 /* We try to get the Error here but NSS has two or more error interfaces, and sometimes it uses none of them. */
b2bc38
                 int32_t status = PORT_GetError();
b2bc38
@@ -123,18 +123,60 @@ pbkdf2_sha256_hash(char *hash_out, size_t hash_out_len, SECItem *pwd, SECItem *s
b2bc38
         return SECFailure;
b2bc38
     }
b2bc38
 
b2bc38
-    if (PK11_ExtractKeyValue(symkey) == SECSuccess) {
b2bc38
-        result = PK11_GetKeyData(symkey);
b2bc38
-        if (result != NULL && result->len <= hash_out_len) {
b2bc38
-            memcpy(hash_out, result->data, result->len);
b2bc38
-            PK11_FreeSymKey(symkey);
b2bc38
+    /*
b2bc38
+     * First, we need to generate a wrapped key for PK11_Decrypt call:
b2bc38
+     * slot is the same slot we used in PK11_PBEKeyGen()
b2bc38
+     * 256 bits / 8 bit per byte
b2bc38
+     */
b2bc38
+    PK11SymKey *wrapKey = PK11_KeyGen(slot, CKM_AES_ECB, NULL, 256/8, NULL);
b2bc38
+    PK11_FreeSlot(slot);
b2bc38
+    if (wrapKey == NULL) {
b2bc38
+        slapi_log_err(SLAPI_LOG_ERR, "pbkdf2_sha256_hash", "Unable to generate a wrapped key.\n");
b2bc38
+        return SECFailure;
b2bc38
+	}
b2bc38
+
b2bc38
+    wrapKeyData = (SECItem *)PORT_Alloc(sizeof(SECItem));
b2bc38
+    /* Align the wrapped key with 32 bytes. */
b2bc38
+    wrapKeyData->len = (PK11_GetKeyLength(symkey) + 31) & ~31;
b2bc38
+    /* Allocate the aligned space for pkc5PBE key plus AESKey block */
b2bc38
+    wrapKeyData->data = (unsigned char *)slapi_ch_calloc(wrapKeyData->len, sizeof(unsigned char));
b2bc38
+
b2bc38
+    /* Get symkey wrapped with wrapKey - required for PK11_Decrypt call */
b2bc38
+    rv = PK11_WrapSymKey(CKM_AES_ECB, NULL, wrapKey, symkey, wrapKeyData);
b2bc38
+    if (rv != SECSuccess) {
b2bc38
+        PK11_FreeSymKey(symkey);
b2bc38
+        PK11_FreeSymKey(wrapKey);
b2bc38
+        SECITEM_FreeItem(wrapKeyData, PR_TRUE);
b2bc38
+        slapi_log_err(SLAPI_LOG_ERR, "pbkdf2_sha256_hash", "Unable to wrap the symkey. (%d)\n", rv);
b2bc38
+        return SECFailure;
b2bc38
+    }
b2bc38
+
b2bc38
+    /* Allocate the space for our result */
b2bc38
+    void *result = (char *)slapi_ch_calloc(wrapKeyData->len, sizeof(char));
b2bc38
+    unsigned int result_len = 0;
b2bc38
+
b2bc38
+    /* User wrapKey to decrypt the wrapped contents.
b2bc38
+     * result is the hash that we need;
b2bc38
+     * result_len is the actual lengh of the data;
b2bc38
+     * has_out_len is the maximum (the space we allocted for hash_out)
b2bc38
+     */
b2bc38
+    rv = PK11_Decrypt(wrapKey, CKM_AES_ECB, NULL, result, &result_len, hash_out_len, wrapKeyData->data, wrapKeyData->len);
b2bc38
+    PK11_FreeSymKey(symkey);
b2bc38
+    PK11_FreeSymKey(wrapKey);
b2bc38
+    SECITEM_FreeItem(wrapKeyData, PR_TRUE);
b2bc38
+
b2bc38
+    if (rv == SECSuccess) {
b2bc38
+        if (result != NULL && result_len <= hash_out_len) {
b2bc38
+            memcpy(hash_out, result, result_len);
b2bc38
+            slapi_ch_free((void **)&result);
b2bc38
         } else {
b2bc38
-            PK11_FreeSymKey(symkey);
b2bc38
-            slapi_log_err(SLAPI_LOG_ERR, (char *)schemeName, "Unable to retrieve (get) hash output.\n");
b2bc38
+            slapi_log_err(SLAPI_LOG_ERR, "pbkdf2_sha256_hash", "Unable to retrieve (get) hash output.\n");
b2bc38
+            slapi_ch_free((void **)&result);
b2bc38
             return SECFailure;
b2bc38
         }
b2bc38
     } else {
b2bc38
-        slapi_log_err(SLAPI_LOG_ERR, (char *)schemeName, "Unable to extract hash output.\n");
b2bc38
+        slapi_log_err(SLAPI_LOG_ERR, "pbkdf2_sha256_hash", "Unable to extract hash output. (%d)\n", rv);
b2bc38
+        slapi_ch_free((void **)&result);
b2bc38
         return SECFailure;
b2bc38
     }
b2bc38
 
b2bc38
diff --git a/ldap/servers/slapd/main.c b/ldap/servers/slapd/main.c
b2bc38
index 61ed40b7d..04d0494f8 100644
b2bc38
--- a/ldap/servers/slapd/main.c
b2bc38
+++ b/ldap/servers/slapd/main.c
b2bc38
@@ -2895,9 +2895,21 @@ slapd_do_all_nss_ssl_init(int slapd_exemode, int importexport_encrypt, int s_por
b2bc38
      * is enabled or not. We use NSS for random number generation and
b2bc38
      * other things even if we are not going to accept SSL connections.
b2bc38
      * We also need NSS for attribute encryption/decryption on import and export.
b2bc38
+     *
b2bc38
+     * It's important to remember that while in FIPS mode the administrator should always enable
b2bc38
+     * the security, otherwise we don't call slapd_pk11_authenticate which is a requirement for FIPS mode
b2bc38
      */
b2bc38
+    PRBool isFIPS = slapd_pk11_isFIPS();
b2bc38
     int init_ssl = config_get_security();
b2bc38
 
b2bc38
+    if (isFIPS && !init_ssl) {
b2bc38
+        slapi_log_err(SLAPI_LOG_WARNING, "slapd_do_all_nss_ssl_init",
b2bc38
+                      "ERROR: TLS is not enabled, and the machine is in FIPS mode. "
b2bc38
+                      "Some functionality won't work correctly (for example, "
b2bc38
+                      "users with PBKDF2_SHA256 password scheme won't be able to log in). "
b2bc38
+                      "It's highly advisable to enable TLS on this instance.\n");
b2bc38
+    }
b2bc38
+
b2bc38
     if (slapd_exemode == SLAPD_EXEMODE_SLAPD) {
b2bc38
         init_ssl = init_ssl && (0 != s_port) && (s_port <= LDAP_PORT_MAX);
b2bc38
     } else {
b2bc38
diff --git a/src/lib389/lib389/__init__.py b/src/lib389/lib389/__init__.py
b2bc38
index 29ee5245a..e0299c5b4 100644
b2bc38
--- a/src/lib389/lib389/__init__.py
b2bc38
+++ b/src/lib389/lib389/__init__.py
b2bc38
@@ -1588,6 +1588,10 @@ class DirSrv(SimpleLDAPObject, object):
b2bc38
         :param post_open: Open the server connection after restart.
b2bc38
         :type post_open: bool
b2bc38
         """
b2bc38
+        if self.config.get_attr_val_utf8_l("nsslapd-security") == 'on':
b2bc38
+            self.restart(post_open=post_open)
b2bc38
+            return
b2bc38
+
b2bc38
         # If it doesn't exist, create a cadb.
b2bc38
         ssca = NssSsl(dbpath=self.get_ssca_dir())
b2bc38
         if not ssca._db_exists():
b2bc38
diff --git a/src/lib389/lib389/topologies.py b/src/lib389/lib389/topologies.py
b2bc38
index e9969f524..e7d56582d 100644
b2bc38
--- a/src/lib389/lib389/topologies.py
b2bc38
+++ b/src/lib389/lib389/topologies.py
b2bc38
@@ -15,7 +15,7 @@ import socket
b2bc38
 import pytest
b2bc38
 
b2bc38
 from lib389 import DirSrv
b2bc38
-from lib389.utils import generate_ds_params
b2bc38
+from lib389.utils import generate_ds_params, is_fips
b2bc38
 from lib389.mit_krb5 import MitKrb5
b2bc38
 from lib389.saslmap import SaslMappings
b2bc38
 from lib389.replica import ReplicationManager, Replicas
b2bc38
@@ -108,6 +108,10 @@ def _create_instances(topo_dict, suffix):
b2bc38
             if role == ReplicaRole.HUB:
b2bc38
                 hs[instance.serverid] = instance
b2bc38
                 instances.update(hs)
b2bc38
+            # We should always enable TLS while in FIPS mode because otherwise NSS database won't be
b2bc38
+            # configured in a FIPS compliant way
b2bc38
+            if is_fips():
b2bc38
+                instance.enable_tls()
b2bc38
             log.info("Instance with parameters {} was created.".format(args_instance))
b2bc38
 
b2bc38
     if "standalone1" in instances and len(instances) == 1:
b2bc38
diff --git a/src/lib389/lib389/utils.py b/src/lib389/lib389/utils.py
b2bc38
index b270784ce..5ba0c6676 100644
b2bc38
--- a/src/lib389/lib389/utils.py
b2bc38
+++ b/src/lib389/lib389/utils.py
b2bc38
@@ -1430,3 +1430,16 @@ def is_valid_hostname(hostname):
b2bc38
         hostname = hostname[:-1] # strip exactly one dot from the right, if present
b2bc38
     allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?
b2bc38
     return all(allowed.match(x) for x in hostname.split("."))
b2bc38
+
b2bc38
+
b2bc38
+def is_fips():
b2bc38
+    if os.path.exists('/proc/sys/crypto/fips_enabled'):
b2bc38
+        with open('/proc/sys/crypto/fips_enabled', 'r') as f:
b2bc38
+            state = f.readline().strip()
b2bc38
+            if state == '1':
b2bc38
+                return True
b2bc38
+            else:
b2bc38
+                return False
b2bc38
+    else:
b2bc38
+        return False
b2bc38
+
b2bc38
-- 
b2bc38
2.31.1
b2bc38