haproxy/src/fips.c
William Lallemand 035e4000c8 MEDIUM: ssl: add FIPS elliptic curve check for AWS-LC
Add ssl_fips_check_curves() which validates the configured curve list
against the FIPS-approved NIST P-curves (P-256, P-384, P-521).  Each
colon-separated name is resolved to a NID via OBJ_txt2nid() so all
standard aliases (P-256, prime256v1, secp256r1) are handled uniformly.
A NULL list is silently accepted since the global defaults were already
overwritten with FIPS values at init time.

The check is called right after SSL_CTX_set1_curves_list() in both the
bind (ssl_sock_prepare_ctx) and server (ssl_sock_prepare_srv_ssl_ctx)
configuration paths.
2026-07-02 15:51:59 +00:00

235 lines
6.7 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later */
/* FIPS compliance checks for AWS-LC builds */
#include <stdlib.h>
#include <openssl/ec.h>
#include <haproxy/errors.h>
#include <haproxy/obj_type.h>
#include <haproxy/openssl-compat.h>
#include <haproxy/ssl_sock-t.h>
#include <haproxy/tools.h>
/* FIPS-approved bulk cipher NIDs (TLS 1.2). NID_undef terminates the list.
* DHE and CCM entries are FIPS-approved but not implemented by AWS-LC. */
static const int fips_approved_cipher_nids[] = {
NID_aes_128_gcm,
NID_aes_256_gcm,
NID_aes_128_ccm,
NID_aes_256_ccm,
NID_undef
};
/* FIPS-approved elliptic curve NIDs (NIST P-curves). NID_undef terminates the list. */
static const int fips_approved_curve_nids[] = {
NID_X9_62_prime256v1, /* P-256 */
NID_secp384r1, /* P-384 */
NID_secp521r1, /* P-521 */
NID_undef
};
/* FIPS-approved TLS 1.3 ciphersuite names. NULL terminates the list.
* TLS_AES_128_CCM_SHA256 is FIPS-approved but not implemented by AWS-LC. */
static const char *fips_approved_ciphersuites[] = {
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_AES_128_CCM_SHA256",
NULL
};
/* Fill display fields from <obj> for use in error messages. */
static void fips_obj_info(const enum obj_type *obj,
const char **proxy_name, const char **type_str,
const char **obj_name,
const char **file, int *line)
{
switch (obj_type(obj)) {
case OBJ_TYPE_SERVER: {
struct server *s = objt_server((enum obj_type *)obj);
*proxy_name = s->proxy->id ? s->proxy->id : "-";
*type_str = "server";
*obj_name = s->id ? s->id : "-";
*file = s->conf.file;
*line = s->conf.line;
break;
}
case OBJ_TYPE_LISTENER: {
struct listener *li = objt_listener((enum obj_type *)obj);
*proxy_name = li->bind_conf->frontend->id ? li->bind_conf->frontend->id : "-";
*type_str = "bind";
*obj_name = li->bind_conf->arg ? li->bind_conf->arg : "-";
*file = li->bind_conf->file;
*line = li->bind_conf->line;
break;
}
default:
*proxy_name = *type_str = *obj_name = *file = NULL;
*line = 0;
break;
}
}
/* Check that the TLS 1.3 ciphersuite list <ciphersuites> is FIPS-compliant. */
int ssl_fips_check_ciphersuites(const char *ciphersuites, const enum obj_type *obj)
{
const char *proxy_name, *type_str, *obj_name, *file;
const char *p, *end;
char *list = NULL;
int i, line;
size_t len;
if (!FIPS_mode() || !ciphersuites)
return 0;
p = ciphersuites;
while (p && *p) {
end = strchr(p, ':');
len = end ? (size_t)(end - p) : strlen(p);
for (i = 0; fips_approved_ciphersuites[i]; i++) {
if (strlen(fips_approved_ciphersuites[i]) == len &&
strncmp(p, fips_approved_ciphersuites[i], len) == 0)
goto next;
}
memprintf(&list, "%s%s'%.*s'", list ? list : "",
list ? ", " : "", (int)len, p);
next:
p = end ? end + 1 : NULL;
}
if (list) {
fips_obj_info(obj, &proxy_name, &type_str, &obj_name, &file, &line);
if (file)
ha_alert("[%s:%d] %s '%s/%s': FIPS mode active but non-FIPS ciphersuite(s) configured: %s.\n",
file, line, type_str, proxy_name, obj_name, list);
else
ha_alert("%s '%s/%s': FIPS mode active but non-FIPS ciphersuite(s) configured: %s.\n",
type_str, proxy_name, obj_name, list);
free(list);
return ERR_ALERT | ERR_ABORT | ERR_FATAL;
}
return 0;
}
/* Check that the elliptic curve list <curves> is FIPS-compliant. */
int ssl_fips_check_curves(const char *curves, const enum obj_type *obj)
{
const char *proxy_name, *type_str, *obj_name, *file;
const char *p, *end;
char *list = NULL;
char name[64];
int i, nid, line;
size_t len;
if (!FIPS_mode() || !curves)
return 0;
p = curves;
while (p && *p) {
end = strchr(p, ':');
len = end ? (size_t)(end - p) : strlen(p);
if (len < sizeof(name)) {
memcpy(name, p, len);
name[len] = '\0';
nid = OBJ_txt2nid(name);
if (nid == NID_undef)
nid = EC_curve_nist2nid(name);
for (i = 0; fips_approved_curve_nids[i] != NID_undef; i++) {
if (nid == fips_approved_curve_nids[i])
goto next;
}
}
memprintf(&list, "%s%s'%.*s'", list ? list : "",
list ? ", " : "", (int)len, p);
next:
p = end ? end + 1 : NULL;
}
if (list) {
fips_obj_info(obj, &proxy_name, &type_str, &obj_name, &file, &line);
if (file)
ha_alert("[%s:%d] %s '%s/%s': FIPS mode active but non-FIPS curve(s) configured: %s.\n",
file, line, type_str, proxy_name, obj_name, list);
else
ha_alert("%s '%s/%s': FIPS mode active but non-FIPS curve(s) configured: %s.\n",
type_str, proxy_name, obj_name, list);
free(list);
return ERR_ALERT | ERR_ABORT | ERR_FATAL;
}
return 0;
}
/* Check that the TLS 1.2 cipher list configured on <ctx> is FIPS-compliant. */
int ssl_fips_check_ciphers(SSL_CTX *ctx, const enum obj_type *obj)
{
const char *proxy_name, *type_str, *obj_name, *file;
STACK_OF(SSL_CIPHER) *cipher_list;
const SSL_CIPHER *cipher;
int i, j, cipher_nid, kx_nid, line;
char *list = NULL;
if (!FIPS_mode())
return 0;
cipher_list = SSL_CTX_get_ciphers(ctx);
if (!cipher_list)
return 0;
for (i = 0; i < sk_SSL_CIPHER_num(cipher_list); i++) {
cipher = sk_SSL_CIPHER_value(cipher_list, i);
cipher_nid = SSL_CIPHER_get_cipher_nid(cipher);
kx_nid = SSL_CIPHER_get_kx_nid(cipher);
if (kx_nid == NID_kx_ecdhe
#ifdef NID_kx_dhe
|| kx_nid == NID_kx_dhe
#endif
) {
for (j = 0; fips_approved_cipher_nids[j] != NID_undef; j++) {
if (cipher_nid == fips_approved_cipher_nids[j])
goto next;
}
}
memprintf(&list, "%s%s'%s'", list ? list : "",
list ? ", " : "", SSL_CIPHER_get_name(cipher));
next:;
}
if (list) {
fips_obj_info(obj, &proxy_name, &type_str, &obj_name, &file, &line);
if (file)
ha_alert("[%s:%d] %s '%s/%s': FIPS mode active but non-FIPS cipher(s) configured: %s.\n",
file, line, type_str, proxy_name, obj_name, list);
else
ha_alert("%s '%s/%s': FIPS mode active but non-FIPS cipher(s) configured: %s.\n",
type_str, proxy_name, obj_name, list);
free(list);
return ERR_ALERT | ERR_ABORT | ERR_FATAL;
}
return 0;
}
/* Check that the minimum TLS version <min_ver> is FIPS-compliant. */
int ssl_fips_check_version(int min_ver, const enum obj_type *obj)
{
const char *proxy_name, *type_str, *obj_name, *file;
int line;
if (!FIPS_mode())
return 0;
if (min_ver && min_ver < CONF_TLSV12) {
fips_obj_info(obj, &proxy_name, &type_str, &obj_name, &file, &line);
if (file)
ha_alert("[%s:%d] %s '%s/%s': FIPS mode active but ssl-min-ver is set below TLS 1.2.\n",
file, line, type_str, proxy_name, obj_name);
else
ha_alert("%s '%s/%s': FIPS mode active but ssl-min-ver is set below TLS 1.2.\n",
type_str, proxy_name, obj_name);
return ERR_ALERT | ERR_ABORT | ERR_FATAL;
}
return 0;
}