Blame SOURCES/downstream-FIPS-with-PRNG-and-RADIUS-and-MD4.patch

a0043a
From a7318c3cd6e1f58adb80493c05b59e6c180cd584 Mon Sep 17 00:00:00 2001
a0043a
From: Julien Rische <jrische@redhat.com>
a0043a
Date: Wed, 23 Feb 2022 17:34:33 +0100
cb4cef
Subject: [PATCH] [downstream] FIPS with PRNG and RADIUS and MD4
cb4cef
cb4cef
NB: Use openssl's PRNG in FIPS mode and taint within krad.
cb4cef
cb4cef
A lot of the FIPS error conditions from OpenSSL are incredibly
cb4cef
mysterious (at best, things return NULL unexpectedly; at worst,
cb4cef
internal assertions are tripped; most of the time, you just get
cb4cef
ENOMEM).  In order to cope with this, we need to have some level of
cb4cef
awareness of what we can and can't safely call.
cb4cef
cb4cef
This will slow down some calls slightly (FIPS_mode() takes multiple
cb4cef
locks), but not for any ciphers we care about - which is to say that
cb4cef
AES is fine.  Shame about SPAKE though.
cb4cef
cb4cef
post6 restores MD4 (and therefore keygen-only RC4).
cb4cef
cb4cef
post7 restores MD5 and adds radius_md5_fips_override.
cb4cef
a0043a
post8 restores MD4/MD5 for OpenSSL 3.0
a0043a
a0043a
Use OpenSSL 3.0 library context to access MD4 and MD5 lazily from
a0043a
legacy provider if RC4 encryption type is enabled, without affecting
a0043a
global context.
a0043a
a0043a
Remove EVP_MD_CTX_FLAG_NON_FIPS_ALLOW flag since does not have any
a0043a
effect anymore.
a0043a
a0043a
Last-updated: krb5-1.19
cb4cef
---
a0043a
 doc/admin/conf_files/krb5_conf.rst            |  6 ++
a0043a
 src/lib/crypto/krb/prng.c                     | 11 ++-
a0043a
 .../crypto/openssl/enc_provider/camellia.c    |  6 ++
a0043a
 src/lib/crypto/openssl/enc_provider/rc4.c     | 13 +++-
a0043a
 .../crypto/openssl/hash_provider/hash_evp.c   | 85 ++++++++++++++++++-
a0043a
 src/lib/crypto/openssl/hmac.c                 |  6 +-
a0043a
 src/lib/krad/attr.c                           | 46 ++++++++---
cb4cef
 src/lib/krad/attrset.c                        |  5 +-
a0043a
 src/lib/krad/internal.h                       | 28 ++++++-
a0043a
 src/lib/krad/packet.c                         | 22 +++---
a0043a
 src/lib/krad/remote.c                         | 10 ++-
cb4cef
 src/lib/krad/t_attr.c                         |  3 +-
cb4cef
 src/lib/krad/t_attrset.c                      |  4 +-
a0043a
 src/plugins/preauth/spake/spake_client.c      |  6 ++
a0043a
 src/plugins/preauth/spake/spake_kdc.c         |  6 ++
a0043a
 15 files changed, 218 insertions(+), 35 deletions(-)
cb4cef
cb4cef
diff --git a/doc/admin/conf_files/krb5_conf.rst b/doc/admin/conf_files/krb5_conf.rst
cb4cef
index 675175955..adba8238d 100644
cb4cef
--- a/doc/admin/conf_files/krb5_conf.rst
cb4cef
+++ b/doc/admin/conf_files/krb5_conf.rst
cb4cef
@@ -330,6 +330,12 @@ The libdefaults section may contain any of the following relations:
cb4cef
     qualification of shortnames, set this relation to the empty string
cb4cef
     with ``qualify_shortname = ""``.  (New in release 1.18.)
cb4cef
 
cb4cef
+**radius_md5_fips_override**
cb4cef
+    Downstream-only option to enable use of MD5 in RADIUS
cb4cef
+    communication (libkrad).  This allows for local (or protected
cb4cef
+    tunnel) communication with a RADIUS server that doesn't use krad
cb4cef
+    (e.g., freeradius) while in FIPS mode.
cb4cef
+
cb4cef
 **rdns**
cb4cef
     If this flag is true, reverse name lookup will be used in addition
cb4cef
     to forward name lookup to canonicalizing hostnames for use in
cb4cef
diff --git a/src/lib/crypto/krb/prng.c b/src/lib/crypto/krb/prng.c
cb4cef
index cb9ca9b98..f0e9984ca 100644
cb4cef
--- a/src/lib/crypto/krb/prng.c
cb4cef
+++ b/src/lib/crypto/krb/prng.c
cb4cef
@@ -26,6 +26,8 @@
cb4cef
 
cb4cef
 #include "crypto_int.h"
cb4cef
 
cb4cef
+#include <openssl/rand.h>
cb4cef
+
cb4cef
 krb5_error_code KRB5_CALLCONV
cb4cef
 krb5_c_random_seed(krb5_context context, krb5_data *data)
