|
|
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 |
|