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

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