FIPS 140 service indicator revamp

David Sugar david at atsec.com
Mon Oct 14 11:27:35 CEST 2024


Hi,

Libgcrypt implements a FIPS 140 service indicator to comply with the rules
stipulated by FIPS 140-3. However, based on recent clarifications provided by
NIST around the topic, the current implementation falls short of meeting these
clarifications.

The current service indicator "statically" returns the indicator whether an
algorithm operates in a FIPS-compliant manner or not. This includes the
indicator whether the cryptographic algorithm is an "approved" algorithm or
not.

The issue with this approach is that it is "static" in the sense that a caller
asks libgcrypt whether algorithm X is approved or not. NIST clarified that
such static behavior is not sufficient. NIST requests a "dynamic" indicator in
the sense that during the processing of the actual request to perform a
cryptographic operation, the indicator must be "generated" and "returned" to
the caller. I.e. when the caller performs, say an RSA 1024 operation, that
very API call is required to generate the indicator that the request was not
FIPS compliant. Conversely, when the caller performs an RSA 2048 operation,
that very API call must generate the indicator that it was an approved
mechanism.

The NIST requirement does not pre-scribe how exactly that indicator is to be
implemented. This implies we have quite some freedom to implement it.

This email offers a proposal to solve the service indicator non-compliance.
The provided patch adjusts one cryptographic service to show the chosen
approach. If we all agree, we will proceed in developing the patch series to
cover the other services appropriately.

The issue we have to solve is the following: the APIs defined by libcrypt
return a status value with an integer field. That field is defined to be 0 for
success and != 0 for all errors. With the presence of a FIPS service
indicator, we must return a second return status which may not be equal to
zero. Yet, we want consumers to not view the result of the FIPS service
indicator as an error to ensure even in FIPS mode, non-approved services can
be invoked without changing the caller. Thus, it is not possible to return the
FIPS service indicator as part of the standard return status (e.g. by using
the high bits in the integer field).

Our solution we offer here is the use of the errno to convey the FIPS status
indicator. As the errno is also used to communicate a real issue, we define
the errno as a FIPS status indicator only if the API return code is zero, i.e.
there is a successful API operation. I.e.:

- if the API return code is 0 -> errno contains the FIPS service indicator

- if the API return code is != 0 -> errno contains the "regular" error
indicator (i.e. the patch will not touch the errno)


To achieve that and not trample the errno during the libgcrypt operation, the
patch suggests:

1. adding a new libgcrypt error code - this error code is only used internally

2. adding an instrumentation to each API call to mark the service as approved
or non-approved at the very end of the operation if the operation was
successful. In case of a non-approved service, the new libgcrypt error code is
used. The placement at the end of the API call implies that (a) the API call
performs all operations and (b) returns the service indicator.

3. in the interface layer of the visibility.c, the FIPS non-approved "error"
is converted into an API return code of 0 and an errno of -EOPNOTSUPP.
Otherwise the errno is set to 0.


This implies that:

1. a caller even in FIPS mode will always have its API call succeeding (even
if it is non-approved)

2. a caller can check the FIPS indicator if the API return code is 0 by
checking the errno to contain -EOPNOTSUPP. If it contains this error code, the
caller knows the serivce was non-approved. If it contains 0, the caller knows
the service was approved.



Question: The patch currently implements the logic that in case the new
libgcrypt-internal error observed, the errno is set. This implies that for all
non-approved services an appropriate instrumentation is required. Would it
make sense to reverse the logic, i.e. return the libgcrypt-internal error if a
FIPS-approved serivce is triggered? The visibility-layer would then set the
errno to -EOPNOTSUPP if the libgcrypt-internal error is*not* seen. With this
approach, we can have a white-list where we only need to instrument the API
calls that we know are FIPS-approved. This would limit the amount of changes
and with a white-list we many not forget non-approved serivces.

Best regards
David

-- 
atsec information security GmbH, Ismaninger Str. 19, 81675 München, Germany
Phone: +49-89-44249840  /  Web: atsec.com
HRB: 129439 (Amtsgericht München)
Geschaeftsfuehrer: Staffan Persson, Dr. Michael Vogel

-------------- next part --------------
--- libgcrypt/cipher/kdf.c	2024-10-10 11:13:04.000000000 +0200
+++ libgcrypt-fips2/cipher/kdf.c	2024-10-14 11:02:54.146310526 +0200
@@ -239,7 +239,11 @@
    but certain KDF algorithm may use it differently.  {SALT,SALTLEN}
    is a salt as needed by most KDF algorithms.  ITERATIONS is a
    positive integer parameter to most KDFs.  0 is returned on success,
