From 4f8cba1780bc167c52de2a791cad6a1817508bbe Mon Sep 17 00:00:00 2001
From: Julien Rische <jrische@redhat.com>
Date: Wed, 23 Feb 2022 17:34:33 +0100
Subject: [PATCH] [downstream] FIPS with PRNG and RADIUS and MD4

NB: Use openssl's PRNG in FIPS mode and taint within krad.

A lot of the FIPS error conditions from OpenSSL are incredibly
mysterious (at best, things return NULL unexpectedly; at worst,
internal assertions are tripped; most of the time, you just get
ENOMEM).  In order to cope with this, we need to have some level of
awareness of what we can and can't safely call.

This will slow down some calls slightly (FIPS_mode() takes multiple
locks), but not for any ciphers we care about - which is to say that
AES is fine.  Shame about SPAKE though.

post6 restores MD4 (and therefore keygen-only RC4).

post7 restores MD5 and adds radius_md5_fips_override.

post8 restores MD4/MD5 for OpenSSL 3.0

Use OpenSSL 3.0 library context to access MD4 and MD5 lazily from
legacy provider if RC4 encryption type is enabled, without affecting
global context.

Remove EVP_MD_CTX_FLAG_NON_FIPS_ALLOW flag since does not have any
effect anymore.

post9 load both default and legacy provider into library context

Last-updated: krb5-1.19
---
 doc/admin/conf_files/krb5_conf.rst            |  6 ++
 src/lib/crypto/krb/prng.c                     | 11 ++-
 .../crypto/openssl/enc_provider/camellia.c    |  6 ++
 src/lib/crypto/openssl/enc_provider/rc4.c     | 13 ++-
 .../crypto/openssl/hash_provider/hash_evp.c   | 93 ++++++++++++++++++-
 src/lib/crypto/openssl/hmac.c                 |  6 +-
 src/lib/krad/attr.c                           | 46 ++++++---
 src/lib/krad/attrset.c                        |  5 +-
 src/lib/krad/internal.h                       | 28 +++++-
 src/lib/krad/packet.c                         | 22 +++--
 src/lib/krad/remote.c                         | 10 +-
 src/lib/krad/t_attr.c                         |  3 +-
 src/lib/krad/t_attrset.c                      |  4 +-
 src/plugins/preauth/spake/spake_client.c      |  6 ++
 src/plugins/preauth/spake/spake_kdc.c         |  6 ++
 15 files changed, 230 insertions(+), 35 deletions(-)

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