cb4cef
 {
cb4cef
@@ -99,9 +101,16 @@ krb5_boolean
cb4cef
 k5_get_os_entropy(unsigned char *buf, size_t len, int strong)
cb4cef
 {
cb4cef
     const char *device;
cb4cef
-#if defined(__linux__) && defined(SYS_getrandom)
cb4cef
     int r;
cb4cef
 
cb4cef
+    /* A wild FIPS mode appeared! */
cb4cef
+    if (FIPS_mode()) {
cb4cef
+        /* The return codes on this API are not good */
cb4cef
+        r = RAND_bytes(buf, len);
cb4cef
+        return r == 1;
cb4cef
+    }
cb4cef
+
cb4cef
+#if defined(__linux__) && defined(SYS_getrandom)
cb4cef
     while (len > 0) {
cb4cef
         /*
cb4cef
          * Pull from the /dev/urandom pool, but require it to have been seeded.
cb4cef
diff --git a/src/lib/crypto/openssl/enc_provider/camellia.c b/src/lib/crypto/openssl/enc_provider/camellia.c
cb4cef
index 2da691329..f79679a0b 100644
cb4cef
--- a/src/lib/crypto/openssl/enc_provider/camellia.c
cb4cef
+++ b/src/lib/crypto/openssl/enc_provider/camellia.c
cb4cef
@@ -304,6 +304,9 @@ krb5int_camellia_cbc_mac(krb5_key key, const krb5_crypto_iov *data,
cb4cef
     unsigned char blockY[CAMELLIA_BLOCK_SIZE], blockB[CAMELLIA_BLOCK_SIZE];
cb4cef
     struct iov_cursor cursor;
cb4cef
 
cb4cef
+    if (FIPS_mode())
cb4cef
+        return KRB5_CRYPTO_INTERNAL;
cb4cef
+
cb4cef
     if (output->length < CAMELLIA_BLOCK_SIZE)
cb4cef
         return KRB5_BAD_MSIZE;
cb4cef
 
cb4cef
@@ -331,6 +334,9 @@ static krb5_error_code
cb4cef
 krb5int_camellia_init_state (const krb5_keyblock *key, krb5_keyusage usage,
cb4cef
                              krb5_data *state)
cb4cef
 {
cb4cef
+    if (FIPS_mode())
cb4cef
+        return KRB5_CRYPTO_INTERNAL;
cb4cef
+
cb4cef
     state->length = 16;
cb4cef
     state->data = (void *) malloc(16);
cb4cef
     if (state->data == NULL)
cb4cef
diff --git a/src/lib/crypto/openssl/enc_provider/rc4.c b/src/lib/crypto/openssl/enc_provider/rc4.c
cb4cef
index bc87c6f42..9bf407899 100644
cb4cef
--- a/src/lib/crypto/openssl/enc_provider/rc4.c
cb4cef
+++ b/src/lib/crypto/openssl/enc_provider/rc4.c
cb4cef
@@ -66,6 +66,9 @@ k5_arcfour_docrypt(krb5_key key, const krb5_data *state, krb5_crypto_iov *data,
cb4cef
     EVP_CIPHER_CTX *ctx = NULL;
cb4cef
     struct arcfour_state *arcstate;
cb4cef
 
cb4cef
+    if (FIPS_mode())
cb4cef
+        return KRB5_CRYPTO_INTERNAL;
cb4cef
+
cb4cef
     arcstate = (state != NULL) ? (void *)state->data : NULL;
cb4cef
     if (arcstate != NULL) {
cb4cef
         ctx = arcstate->ctx;
cb4cef
@@ -113,7 +116,12 @@ k5_arcfour_docrypt(krb5_key key, const krb5_data *state, krb5_crypto_iov *data,
cb4cef
 static void
cb4cef
 k5_arcfour_free_state(krb5_data *state)
cb4cef
 {
cb4cef
-    struct arcfour_state *arcstate = (void *)state->data;
cb4cef
+    struct arcfour_state *arcstate;
cb4cef
+
cb4cef
+    if (FIPS_mode())
cb4cef
+        return;
cb4cef
+
cb4cef
+    arcstate = (void *) state->data;
cb4cef
 
cb4cef
     EVP_CIPHER_CTX_free(arcstate->ctx);
cb4cef
     free(arcstate);
cb4cef
@@ -125,6 +133,9 @@ k5_arcfour_init_state(const krb5_keyblock *key,
cb4cef
 {
cb4cef
     struct arcfour_state *arcstate;
cb4cef
 
cb4cef
+    if (FIPS_mode())
cb4cef
+        return KRB5_CRYPTO_INTERNAL;
cb4cef
+
cb4cef
     /*
cb4cef
      * The cipher state here is a saved pointer to a struct arcfour_state
cb4cef
      * object, rather than a flat byte array as in most enc providers.  The
cb4cef
diff --git a/src/lib/crypto/openssl/hash_provider/hash_evp.c b/src/lib/crypto/openssl/hash_provider/hash_evp.c
a0043a
index 1e0fb8fc3..4b8e1a6b2 100644
cb4cef
--- a/src/lib/crypto/openssl/hash_provider/hash_evp.c
cb4cef
+++ b/src/lib/crypto/openssl/hash_provider/hash_evp.c
a0043a
@@ -32,6 +32,50 @@
a0043a
 
a0043a
 #include "crypto_int.h"
a0043a
 #include <openssl/evp.h>
a0043a
+#include <openssl/provider.h>
a0043a
+#include <threads.h>
a0043a
+
a0043a
+typedef struct ossl_lib_md_context {
a0043a
+    OSSL_LIB_CTX *libctx;
a0043a
+    OSSL_PROVIDER *legacy_provider;
a0043a
+    EVP_MD *md;
a0043a
+} ossl_md_context_t;
a0043a
+
a0043a
+static thread_local ossl_md_context_t *ossl_md_ctx = NULL;
a0043a
+
a0043a
+static krb5_error_code
a0043a
+init_ossl_md_ctx(ossl_md_context_t *ctx, const char *algo)
a0043a
+{
a0043a
+    ctx->libctx = OSSL_LIB_CTX_new();
a0043a
+    if (!ctx->libctx)
a0043a
+        return KRB5_CRYPTO_INTERNAL;
a0043a
+
a0043a
+    /*
a0043a
+     * Load both legacy and default provider as both may be needed.
a0043a
+     * If they fail keep going and an error will be raised when we try to
a0043a
+     * fetch the cipher later.
a0043a
+     */
a0043a
+    ctx->legacy_provider = OSSL_PROVIDER_load(ctx->libctx, "legacy");
a0043a
+
a0043a
+    ctx->md = EVP_MD_fetch(ctx->libctx, algo, NULL);
a0043a
+    if (!ctx->md)
a0043a
+        return KRB5_CRYPTO_INTERNAL;
a0043a
+
a0043a
+    return 0;
a0043a
+}
a0043a
+
a0043a
+static void
a0043a
+deinit_ossl_ctx(ossl_md_context_t *ctx)
a0043a
+{
a0043a
+    if (ctx->md)
a0043a
+        EVP_MD_free(ctx->md);
a0043a
+
a0043a
+    if (ctx->legacy_provider)
a0043a
+        OSSL_PROVIDER_unload(ctx->legacy_provider);
a0043a
+
a0043a
+    if (ctx->libctx)
a0043a
+        OSSL_LIB_CTX_free(ctx->libctx);
a0043a
+}
a0043a
 
a0043a
 static krb5_error_code
a0043a
 hash_evp(const EVP_MD *type, const krb5_crypto_iov *data, size_t num_data,
a0043a
@@ -61,16 +104,53 @@ hash_evp(const EVP_MD *type, const krb5_crypto_iov *data, size_t num_data,
a0043a
     return ok ? 0 : KRB5_CRYPTO_INTERNAL;
a0043a
 }
cb4cef
 
a0043a
+static krb5_error_code
a0043a
+hash_legacy_evp(const char *algo, const krb5_crypto_iov *data, size_t num_data,
a0043a
+                krb5_data *output)
a0043a
+{
a0043a
+    krb5_error_code err;
a0043a
+
a0043a
+    if (!ossl_md_ctx) {
a0043a
+        ossl_md_ctx = malloc(sizeof(ossl_md_context_t));
a0043a
+        if (!ossl_md_ctx)
a0043a
+            return ENOMEM;
a0043a
+
a0043a
+        err = init_ossl_md_ctx(ossl_md_ctx, algo);
a0043a
+        if (err) {
a0043a
+            deinit_ossl_ctx(ossl_md_ctx);
a0043a
+            free(ossl_md_ctx);
a0043a
+            ossl_md_ctx = NULL;
a0043a
+            goto end;
a0043a
+        }
cb4cef
+    }
cb4cef
+
a0043a
+    err = hash_evp(ossl_md_ctx->md, data, num_data, output);
a0043a
+
a0043a
+end:
a0043a
+    return err;
a0043a
+}
a0043a
+
cb4cef
 static krb5_error_code
cb4cef
 hash_md4(const krb5_crypto_iov *data, size_t num_data, krb5_data *output)
cb4cef
 {
a0043a
-    return hash_evp(EVP_md4(), data, num_data, output);
cb4cef
+    /*
cb4cef
+     * MD4 is needed in FIPS mode to perform key generation for RC4 keys used
cb4cef
+     * by IPA.  These keys are only used along a (separately) secured channel
cb4cef
+     * for legacy reasons when performing trusts to Active Directory.
cb4cef
+     */
a0043a
+    return FIPS_mode() ? hash_legacy_evp("MD4", data, num_data, output)
a0043a
+                       : hash_evp(EVP_md4(), data, num_data, output);
cb4cef
 }
cb4cef
 
cb4cef
 static krb5_error_code
cb4cef
 hash_md5(const krb5_crypto_iov *data, size_t num_data, krb5_data *output)
cb4cef
 {
a0043a
-    return hash_evp(EVP_md5(), data, num_data, output);
a0043a
+    /*
a0043a
+     * MD5 is needed in FIPS mode for communication with RADIUS servers.  This
a0043a
+     * is gated in libkrad by libdefaults->radius_md5_fips_override.
a0043a
+     */
a0043a
+    return FIPS_mode() ? hash_legacy_evp("MD5", data, num_data, output)
a0043a
+                       : hash_evp(EVP_md5(), data, num_data, output);
cb4cef
 }
cb4cef
 
a0043a
 static krb5_error_code
cb4cef
diff --git a/src/lib/crypto/openssl/hmac.c b/src/lib/crypto/openssl/hmac.c
cb4cef
index 7dc59dcc0..769a50c00 100644
cb4cef
--- a/src/lib/crypto/openssl/hmac.c
cb4cef
+++ b/src/lib/crypto/openssl/hmac.c
cb4cef
@@ -103,7 +103,11 @@ map_digest(const struct krb5_hash_provider *hash)
cb4cef
         return EVP_sha256();
cb4cef
     else if (!strncmp(hash->hash_name, "SHA-384",7))
cb4cef
         return EVP_sha384();
cb4cef
-    else if (!strncmp(hash->hash_name, "MD5", 3))
cb4cef
+
cb4cef
+    if (FIPS_mode())
cb4cef
+        return NULL;
cb4cef
+
cb4cef
+    if (!strncmp(hash->hash_name, "MD5", 3))
cb4cef
         return EVP_md5();
cb4cef
     else if (!strncmp(hash->hash_name, "MD4", 3))
cb4cef
         return EVP_md4();
cb4cef
diff --git a/src/lib/krad/attr.c b/src/lib/krad/attr.c
cb4cef
index 9c13d9d75..42d354a3b 100644
cb4cef
--- a/src/lib/krad/attr.c
cb4cef
+++ b/src/lib/krad/attr.c
cb4cef
@@ -38,7 +38,8 @@
cb4cef
 typedef krb5_error_code
cb4cef
 (*attribute_transform_fn)(krb5_context ctx, const char *secret,
cb4cef
                           const unsigned char *auth, const krb5_data *in,
cb4cef
-                          unsigned char outbuf[MAX_ATTRSIZE], size_t *outlen);
cb4cef
+                          unsigned char outbuf[MAX_ATTRSIZE], size_t *outlen,
cb4cef
+                          krb5_boolean *is_fips);
cb4cef
 
cb4cef
 typedef struct {
cb4cef
     const char *name;
cb4cef
@@ -51,12 +52,14 @@ typedef struct {
cb4cef
 static krb5_error_code
cb4cef
 user_password_encode(krb5_context ctx, const char *secret,
cb4cef
                      const unsigned char *auth, const krb5_data *in,
cb4cef
-                     unsigned char outbuf[MAX_ATTRSIZE], size_t *outlen);
cb4cef
+                     unsigned char outbuf[MAX_ATTRSIZE], size_t *outlen,
cb4cef
+                     krb5_boolean *is_fips);
cb4cef
 
cb4cef
 static krb5_error_code
cb4cef
 user_password_decode(krb5_context ctx, const char *secret,
cb4cef
                      const unsigned char *auth, const krb5_data *in,
cb4cef
-                     unsigned char outbuf[MAX_ATTRSIZE], size_t *outlen);
cb4cef
+                     unsigned char outbuf[MAX_ATTRSIZE], size_t *outlen,
cb4cef
+                     krb5_boolean *ignored);
cb4cef
 
cb4cef
 static const attribute_record attributes[UCHAR_MAX] = {
cb4cef
     {"User-Name", 1, MAX_ATTRSIZE, NULL, NULL},
cb4cef
@@ -128,7 +131,8 @@ static const attribute_record attributes[UCHAR_MAX] = {
cb4cef
 static krb5_error_code
cb4cef
 user_password_encode(krb5_context ctx, const char *secret,
cb4cef
                      const unsigned char *auth, const krb5_data *in,
cb4cef
-                     unsigned char outbuf[MAX_ATTRSIZE], size_t *outlen)
cb4cef
+                     unsigned char outbuf[MAX_ATTRSIZE], size_t *outlen,
cb4cef
+                     krb5_boolean *is_fips)
cb4cef
 {
cb4cef
     const unsigned char *indx;
cb4cef
     krb5_error_code retval;
cb4cef
@@ -154,8 +158,15 @@ user_password_encode(krb5_context ctx, const char *secret,
cb4cef
     for (blck = 0, indx = auth; blck * BLOCKSIZE < len; blck++) {
cb4cef
         memcpy(tmp.data + seclen, indx, BLOCKSIZE);
cb4cef
 
cb4cef
-        retval = krb5_c_make_checksum(ctx, CKSUMTYPE_RSA_MD5, NULL, 0, &tmp,
cb4cef
-                                      &sum);
cb4cef
+        if (kr_use_fips(ctx)) {
cb4cef
+            /* Skip encryption here.  Taint so that we won't pass it out of
cb4cef
+             * the machine by accident. */
cb4cef
+            *is_fips = TRUE;
cb4cef
+            sum.contents = calloc(1, BLOCKSIZE);
cb4cef
+        } else {
cb4cef
+            retval = krb5_c_make_checksum(ctx, CKSUMTYPE_RSA_MD5, NULL, 0, &tmp,
cb4cef
+                                          &sum);
cb4cef
+        }
cb4cef
         if (retval != 0) {
cb4cef
             zap(tmp.data, tmp.length);
cb4cef
             zap(outbuf, len);
cb4cef
@@ -180,7 +191,8 @@ user_password_encode(krb5_context ctx, const char *secret,
cb4cef
 static krb5_error_code
cb4cef
 user_password_decode(krb5_context ctx, const char *secret,
cb4cef
                      const unsigned char *auth, const krb5_data *in,
cb4cef
-                     unsigned char outbuf[MAX_ATTRSIZE], size_t *outlen)
cb4cef
+                     unsigned char outbuf[MAX_ATTRSIZE], size_t *outlen,
cb4cef
+                     krb5_boolean *is_fips)
cb4cef
 {
cb4cef
     const unsigned char *indx;
cb4cef
     krb5_error_code retval;
cb4cef
@@ -204,8 +216,15 @@ user_password_decode(krb5_context ctx, const char *secret,
cb4cef
     for (blck = 0, indx = auth; blck * BLOCKSIZE < in->length; blck++) {
cb4cef
         memcpy(tmp.data + seclen, indx, BLOCKSIZE);
cb4cef
 
cb4cef
-        retval = krb5_c_make_checksum(ctx, CKSUMTYPE_RSA_MD5, NULL, 0,
cb4cef
-                                      &tmp, &sum);
cb4cef
+        if (kr_use_fips(ctx)) {
cb4cef
+            /* Skip encryption here.  Taint so that we won't pass it out of
cb4cef
+             * the machine by accident. */
cb4cef
+            *is_fips = TRUE;
cb4cef
+            sum.contents = calloc(1, BLOCKSIZE);
cb4cef
+        } else {
cb4cef
+            retval = krb5_c_make_checksum(ctx, CKSUMTYPE_RSA_MD5, NULL, 0,
cb4cef
+                                          &tmp, &sum);
cb4cef
+        }
cb4cef
         if (retval != 0) {
cb4cef
             zap(tmp.data, tmp.length);
cb4cef
             zap(outbuf, in->length);
cb4cef
@@ -248,7 +267,7 @@ krb5_error_code
cb4cef
 kr_attr_encode(krb5_context ctx, const char *secret,
cb4cef
                const unsigned char *auth, krad_attr type,
cb4cef
                const krb5_data *in, unsigned char outbuf[MAX_ATTRSIZE],
cb4cef
-               size_t *outlen)
cb4cef
+               size_t *outlen, krb5_boolean *is_fips)
cb4cef
 {
cb4cef
     krb5_error_code retval;
cb4cef
 
cb4cef
@@ -265,7 +284,8 @@ kr_attr_encode(krb5_context ctx, const char *secret,
cb4cef
         return 0;
cb4cef
     }
cb4cef
 
cb4cef
-    return attributes[type - 1].encode(ctx, secret, auth, in, outbuf, outlen);
cb4cef
+    return attributes[type - 1].encode(ctx, secret, auth, in, outbuf, outlen,
cb4cef
+                                       is_fips);
cb4cef
 }
cb4cef
 
cb4cef
 krb5_error_code
cb4cef
@@ -274,6 +294,7 @@ kr_attr_decode(krb5_context ctx, const char *secret, const unsigned char *auth,
cb4cef
                unsigned char outbuf[MAX_ATTRSIZE], size_t *outlen)
cb4cef
 {
cb4cef
     krb5_error_code retval;
cb4cef
+    krb5_boolean ignored;
cb4cef
 
cb4cef
     retval = kr_attr_valid(type, in);
cb4cef
     if (retval != 0)
cb4cef
@@ -288,7 +309,8 @@ kr_attr_decode(krb5_context ctx, const char *secret, const unsigned char *auth,
cb4cef
         return 0;
cb4cef
     }
cb4cef
 
cb4cef
-    return attributes[type - 1].decode(ctx, secret, auth, in, outbuf, outlen);
cb4cef
+    return attributes[type - 1].decode(ctx, secret, auth, in, outbuf, outlen,
cb4cef
+                                       &ignored);
cb4cef
 }
cb4cef
 
cb4cef
 krad_attr
cb4cef
diff --git a/src/lib/krad/attrset.c b/src/lib/krad/attrset.c
cb4cef
index 03c613716..d89982a13 100644
cb4cef
--- a/src/lib/krad/attrset.c
cb4cef
+++ b/src/lib/krad/attrset.c
cb4cef
@@ -167,7 +167,8 @@ krad_attrset_copy(const krad_attrset *set, krad_attrset **copy)
cb4cef
 krb5_error_code
cb4cef
 kr_attrset_encode(const krad_attrset *set, const char *secret,
cb4cef
                   const unsigned char *auth,
cb4cef
-                  unsigned char outbuf[MAX_ATTRSETSIZE], size_t *outlen)
cb4cef
+                  unsigned char outbuf[MAX_ATTRSETSIZE], size_t *outlen,
cb4cef
+                  krb5_boolean *is_fips)
cb4cef
 {
cb4cef
     unsigned char buffer[MAX_ATTRSIZE];
cb4cef
     krb5_error_code retval;
cb4cef
@@ -181,7 +182,7 @@ kr_attrset_encode(const krad_attrset *set, const char *secret,
cb4cef
 
cb4cef
     K5_TAILQ_FOREACH(a, &set->list, list) {
cb4cef
         retval = kr_attr_encode(set->ctx, secret, auth, a->type, &a->attr,
cb4cef
-                                buffer, &attrlen);
cb4cef
+                                buffer, &attrlen, is_fips);
cb4cef
         if (retval != 0)
cb4cef
             return retval;
cb4cef
 
cb4cef
diff --git a/src/lib/krad/internal.h b/src/lib/krad/internal.h
cb4cef
index 0143d155a..223ffd730 100644
cb4cef
--- a/src/lib/krad/internal.h
cb4cef
+++ b/src/lib/krad/internal.h
cb4cef
@@ -39,6 +39,8 @@
cb4cef
 #include <sys/socket.h>
cb4cef
 #include <netdb.h>
cb4cef
 
cb4cef
+#include <openssl/crypto.h>
cb4cef
+
cb4cef
 #ifndef UCHAR_MAX
cb4cef
 #define UCHAR_MAX 255
cb4cef
 #endif
cb4cef
@@ -49,6 +51,13 @@
cb4cef
 
cb4cef
 typedef struct krad_remote_st krad_remote;
cb4cef
 
cb4cef
+struct krad_packet_st {
cb4cef
+    char buffer[KRAD_PACKET_SIZE_MAX];
cb4cef
+    krad_attrset *attrset;
cb4cef
+    krb5_data pkt;
cb4cef
+    krb5_boolean is_fips;
cb4cef
+};
cb4cef
+
cb4cef
 /* Validate constraints of an attribute. */
cb4cef
 krb5_error_code
cb4cef
 kr_attr_valid(krad_attr type, const krb5_data *data);
cb4cef
@@ -57,7 +66,8 @@ kr_attr_valid(krad_attr type, const krb5_data *data);
cb4cef
 krb5_error_code
cb4cef
 kr_attr_encode(krb5_context ctx, const char *secret, const unsigned char *auth,
cb4cef
                krad_attr type, const krb5_data *in,
cb4cef
-               unsigned char outbuf[MAX_ATTRSIZE], size_t *outlen);
cb4cef
+               unsigned char outbuf[MAX_ATTRSIZE], size_t *outlen,
cb4cef
+               krb5_boolean *is_fips);
cb4cef
 
cb4cef
 /* Decode an attribute. */
cb4cef
 krb5_error_code
cb4cef
@@ -69,7 +79,8 @@ kr_attr_decode(krb5_context ctx, const char *secret, const unsigned char *auth,
cb4cef
 krb5_error_code
cb4cef
 kr_attrset_encode(const krad_attrset *set, const char *secret,
cb4cef
                   const unsigned char *auth,
cb4cef
-                  unsigned char outbuf[MAX_ATTRSETSIZE], size_t *outlen);
cb4cef
+                  unsigned char outbuf[MAX_ATTRSETSIZE], size_t *outlen,
cb4cef
+                  krb5_boolean *is_fips);
cb4cef
 
cb4cef
 /* Decode attributes from a buffer. */
cb4cef
 krb5_error_code
cb4cef
@@ -152,4 +163,17 @@ gai_error_code(int err)
cb4cef
     }
cb4cef
 }
cb4cef
 
cb4cef
+static inline krb5_boolean
cb4cef
+kr_use_fips(krb5_context ctx)
cb4cef
+{
cb4cef
+    int val = 0;
cb4cef
+
cb4cef
+    if (!FIPS_mode())
cb4cef
+        return 0;
cb4cef
+
cb4cef
+    profile_get_boolean(ctx->profile, "libdefaults",
cb4cef
+                        "radius_md5_fips_override", NULL, 0, &val;;
cb4cef
+    return !val;
cb4cef
+}
cb4cef
+
cb4cef
 #endif /* INTERNAL_H_ */
cb4cef
diff --git a/src/lib/krad/packet.c b/src/lib/krad/packet.c
cb4cef
index c597174b6..fc2d24800 100644
cb4cef
--- a/src/lib/krad/packet.c
cb4cef
+++ b/src/lib/krad/packet.c
cb4cef
@@ -53,12 +53,6 @@ typedef unsigned char uchar;
cb4cef
 #define pkt_auth(p) ((uchar *)offset(&(p)->pkt, OFFSET_AUTH))
cb4cef
 #define pkt_attr(p) ((unsigned char *)offset(&(p)->pkt, OFFSET_ATTR))
cb4cef
 
cb4cef
-struct krad_packet_st {
cb4cef
-    char buffer[KRAD_PACKET_SIZE_MAX];
cb4cef
-    krad_attrset *attrset;
cb4cef
-    krb5_data pkt;
cb4cef
-};
cb4cef
-
cb4cef
 typedef struct {
cb4cef
     uchar x[(UCHAR_MAX + 1) / 8];
cb4cef
 } idmap;
cb4cef
@@ -187,8 +181,14 @@ auth_generate_response(krb5_context ctx, const char *secret,
cb4cef
     memcpy(data.data + response->pkt.length, secret, strlen(secret));
cb4cef
 
cb4cef
     /* Hash it. */
cb4cef
-    retval = krb5_c_make_checksum(ctx, CKSUMTYPE_RSA_MD5, NULL, 0, &data,
cb4cef
-                                  &hash);
cb4cef
+    if (kr_use_fips(ctx)) {
cb4cef
+        /* This checksum does very little security-wise anyway, so don't
cb4cef
+         * taint. */
cb4cef
+        hash.contents = calloc(1, AUTH_FIELD_SIZE);
cb4cef
+    } else {
cb4cef
+        retval = krb5_c_make_checksum(ctx, CKSUMTYPE_RSA_MD5, NULL, 0, &data,
cb4cef
+                                      &hash);
cb4cef
+    }
cb4cef
     free(data.data);
cb4cef
     if (retval != 0)
cb4cef
         return retval;
cb4cef
@@ -276,7 +276,7 @@ krad_packet_new_request(krb5_context ctx, const char *secret, krad_code code,
cb4cef
 
cb4cef
     /* Encode the attributes. */
cb4cef
     retval = kr_attrset_encode(set, secret, pkt_auth(pkt), pkt_attr(pkt),
cb4cef
-                               &attrset_len);
cb4cef
+                               &attrset_len, &pkt->is_fips);
cb4cef
     if (retval != 0)
cb4cef
         goto error;
cb4cef
 
cb4cef
@@ -314,7 +314,7 @@ krad_packet_new_response(krb5_context ctx, const char *secret, krad_code code,
cb4cef
 
cb4cef
     /* Encode the attributes. */
cb4cef
     retval = kr_attrset_encode(set, secret, pkt_auth(request), pkt_attr(pkt),
cb4cef
-                               &attrset_len);
cb4cef
+                               &attrset_len, &pkt->is_fips);
cb4cef
     if (retval != 0)
cb4cef
         goto error;
cb4cef
 
cb4cef
@@ -451,6 +451,8 @@ krad_packet_decode_response(krb5_context ctx, const char *secret,
cb4cef
 const krb5_data *
cb4cef
 krad_packet_encode(const krad_packet *pkt)
cb4cef
 {
cb4cef
+    if (pkt->is_fips)
cb4cef
+        return NULL;
cb4cef
     return &pkt->pkt;
cb4cef
 }
cb4cef
 
cb4cef
diff --git a/src/lib/krad/remote.c b/src/lib/krad/remote.c
cb4cef
index c96a9b4ee..eca432424 100644
cb4cef
--- a/src/lib/krad/remote.c
cb4cef
+++ b/src/lib/krad/remote.c
cb4cef
@@ -263,7 +263,7 @@ on_io_write(krad_remote *rr)
cb4cef
     request *r;
cb4cef
 
cb4cef
     K5_TAILQ_FOREACH(r, &rr->list, list) {
cb4cef
-        tmp = krad_packet_encode(r->request);
cb4cef
+        tmp = &r->request->pkt;
cb4cef
 
cb4cef
         /* If the packet has already been sent, do nothing. */
cb4cef
         if (r->sent == tmp->length)
cb4cef
@@ -359,7 +359,7 @@ on_io_read(krad_remote *rr)
cb4cef
     if (req != NULL) {
cb4cef
         K5_TAILQ_FOREACH(r, &rr->list, list) {
cb4cef
             if (r->request == req &&
cb4cef
-                r->sent == krad_packet_encode(req)->length) {
cb4cef
+                r->sent == req->pkt.length) {
cb4cef
                 request_finish(r, 0, rsp);
cb4cef
                 break;
cb4cef
             }
cb4cef
@@ -455,6 +455,12 @@ kr_remote_send(krad_remote *rr, krad_code code, krad_attrset *attrs,
cb4cef
                                      (krad_packet_iter_cb)iterator, &r, &tmp);
cb4cef
     if (retval != 0)
cb4cef
         goto error;
cb4cef
+    else if (tmp->is_fips && rr->info->ai_family != AF_LOCAL &&
cb4cef
+        rr->info->ai_family != AF_UNIX) {
cb4cef
+        /* This would expose cleartext passwords, so abort. */
cb4cef
+        retval = ESOCKTNOSUPPORT;
cb4cef
+        goto error;
cb4cef
+    }
cb4cef
 
cb4cef
     K5_TAILQ_FOREACH(r, &rr->list, list) {
cb4cef
         if (r->request == tmp) {
cb4cef
diff --git a/src/lib/krad/t_attr.c b/src/lib/krad/t_attr.c
cb4cef
index eb2a780c8..4d285ad9d 100644
cb4cef
--- a/src/lib/krad/t_attr.c
cb4cef
+++ b/src/lib/krad/t_attr.c
cb4cef
@@ -50,6 +50,7 @@ main()
cb4cef
     const char *tmp;
cb4cef
     krb5_data in;
cb4cef
     size_t len;
cb4cef
+    krb5_boolean is_fips = FALSE;
cb4cef
 
cb4cef
     noerror(krb5_init_context(&ctx));
cb4cef
 
cb4cef
@@ -73,7 +74,7 @@ main()
cb4cef
     in = string2data((char *)decoded);
cb4cef
     retval = kr_attr_encode(ctx, secret, auth,
cb4cef
                             krad_attr_name2num("User-Password"),
cb4cef
-                            &in, outbuf, &len;;
cb4cef
+                            &in, outbuf, &len, &is_fips);
cb4cef
     insist(retval == 0);
cb4cef
     insist(len == sizeof(encoded));
cb4cef
     insist(memcmp(outbuf, encoded, len) == 0);
cb4cef
diff --git a/src/lib/krad/t_attrset.c b/src/lib/krad/t_attrset.c
cb4cef
index 7928335ca..0f9576253 100644
cb4cef
--- a/src/lib/krad/t_attrset.c
cb4cef
+++ b/src/lib/krad/t_attrset.c
cb4cef
@@ -49,6 +49,7 @@ main()
cb4cef
     krb5_context ctx;
cb4cef
     size_t len = 0, encode_len;
cb4cef
     krb5_data tmp;
cb4cef
+    krb5_boolean is_fips = FALSE;
cb4cef
 
cb4cef
     noerror(krb5_init_context(&ctx));
cb4cef
     noerror(krad_attrset_new(ctx, &set);;
cb4cef
@@ -62,7 +63,8 @@ main()
cb4cef
     noerror(krad_attrset_add(set, krad_attr_name2num("User-Password"), &tmp));
cb4cef
 
cb4cef
     /* Encode attrset. */
cb4cef
-    noerror(kr_attrset_encode(set, "foo", auth, buffer, &encode_len));
cb4cef
+    noerror(kr_attrset_encode(set, "foo", auth, buffer, &encode_len,
cb4cef
+                              &is_fips));
cb4cef
     krad_attrset_free(set);
cb4cef
 
cb4cef
     /* Manually encode User-Name. */
cb4cef
diff --git a/src/plugins/preauth/spake/spake_client.c b/src/plugins/preauth/spake/spake_client.c
cb4cef
index 00734a13b..a3ce22b70 100644
cb4cef
--- a/src/plugins/preauth/spake/spake_client.c
cb4cef
+++ b/src/plugins/preauth/spake/spake_client.c
cb4cef
@@ -38,6 +38,8 @@
cb4cef
 #include "groups.h"
cb4cef
 #include <krb5/clpreauth_plugin.h>
cb4cef
 
cb4cef
+#include <openssl/crypto.h>
cb4cef
+
cb4cef
 typedef struct reqstate_st {
cb4cef
     krb5_pa_spake *msg;         /* set in prep_questions, used in process */
cb4cef
     krb5_keyblock *initial_key;
cb4cef
@@ -375,6 +377,10 @@ clpreauth_spake_initvt(krb5_context context, int maj_ver, int min_ver,
cb4cef
 
cb4cef
     if (maj_ver != 1)
cb4cef
         return KRB5_PLUGIN_VER_NOTSUPP;
cb4cef
+
cb4cef
+    if (FIPS_mode())
cb4cef
+        return KRB5_CRYPTO_INTERNAL;
cb4cef
+
cb4cef
     vt = (krb5_clpreauth_vtable)vtable;
cb4cef
     vt->name = "spake";
cb4cef
     vt->pa_type_list = pa_types;
cb4cef
diff --git a/src/plugins/preauth/spake/spake_kdc.c b/src/plugins/preauth/spake/spake_kdc.c
cb4cef
index 88c964ce1..c7df0392f 100644
cb4cef
--- a/src/plugins/preauth/spake/spake_kdc.c
cb4cef
+++ b/src/plugins/preauth/spake/spake_kdc.c
cb4cef
@@ -41,6 +41,8 @@
cb4cef
 
cb4cef
 #include <krb5/kdcpreauth_plugin.h>
cb4cef
 
cb4cef
+#include <openssl/crypto.h>
cb4cef
+
cb4cef
 /*
cb4cef
  * The SPAKE kdcpreauth module uses a secure cookie containing the following
cb4cef
  * concatenated fields (all integer fields are big-endian):
cb4cef
@@ -571,6 +573,10 @@ kdcpreauth_spake_initvt(krb5_context context, int maj_ver, int min_ver,
cb4cef
 
cb4cef
     if (maj_ver != 1)
cb4cef
         return KRB5_PLUGIN_VER_NOTSUPP;
cb4cef
+
cb4cef
+    if (FIPS_mode())
cb4cef
+        return KRB5_CRYPTO_INTERNAL;
cb4cef
+
cb4cef
     vt = (krb5_kdcpreauth_vtable)vtable;
cb4cef
     vt->name = "spake";
cb4cef
     vt->pa_type_list = pa_types;