-   or an error code on failure.  */
+   or an error code on failure.  
+    
+   FIPS: if fips_mode() is enabled, the function will return
+         GPG_ERR_FORBIDDEN if the function call violates one
+         or more fips requirements. */
 gpg_err_code_t
 _gcry_kdf_derive (const void *passphrase, size_t passphraselen,
                   int algo, int subalgo,
@@ -285,19 +289,19 @@
         {
           /* FIPS requires minimum passphrase length, see FIPS 140-3 IG D.N */
           if (fips_mode () && passphraselen < 8)
-            return GPG_ERR_INV_VALUE;
+            return fips_not_compliant();
 
           /* FIPS requires minimum salt length of 128 b (SP 800-132 sec. 5.1, p.6) */
           if (fips_mode () && saltlen < 16)
-            return GPG_ERR_INV_VALUE;
+            return fips_not_compliant();
 
           /* FIPS requires minimum iterations bound (SP 800-132 sec 5.2, p.6) */
           if (fips_mode () && iterations < 1000)
-            return GPG_ERR_INV_VALUE;
+            return fips_not_compliant();
 
           /* Check minimum key size */
           if (fips_mode () && keysize < 14)
-            return GPG_ERR_INV_VALUE;
+            return fips_not_compliant();
 
           ec = _gcry_kdf_pkdf2 (passphrase, passphraselen, subalgo,
                                 salt, saltlen, iterations, keysize, keybuffer);
@@ -318,6 +322,10 @@
       ec = GPG_ERR_UNKNOWN_ALGORITHM;
       break;
     }
+    
+ /* On success we verify that the kdf is allowed in fips_mode */
+ if (ec == 0 && fips_mode () && !_gcry_fips_check_kdf_compliant(algo, subalgo))
+   ec = fips_not_compliant();
 
  leave:
   return ec;
--- libgcrypt/src/fips.c	2024-10-10 11:13:04.000000000 +0200
+++ libgcrypt-fips2/src/fips.c	2024-10-14 10:58:54.243408712 +0200
@@ -1172,3 +1172,28 @@
   abort ();
   /*NOTREACHED*/
 }
+
+enum gcry_fips_kdf_valid_alg_idx
+  {
+    GCRY_FIPS_KDF_PBKDF2_IDX = 0,
+    /* This has to be the last element */
+    GCRY_FIPS_KDF_TOTAL
+  };
+
+int gcry_fips_kdf_valid_alg[GCRY_FIPS_KDF_TOTAL] = {
+    GCRY_KDF_PBKDF2,
+};
+
+/* This function should be called to ensure that the used kdf
+   is fips compliant. */
+int
+_gcry_fips_check_kdf_compliant(int algo, int subalgo /* TODO: dead param */)
+{
+    for (int i = 0; i < GCRY_FIPS_KDF_TOTAL; i++)
+      {
+        if (gcry_fips_kdf_valid_alg[i] == algo)
+            return 1;
+      }
+    return 0;
+}
+
--- libgcrypt/src/g10lib.h	2024-10-10 11:13:04.000000000 +0200
+++ libgcrypt-fips2/src/g10lib.h	2024-10-14 09:55:52.409417815 +0200
@@ -484,6 +484,9 @@
                 (!fips_mode () || _gcry_global_is_operational ()))
 
 #define fips_not_operational()  (GPG_ERR_NOT_OPERATIONAL)
+#define fips_not_compliant()    (GPG_ERR_FORBIDDEN)
+
+int _gcry_fips_check_kdf_compliant(int algo, int subalgo);
 
 int _gcry_fips_test_operational (void);
 int _gcry_fips_test_error_or_operational (void);
--- libgcrypt/src/visibility.c	2024-10-10 11:13:04.000000000 +0200
+++ libgcrypt-fips2/src/visibility.c	2024-10-14 11:01:26.000023647 +0200
@@ -20,6 +20,7 @@
 
 #include <config.h>
 #include <stdarg.h>
+#include <errno.h>
 
 #define _GCRY_INCLUDED_BY_VISIBILITY_C
 #include "g10lib.h"
@@ -1398,11 +1399,29 @@
                  unsigned long iterations,
                  size_t keysize, void *keybuffer)
 {
+  gpg_err_code_t status;
+
   if (!fips_is_operational ())
     return gpg_error (fips_not_operational ());
-  return gpg_error (_gcry_kdf_derive (passphrase, passphraselen, algo, hashalgo,
-                                      salt, saltlen, iterations,
-                                      keysize, keybuffer));
+  status = _gcry_kdf_derive (passphrase,
+                              passphraselen, algo, hashalgo,
+                              salt, saltlen, iterations,
+                              keysize, keybuffer);
+  /*
+   * A FIPS error is indicated by the errno EOPNOTSUPP, i.e.,
+   * to check for success in fips_mode the caller must immediately
+   * check `errno == EOPNOTSUPP` and act upon it, even if the returned
+   * status code is success.
+   */
+  if (status == fips_not_compliant())
+    {
+      errno = EOPNOTSUPP;
+      status = 0;
+    }
+  else
+    errno = 0;
+
+  return gpg_error (status);
 }
 
 gpg_error_t
-------------- next part --------------
A non-text attachment was scrubbed...
Name: OpenPGP_signature.asc
Type: application/pgp-signature
Size: 840 bytes
Desc: OpenPGP digital signature
URL: <https://lists.gnupg.org/pipermail/gcrypt-devel/attachments/20241014/194b3d14/attachment.sig>


More information about the Gcrypt-devel mailing list