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

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