From 0ec6e5de4a5f1f8174b722044403fa8df090ab00 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Mar 01 2022 10:26:10 +0000 Subject: import freeradius-3.0.21-26.el9 --- diff --git a/SOURCES/freeradius-Backport-OpenSSL3-fixes.patch b/SOURCES/freeradius-Backport-OpenSSL3-fixes.patch new file mode 100644 index 0000000..ac6d783 --- /dev/null +++ b/SOURCES/freeradius-Backport-OpenSSL3-fixes.patch @@ -0,0 +1,10880 @@ +From: Antonio Torres +Date: Tue, 11 Jan 2022 +Subject: [PATCH] Backport OpenSSL3 fixes from 3.0.26 + +Backport TLS and OpenSSL3 fixes from the future 3.0.26 FreeRADIUS release. + +Additionally include checks to avoid segfault when trying to use MD4 algorithm +while having OpenSSL legacy provider disabled. + +Related: rhbz#1978216 +Signed-off-by: Antonio Torres +--- + share/dictionary.freeradius.internal | 50 +- + src/include/build.h | 25 +- + src/include/libradius.h | 23 +- + src/include/listen.h | 24 +- + src/include/md4.h | 49 +- + src/include/md5.h | 29 +- + src/include/openssl3.h | 109 ++ + src/include/tls-h | 32 +- + src/include/token.h | 7 +- + src/lib/hmacmd5.c | 4 +- + src/lib/hmacsha1.c | 5 +- + src/lib/md4.c | 1 + + src/lib/md5.c | 1 + + src/lib/pair.c | 97 +- + src/lib/print.c | 19 +- + src/lib/radius.c | 300 +++- + src/lib/token.c | 24 +- + src/main/cb.c | 118 +- + src/main/map.c | 54 +- + src/main/tls.c | 1819 ++++++++++++++++---- + src/main/tls_listen.c | 171 +- + src/modules/proto_dhcp/rlm_dhcp.c | 2 +- + src/modules/rlm_eap/libeap/eap_tls.c | 178 +- + src/modules/rlm_eap/libeap/eap_tls.h | 10 +- + src/modules/rlm_eap/libeap/mppe_keys.c | 161 +- + src/modules/rlm_eap/radeapclient.c | 8 + + src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c | 51 +- + .../rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c | 60 +- + src/modules/rlm_eap/types/rlm_eap_peap/peap.c | 22 +- + .../rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c | 41 +- + src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h | 190 ++ + src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c | 719 +++++--- + src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h | 16 +- + .../rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c | 508 +++++- + .../rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h | 2 + + .../rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c | 52 +- + .../rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h | 5 + + .../rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c | 40 +- + src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c | 25 +- + src/modules/rlm_exec/rlm_exec.c | 4 +- + src/modules/rlm_expr/rlm_expr.c | 115 ++ + src/modules/rlm_files/rlm_files.c | 2 +- + src/modules/rlm_ldap/ldap.h | 1 + + src/modules/rlm_mschap/rlm_mschap.c | 97 +- + src/modules/rlm_otp/otp_mppe.c | 16 +- + src/modules/rlm_otp/otp_pwe.c | 8 - + src/modules/rlm_otp/otp_radstate.c | 9 +- + src/modules/rlm_rest/rest.c | 107 +- + src/modules/rlm_rest/rest.h | 18 + + src/modules/rlm_rest/rlm_rest.c | 12 + + src/modules/rlm_wimax/milenage.c | 642 +++++++ + src/modules/rlm_wimax/milenage.h | 128 ++ + src/modules/rlm_wimax/rlm_wimax.c | 429 ++++- + src/tests/keywords/md4 | 58 + + 54 files changed, 5583 insertions(+), 1114 deletions(-) + +diff --git a/share/dictionary.freeradius.internal b/share/dictionary.freeradius.internal +index 724e1f7ff6..53dd04ec9a 100644 +--- a/share/dictionary.freeradius.internal ++++ b/share/dictionary.freeradius.internal +@@ -254,6 +254,7 @@ ATTRIBUTE FreeRADIUS-Response-Delay-USec 1155 integer + + ATTRIBUTE REST-HTTP-Header 1160 string + ATTRIBUTE REST-HTTP-Body 1161 string ++ATTRIBUTE REST-HTTP-Status-Code 1162 integer + + ATTRIBUTE Cache-Expires 1170 date + ATTRIBUTE Cache-Created 1171 date +@@ -277,7 +278,24 @@ ATTRIBUTE SSHA2-256-Password 1178 octets + ATTRIBUTE SSHA2-384-Password 1179 octets + ATTRIBUTE SSHA2-512-Password 1180 octets + ++ATTRIBUTE PBKDF2-Password 1181 octets ++ATTRIBUTE SSHA3-224-Password 1182 octets ++ATTRIBUTE SSHA3-256-Password 1183 octets ++ATTRIBUTE SSHA3-384-Password 1184 octets ++ATTRIBUTE SSHA3-512-Password 1185 octets ++ + ATTRIBUTE MS-CHAP-Peer-Challenge 1192 octets ++ATTRIBUTE Home-Server-Name 1193 string ++ATTRIBUTE Originating-Realm-Key 1194 string ++ATTRIBUTE Proxy-To-Originating-Realm 1195 string ++ ++ATTRIBUTE TOTP-Secret 1194 string # base32 encoded ++ATTRIBUTE TOTP-Key 1195 octets # raw key ++ATTRIBUTE TOTP-Password 1196 string ++ ++ATTRIBUTE Proxy-Tunneled-Request-As-EAP 1197 integer ++VALUE Proxy-Tunneled-Request-As-EAP No 0 ++VALUE Proxy-Tunneled-Request-As-EAP Yes 1 + + # + # Range: 1200-1279 +@@ -318,6 +336,10 @@ ATTRIBUTE EAP-Sim-Algo-Version 1216 integer + ATTRIBUTE Outer-Realm-Name 1218 string + ATTRIBUTE Inner-Realm-Name 1219 string + ++ATTRIBUTE EAP-Pwd-Password-Hash 1220 octets ++ATTRIBUTE EAP-Pwd-Password-Salt 1221 octets ++ATTRIBUTE EAP-Pwd-Password-Prep 1222 byte ++ + # + # Range: 1280 - 1535 + # EAP-type specific attributes +@@ -516,6 +538,11 @@ ATTRIBUTE Tmp-Cast-IPv4Prefix 1870 ipv4prefix + # these attributes. + # + ATTRIBUTE WiMAX-MN-NAI 1900 string ++ATTRIBUTE WiMAX-SIM-Ki 1901 octets ++ATTRIBUTE WiMAX-SIM-OPc 1902 octets ++ATTRIBUTE WiMAX-SIM-AMF 1903 octets ++ATTRIBUTE WiMAX-SIM-SQN 1904 octets ++ATTRIBUTE WiMAX-SIM-RAND 1905 octets + + ATTRIBUTE TLS-Cert-Serial 1910 string + ATTRIBUTE TLS-Cert-Expiration 1911 string +@@ -526,7 +553,7 @@ ATTRIBUTE TLS-Cert-Subject-Alt-Name-Email 1915 string + ATTRIBUTE TLS-Cert-Subject-Alt-Name-Dns 1916 string + ATTRIBUTE TLS-Cert-Subject-Alt-Name-Upn 1917 string + ATTRIBUTE TLS-Cert-Valid-Since 1918 string +-# 1919: reserved for future cert attribute ++ATTRIBUTE TLS-Session-Information 1919 string + ATTRIBUTE TLS-Client-Cert-Serial 1920 string + ATTRIBUTE TLS-Client-Cert-Expiration 1921 string + ATTRIBUTE TLS-Client-Cert-Issuer 1922 string +@@ -543,10 +570,19 @@ ATTRIBUTE TLS-Client-Cert-Subject-Alt-Name-Upn 1932 string + ATTRIBUTE TLS-PSK-Identity 1933 string + ATTRIBUTE TLS-Client-Cert-X509v3-Extended-Key-Usage-OID 1936 string + ATTRIBUTE TLS-Client-Cert-Valid-Since 1937 string ++ATTRIBUTE TLS-Cache-Method 1938 integer ++VALUE TLS-Cache-Method save 1 ++VALUE TLS-Cache-Method load 2 ++VALUE TLS-Cache-Method clear 3 ++VALUE TLS-Cache-Method refresh 4 + +-# 1938 - 1939: reserved for future cert attributes + +-# 1940 - 1949: reserved for TLS session caching, mostly in 4.0 ++# 1939: reserved for future cert attributes ++ ++# 1940 - 1959: reserved for TLS session caching, mostly in 4.0 ++ ++ATTRIBUTE TLS-Session-ID 1940 octets ++ATTRIBUTE TLS-Session-Data 1942 octets + + # Set by EAP-TLS code + ATTRIBUTE TLS-OCSP-Cert-Valid 1943 integer +@@ -560,8 +596,13 @@ ATTRIBUTE TLS-Cache-Filename 1946 string + ATTRIBUTE TLS-Session-Version 1947 string + ATTRIBUTE TLS-Session-Cipher-Suite 1948 string + ++ATTRIBUTE TLS-Session-Cert-File 1949 string ++ATTRIBUTE TLS-Session-Cert-Private-Key-File 1950 string ++ ++ATTRIBUTE TLS-Server-Name-Indication 1951 string ++ + # +-# Range: 1950-2099 ++# Range: 1960-2099 + # Free + # + # Range: 2100-2199 +@@ -650,6 +691,7 @@ VALUE Session-Type Local 1 + VALUE Post-Auth-Type Local 1 + VALUE Post-Auth-Type Reject 2 + VALUE Post-Auth-Type Challenge 3 ++VALUE Post-Auth-Type Client-Lost 4 + + # + # And Post-Proxy +diff --git a/src/include/build.h b/src/include/build.h +index 5da940c2b7..e1c2a1c79b 100644 +--- a/src/include/build.h ++++ b/src/include/build.h +@@ -46,9 +46,13 @@ extern "C" { + * compiler. + */ + #ifdef __GNUC__ +-# define CC_HINT(_x) __attribute__ ((_x)) ++# define CC_HINT(...) __attribute__ ((__VA_ARGS__)) ++# define likely(_x) __builtin_expect((_x), 1) ++# define unlikely(_x) __builtin_expect((_x), 0) + #else +-# define CC_HINT(_x) ++# define CC_HINT(...) ++# define likely(_x) _x ++# define unlikely(_x) _x + #endif + + #ifdef HAVE_ATTRIBUTE_BOUNDED +@@ -57,6 +61,18 @@ extern "C" { + # define CC_BOUNDED(...) + #endif + ++/* ++ * GCC uses __SANITIZE_ADDRESS__, clang uses __has_feature, which ++ * GCC complains about. ++ */ ++#ifndef __SANITIZE_ADDRESS__ ++#ifdef __has_feature ++#if __has_feature(address_sanitizer) ++#define __SANITIZE_ADDRESS__ (1) ++#endif ++#endif ++#endif ++ + /* + * Macros to add pragmas + */ +@@ -137,6 +153,11 @@ extern "C" { + # endif + #endif + ++#define PRINTF_LIKE(n) CC_HINT(format(printf, n, n+1)) ++#define NEVER_RETURNS CC_HINT(noreturn) ++#define UNUSED CC_HINT(unused) ++#define BLANK_FORMAT " " /* GCC_LINT whines about empty formats */ ++ + #ifdef __cplusplus + } + #endif +diff --git a/src/include/libradius.h b/src/include/libradius.h +index ce2f713de1..757828f070 100644 +--- a/src/include/libradius.h ++++ b/src/include/libradius.h +@@ -57,7 +57,13 @@ RCSIDH(libradius_h, "$Id$") + * Talloc memory allocation is used in preference to malloc throughout + * the libraries and server. + */ ++#ifdef HAVE_WDOCUMENTATION ++DIAG_OFF(documentation) ++#endif + #include ++#ifdef HAVE_WDOCUMENTATION ++DIAG_ON(documentation) ++#endif + + /* + * Defines signatures for any missing functions. +@@ -162,11 +168,6 @@ typedef void (*sig_t)(int); + + #define PAD(_x, _y) (_y - ((_x) % _y)) + +-#define PRINTF_LIKE(n) CC_HINT(format(printf, n, n+1)) +-#define NEVER_RETURNS CC_HINT(noreturn) +-#define UNUSED CC_HINT(unused) +-#define BLANK_FORMAT " " /* GCC_LINT whines about empty formats */ +- + typedef struct attr_flags { + unsigned int is_unknown : 1; //!< Attribute number or vendor is unknown. + unsigned int is_tlv : 1; //!< Is a sub attribute. +@@ -189,6 +190,10 @@ typedef struct attr_flags { + + unsigned int compare : 1; //!< has a paircompare registered + ++ unsigned int is_dup : 1; //!< is a duplicate of another attribute ++ ++ unsigned int secret : 1; //!< is a secret thingy ++ + uint8_t encrypt; //!< Ecryption method. + uint8_t length; + } ATTR_FLAGS; +@@ -443,7 +448,7 @@ size_t vp_prints_value(char *out, size_t outlen, VALUE_PAIR const *vp, char q + + char *vp_aprints_value(TALLOC_CTX *ctx, VALUE_PAIR const *vp, char quote); + +-size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp); ++size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp, bool raw_value); + size_t vp_prints(char *out, size_t outlen, VALUE_PAIR const *vp); + void vp_print(FILE *, VALUE_PAIR const *); + void vp_printlist(FILE *, VALUE_PAIR const *); +@@ -472,6 +477,8 @@ int dict_addvalue(char const *namestr, char const *attrstr, int value); + int dict_init(char const *dir, char const *fn); + void dict_free(void); + int dict_read(char const *dir, char const *filename); ++size_t dict_print_oid(char *buffer, size_t buflen, DICT_ATTR const *da); ++int dict_walk(fr_hash_table_walk_t callback, void *context); + + void dict_attr_free(DICT_ATTR const **da); + int dict_unknown_from_fields(DICT_ATTR *da, unsigned int attr, unsigned int vendor); +@@ -586,6 +593,7 @@ int rad_vp2attr(RADIUS_PACKET const *packet, + VALUE_PAIR const **pvp, uint8_t *ptr, size_t room); + + /* pair.c */ ++VALUE_PAIR *fr_pair_alloc(TALLOC_CTX *ctx); + VALUE_PAIR *fr_pair_afrom_da(TALLOC_CTX *ctx, DICT_ATTR const *da); + VALUE_PAIR *fr_pair_afrom_num(TALLOC_CTX *ctx, unsigned int attr, unsigned int vendor); + int fr_pair_to_unknown(VALUE_PAIR *vp); +@@ -611,6 +619,7 @@ VALUE_PAIR *fr_cursor_remove(vp_cursor_t *cursor); + VALUE_PAIR *fr_cursor_replace(vp_cursor_t *cursor, VALUE_PAIR *new); + void fr_pair_delete_by_num(VALUE_PAIR **, unsigned int attr, unsigned int vendor, int8_t tag); + void fr_pair_add(VALUE_PAIR **, VALUE_PAIR *); ++void fr_pair_prepend(VALUE_PAIR **, VALUE_PAIR *); + void fr_pair_replace(VALUE_PAIR **first, VALUE_PAIR *add); + int fr_pair_cmp(VALUE_PAIR *a, VALUE_PAIR *b); + int fr_pair_list_cmp(VALUE_PAIR *a, VALUE_PAIR *b); +@@ -632,7 +641,7 @@ void fr_pair_value_strsteal(VALUE_PAIR *vp, char const *src); + void fr_pair_value_strcpy(VALUE_PAIR *vp, char const * src); + void fr_pair_value_bstrncpy(VALUE_PAIR *vp, void const * src, size_t len); + void fr_pair_value_sprintf(VALUE_PAIR *vp, char const * fmt, ...) CC_HINT(format (printf, 2, 3)); +-void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from); ++void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, FR_TOKEN op); + void fr_pair_list_move_by_num(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, + unsigned int attr, unsigned int vendor, int8_t tag); + void fr_pair_list_mcopy_by_num(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, +diff --git a/src/include/listen.h b/src/include/listen.h +index 4f50bbf808..b395aeb046 100644 +--- a/src/include/listen.h ++++ b/src/include/listen.h +@@ -45,6 +45,8 @@ typedef enum RAD_LISTEN_TYPE { + typedef enum RAD_LISTEN_STATUS { + RAD_LISTEN_STATUS_INIT = 0, + RAD_LISTEN_STATUS_KNOWN, ++ RAD_LISTEN_STATUS_PAUSE, ++ RAD_LISTEN_STATUS_RESUME, + RAD_LISTEN_STATUS_FROZEN, + RAD_LISTEN_STATUS_EOL, + RAD_LISTEN_STATUS_REMOVE_NOW +@@ -70,9 +72,10 @@ struct rad_listen { + int status; + #ifdef WITH_TCP + int count; +- bool dual; + rbtree_t *children; + rad_listen_t *parent; ++ ++ bool dual; + #endif + bool nodup; + bool synchronous; +@@ -80,12 +83,25 @@ struct rad_listen { + + #ifdef WITH_TLS + fr_tls_server_conf_t *tls; ++ bool check_client_connections; + #endif + + rad_listen_recv_t recv; + rad_listen_send_t send; ++ ++ /* ++ * We don't need a proxy_recv, because the main loop in ++ * process.c calls listener->recv(), and we don't know ++ * what kind of packet we're receiving until we receive ++ * it. ++ */ ++ rad_listen_send_t proxy_send; ++ ++ + rad_listen_encode_t encode; + rad_listen_decode_t decode; ++ rad_listen_encode_t proxy_encode; ++ rad_listen_decode_t proxy_decode; + rad_listen_print_t print; + + CONF_SECTION const *cs; +@@ -146,6 +162,12 @@ typedef struct listen_socket_t { + pthread_mutex_t mutex; + uint8_t *data; + size_t partial; ++ enum { ++ LISTEN_TLS_INIT = 0, ++ LISTEN_TLS_CHECKING, ++ LISTEN_TLS_SETUP, ++ LISTEN_TLS_RUNNING, ++ } state; + #endif + + RADCLIENT_LIST *clients; +diff --git a/src/include/md4.h b/src/include/md4.h +index b7bdd6a15e..f3801728c8 100644 +--- a/src/include/md4.h ++++ b/src/include/md4.h +@@ -71,14 +71,61 @@ void fr_md4_final(uint8_t out[MD4_DIGEST_LENGTH], FR_MD4_CTX *ctx) + void fr_md4_transform(uint32_t buf[4], uint8_t const inc[MD4_BLOCK_LENGTH]) + CC_BOUNDED(__size__, 1, 4, 4) + CC_BOUNDED(__minbytes__, 2, MD4_BLOCK_LENGTH); ++# define fr_md4_destroy(_x) + #else /* HAVE_OPENSSL_MD4_H */ ++#if OPENSSL_VERSION_NUMBER < 0x30000000L + USES_APPLE_DEPRECATED_API + # define FR_MD4_CTX MD4_CTX + # define fr_md4_init MD4_Init + # define fr_md4_update MD4_Update + # define fr_md4_final MD4_Final + # define fr_md4_transform MD4_Transform +-#endif ++# define fr_md4_destroy(_x) ++#else ++#include ++ ++/* ++ * Wrappers for OpenSSL3, so we don't have to butcher the rest of ++ * the code too much. ++ */ ++typedef struct FR_MD4_CTX { ++ EVP_MD_CTX *ctx; ++ EVP_MD const *md; ++ unsigned int len; ++} FR_MD4_CTX; ++ ++static inline void fr_md4_init(FR_MD4_CTX *ctx) ++{ ++ ctx->ctx = EVP_MD_CTX_new(); ++// ctx->md = EVP_MD_fetch(NULL, "MD4", "provider=legacy"); ++ ctx->md = EVP_md4(); ++ ctx->len = MD4_DIGEST_LENGTH; ++ ++ EVP_MD_CTX_set_flags(ctx->ctx, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); ++ if (EVP_DigestInit_ex(ctx->ctx, ctx->md, NULL) != 1) { ++ fprintf(stderr, "Couldn't init MD4 algorithm. Enable OpenSSL legacy provider.\n"); ++ exit(EXIT_FAILURE); ++ } ++} ++ ++static inline void fr_md4_update(FR_MD4_CTX *ctx, uint8_t const *in, size_t inlen) ++{ ++ EVP_DigestUpdate(ctx->ctx, in, inlen); ++} ++ ++static inline void fr_md4_final(uint8_t out[MD4_DIGEST_LENGTH], FR_MD4_CTX *ctx) ++{ ++ EVP_DigestFinal_ex(ctx->ctx, out, &(ctx->len)); ++} ++ ++static inline void fr_md4_destroy(FR_MD4_CTX *ctx) ++{ ++ EVP_MD_CTX_destroy(ctx->ctx); ++// EVP_MD_free(ctx->md); ++} ++ ++#endif /* OPENSSL3 */ ++#endif /* HAVE_OPENSSL_MD4_H */ + + /* md4.c */ + void fr_md4_calc(uint8_t out[MD4_DIGEST_LENGTH], uint8_t const *in, size_t inlen); +diff --git a/src/include/md5.h b/src/include/md5.h +index a44584564f..64025f4a4d 100644 +--- a/src/include/md5.h ++++ b/src/include/md5.h +@@ -68,14 +68,41 @@ void fr_md5_final(uint8_t out[MD5_DIGEST_LENGTH], FR_MD5_CTX *ctx) + void fr_md5_transform(uint32_t state[4], uint8_t const block[MD5_BLOCK_LENGTH]) + CC_BOUNDED(__size__, 1, 4, 4) + CC_BOUNDED(__minbytes__, 2, MD5_BLOCK_LENGTH); ++# define fr_md5_destroy(_x) ++# define fr_md5_copy(_dst, _src) _dst = _src + #else /* HAVE_OPENSSL_MD5_H */ ++#if OPENSSL_VERSION_NUMBER < 0x30000000L + USES_APPLE_DEPRECATED_API + # define FR_MD5_CTX MD5_CTX + # define fr_md5_init MD5_Init + # define fr_md5_update MD5_Update + # define fr_md5_final MD5_Final + # define fr_md5_transform MD5_Transform +-#endif ++# define fr_md5_copy(_dst, _src) _dst = _src ++# define fr_md5_destroy(_x) ++#else ++#include ++ ++/* ++ * Wrappers for OpenSSL3, so we don't have to butcher the rest of ++ * the code too much. ++ */ ++typedef EVP_MD_CTX* FR_MD5_CTX; ++ ++# define fr_md5_init(_ctx) \ ++ do { \ ++ *_ctx = EVP_MD_CTX_new(); \ ++ EVP_MD_CTX_set_flags(*_ctx, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); \ ++ EVP_DigestInit_ex(*_ctx, EVP_md5(), NULL); \ ++ } while (0) ++# define fr_md5_update(_ctx, _str, _len) \ ++ EVP_DigestUpdate(*_ctx, _str, _len) ++# define fr_md5_final(_out, _ctx) \ ++ EVP_DigestFinal_ex(*_ctx, _out, NULL) ++# define fr_md5_destroy(_ctx) EVP_MD_CTX_destroy(*_ctx) ++# define fr_md5_copy(_dst, _src) EVP_MD_CTX_copy_ex(_dst, _src) ++#endif /* OPENSSL3 */ ++#endif /* HAVE_OPENSSL_MD5_H */ + + /* hmac.c */ + void fr_hmac_md5(uint8_t digest[MD5_DIGEST_LENGTH], uint8_t const *text, size_t text_len, +diff --git a/src/include/openssl3.h b/src/include/openssl3.h +new file mode 100644 +index 0000000000..4423ee538a +--- /dev/null ++++ b/src/include/openssl3.h +@@ -0,0 +1,109 @@ ++/* ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA ++ */ ++#ifndef FR_OPENSSL3_H ++#define FR_OPENSSL3_H ++/** ++ * $Id$ ++ * ++ * @file openssl3.h ++ * @brief Wrappers to shut up OpenSSL3 ++ * ++ * @copyright 2021 Network RADIUS SAS (legal@networkradius.com) ++ */ ++ ++RCSIDH(openssl3_h, "$Id$") ++ ++/* ++ * The HMAC APIs are deprecated in OpenSSL3. We don't want to ++ * fill the code with ifdef's, so we define some horrific ++ * wrappers here. ++ * ++ * This file should be included AFTER all OpenSSL header files. ++ */ ++#ifdef HAVE_OPENSSL_SSL_H ++#if OPENSSL_VERSION_NUMBER >= 0x30000000L ++#include ++#include ++#include ++ ++typedef struct { ++ EVP_MAC *mac; ++ EVP_MAC_CTX *ctx; ++} HMAC3_CTX; ++#define HMAC_CTX HMAC3_CTX ++ ++#define HMAC_CTX_new HMAC3_CTX_new ++static inline HMAC3_CTX *HMAC3_CTX_new(void) ++{ ++ HMAC3_CTX *h = calloc(1, sizeof(*h)); ++ ++ return h; ++} ++ ++#define HMAC_Init_ex(_ctx, _key, _keylen, _md, _engine) HMAC3_Init_ex(_ctx, _key, _keylen, _md, _engine) ++static inline int HMAC3_Init_ex(HMAC3_CTX *ctx, const unsigned char *key, unsigned int keylen, const EVP_MD *md, UNUSED void *engine) ++{ ++ OSSL_PARAM params[2], *p = params; ++ char const *name; ++ char *unconst; ++ ++ ctx->mac = EVP_MAC_fetch(NULL, "HMAC", NULL); ++ if (!ctx->mac) return 0; ++ ++ ctx->ctx = EVP_MAC_CTX_new(ctx->mac); ++ if (!ctx->ctx) return 0; ++ ++ name = EVP_MD_get0_name(md); ++ memcpy(&unconst, &name, sizeof(name)); /* const issues */ ++ ++ p[0] = OSSL_PARAM_construct_utf8_string(OSSL_ALG_PARAM_DIGEST, unconst, 0); ++ p[1] = OSSL_PARAM_construct_end(); ++ ++ return EVP_MAC_init(ctx->ctx, key, keylen, params); ++} ++ ++#define HMAC_Update HMAC3_Update ++static inline int HMAC3_Update(HMAC3_CTX *ctx, const unsigned char *data, unsigned int datalen) ++{ ++ return EVP_MAC_update(ctx->ctx, data, datalen); ++} ++ ++#define HMAC_Final HMAC3_Final ++static inline int HMAC3_Final(HMAC3_CTX *ctx, unsigned char *out, unsigned int *len) ++{ ++ size_t mylen = *len; ++ ++ if (!EVP_MAC_final(ctx->ctx, out, &mylen, mylen)) return 0; ++ ++ *len = mylen; ++ return 1; ++} ++ ++#define HMAC_CTX_free HMAC3_CTX_free ++static inline void HMAC3_CTX_free(HMAC3_CTX *ctx) ++{ ++ if (!ctx) return; ++ ++ EVP_MAC_free(ctx->mac); ++ EVP_MAC_CTX_free(ctx->ctx); ++ free(ctx); ++} ++ ++#define HMAC_CTX_set_flags(_ctx, _flags) ++ ++#endif /* OPENSSL_VERSION_NUMBER */ ++#endif ++#endif /* FR_OPENSSL3_H */ +diff --git a/src/include/tls-h b/src/include/tls-h +index 62f57c4715..67835881a7 100644 +--- a/src/include/tls-h ++++ b/src/include/tls-h +@@ -94,7 +94,7 @@ typedef struct _record_t { + } record_t; + + typedef struct _tls_info_t { +- int origin; ++ int origin; // 0 - received (from peer), 1 - sending (to peer) + int content_type; + uint8_t handshake_type; + uint8_t alert_level; +@@ -139,6 +139,9 @@ typedef struct _tls_session_t { + bool invalid_hb_used; //!< Whether heartbleed attack was detected. + bool connected; //!< whether the outgoing socket is connected + bool is_init_finished; //!< whether or not init is finished ++ bool client_cert_ok; //!< whether or not we validated the client certificate ++ bool authentication_success; //!< whether or not the user was authenticated (cert or PW) ++ bool quick_session_tickets; //!< for EAP-TLS. + + /* + * Framed-MTU attribute in RADIUS, if present, can also be used to set this +@@ -160,8 +163,11 @@ typedef struct _tls_session_t { + void *opaque; + void (*free_opaque)(void *opaque); + +- char const *prf_label; ++ char const *label; + bool allow_session_resumption; //!< Whether session resumption is allowed. ++ bool session_not_resumed; //!< Whether our session was not resumed. ++ ++ fr_tls_server_conf_t const *conf; //! for better complaints + } tls_session_t; + + /* +@@ -309,16 +315,17 @@ int tls_error_io_log(REQUEST *request, tls_session_t *session, int ret, char co + CC_HINT(format (printf, 4, 5)); + + void tls_global_cleanup(void); +-tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQUEST *request, bool client_cert); ++tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQUEST *request, bool client_cert, bool allow_tls13); + tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, int fd, VALUE_PAIR **certs); + fr_tls_server_conf_t *tls_server_conf_parse(CONF_SECTION *cs); + fr_tls_server_conf_t *tls_client_conf_parse(CONF_SECTION *cs); + fr_tls_server_conf_t *tls_server_conf_alloc(TALLOC_CTX *ctx); +-SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client); ++SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client, char const *chain_file, char const *private_key_file); + int tls_handshake_recv(REQUEST *, tls_session_t *ssn); + int tls_handshake_send(REQUEST *, tls_session_t *ssn); + void tls_session_information(tls_session_t *ssn); + void tls_session_id(SSL_SESSION *ssn, char *buffer, size_t bufsize); ++X509_STORE *fr_init_x509_store(fr_tls_server_conf_t *conf); + + /* + * Low-level TLS stuff +@@ -360,6 +367,11 @@ struct fr_tls_server_conf_t { + bool disable_tlsv1; + bool disable_tlsv1_1; + bool disable_tlsv1_2; ++ bool tls13_enable_magic; ++ bool disallow_untrusted; //!< allow untrusted CAs to issue client certificates ++ ++ int min_version; ++ int max_version; + + char const *tls_min_version; + char const *tls_max_version; +@@ -371,16 +383,20 @@ struct fr_tls_server_conf_t { + bool check_crl; + bool check_all_crl; + bool allow_expired_crl; ++ uint32_t ca_path_reload_interval; ++ uint32_t ca_path_last_reload; ++ X509_STORE *old_x509_store; + char const *check_cert_cn; + char const *cipher_list; + bool cipher_server_preference; + char const *check_cert_issuer; + + bool session_cache_enable; +- uint32_t session_timeout; ++ uint32_t session_lifetime; + uint32_t session_cache_size; + char const *session_id_name; + char const *session_cache_path; ++ char const *session_cache_server; + fr_hash_table_t *cache_ht; + char session_context_id[SSL_MAX_SSL_SESSION_ID_LENGTH]; + +@@ -389,6 +405,8 @@ struct fr_tls_server_conf_t { + char const *verify_client_cert_cmd; + bool require_client_cert; + ++ pthread_mutex_t mutex; ++ + #ifdef HAVE_OPENSSL_OCSP_H + /* + * OCSP Configuration +@@ -414,6 +432,10 @@ struct fr_tls_server_conf_t { + char const *psk_query; + #endif + ++ char const *realm_dir; ++ fr_hash_table_t *realms; ++ ++ char const *client_hostname; + }; + + #ifdef __cplusplus +diff --git a/src/include/token.h b/src/include/token.h +index 6cbd05217a..c8bb748702 100644 +--- a/src/include/token.h ++++ b/src/include/token.h +@@ -56,16 +56,17 @@ typedef enum fr_token_t { + T_OP_CMP_TRUE, /* =* 20 */ + T_OP_CMP_FALSE, /* !* */ + T_OP_CMP_EQ, /* == */ ++ T_OP_PREPEND, /* ^= */ + T_HASH, /* # */ +- T_BARE_WORD, /* bare word */ +- T_DOUBLE_QUOTED_STRING, /* "foo" 25 */ ++ T_BARE_WORD, /* bare word 25 */ ++ T_DOUBLE_QUOTED_STRING, /* "foo" */ + T_SINGLE_QUOTED_STRING, /* 'foo' */ + T_BACK_QUOTED_STRING, /* `foo` */ + T_TOKEN_LAST + } FR_TOKEN; + + #define T_EQSTART T_OP_ADD +-#define T_EQEND (T_OP_CMP_EQ + 1) ++#define T_EQEND (T_OP_PREPEND + 1) + + typedef struct FR_NAME_NUMBER { + char const *name; +diff --git a/src/lib/hmacmd5.c b/src/lib/hmacmd5.c +index 1cca00fa2a..88ba171aa0 100644 +--- a/src/lib/hmacmd5.c ++++ b/src/lib/hmacmd5.c +@@ -34,6 +34,7 @@ RCSID("$Id$") + + #include + #include ++#include + + #ifdef HAVE_OPENSSL_EVP_H + /** Calculate HMAC using OpenSSL's MD5 implementation +@@ -49,6 +50,7 @@ void fr_hmac_md5(uint8_t digest[MD5_DIGEST_LENGTH], uint8_t const *text, size_t + uint8_t const *key, size_t key_len) + { + HMAC_CTX *ctx = HMAC_CTX_new(); ++ unsigned int len = MD5_DIGEST_LENGTH; + + #ifdef EVP_MD_CTX_FLAG_NON_FIPS_ALLOW + /* Since MD5 is not allowed by FIPS, explicitly allow it. */ +@@ -57,7 +59,7 @@ void fr_hmac_md5(uint8_t digest[MD5_DIGEST_LENGTH], uint8_t const *text, size_t + + HMAC_Init_ex(ctx, key, key_len, EVP_md5(), NULL); + HMAC_Update(ctx, text, text_len); +- HMAC_Final(ctx, digest, NULL); ++ HMAC_Final(ctx, digest, &len); + HMAC_CTX_free(ctx); + } + #else +diff --git a/src/lib/hmacsha1.c b/src/lib/hmacsha1.c +index 211470ea35..8711f983b7 100644 +--- a/src/lib/hmacsha1.c ++++ b/src/lib/hmacsha1.c +@@ -13,6 +13,7 @@ RCSID("$Id$") + #ifdef HAVE_OPENSSL_EVP_H + #include + #include ++#include + #endif + + #include +@@ -35,9 +36,11 @@ void fr_hmac_sha1(uint8_t digest[SHA1_DIGEST_LENGTH], uint8_t const *text, size_ + uint8_t const *key, size_t key_len) + { + HMAC_CTX *ctx = HMAC_CTX_new(); ++ unsigned int len = SHA1_DIGEST_LENGTH; ++ + HMAC_Init_ex(ctx, key, key_len, EVP_sha1(), NULL); + HMAC_Update(ctx, text, text_len); +- HMAC_Final(ctx, digest, NULL); ++ HMAC_Final(ctx, digest, &len); + HMAC_CTX_free(ctx); + } + +diff --git a/src/lib/md4.c b/src/lib/md4.c +index 0515fca1ae..7169000381 100644 +--- a/src/lib/md4.c ++++ b/src/lib/md4.c +@@ -28,6 +28,7 @@ void fr_md4_calc(uint8_t out[MD4_DIGEST_LENGTH], uint8_t const *in, size_t inlen + fr_md4_init(&ctx); + fr_md4_update(&ctx, in, inlen); + fr_md4_final(out, &ctx); ++ fr_md4_destroy(&ctx); + } + + #ifndef HAVE_OPENSSL_MD4_H +diff --git a/src/lib/md5.c b/src/lib/md5.c +index 9858175bd4..b5c1729148 100644 +--- a/src/lib/md5.c ++++ b/src/lib/md5.c +@@ -30,6 +30,7 @@ void fr_md5_calc(uint8_t *out, uint8_t const *in, size_t inlen) + fr_md5_init(&ctx); + fr_md5_update(&ctx, in, inlen); + fr_md5_final(out, &ctx); ++ fr_md5_destroy(&ctx); + } + + #ifndef HAVE_OPENSSL_MD5_H +diff --git a/src/lib/pair.c b/src/lib/pair.c +index d711f90c5d..146c82f95b 100644 +--- a/src/lib/pair.c ++++ b/src/lib/pair.c +@@ -45,7 +45,7 @@ static int _fr_pair_free(VALUE_PAIR *vp) { + return 0; + } + +-static VALUE_PAIR *fr_pair_alloc(TALLOC_CTX *ctx) ++VALUE_PAIR *fr_pair_alloc(TALLOC_CTX *ctx) + { + VALUE_PAIR *vp; + +@@ -121,24 +121,7 @@ VALUE_PAIR *fr_pair_afrom_num(TALLOC_CTX *ctx, unsigned int attr, unsigned int v + DICT_ATTR const *da; + + da = dict_attrbyvalue(attr, vendor); +- if (!da) { +- VALUE_PAIR *vp; +- +- vp = fr_pair_alloc(ctx); +- if (!vp) return NULL; +- +- /* +- * Ensure that the DA is parented by the VP. +- */ +- da = dict_unknown_afrom_fields(vp, attr, vendor); +- if (!da) { +- talloc_free(vp); +- return NULL; +- } +- +- vp->da = da; +- return vp; +- } ++ if (!da) return NULL; + + return fr_pair_afrom_da(ctx, da); + } +@@ -286,6 +269,43 @@ void fr_pair_add(VALUE_PAIR **first, VALUE_PAIR *add) + i->next = add; + } + ++/** Add a VP to the start of the list. ++ * ++ * Links the new VP to 'first', then points 'first' at the new VP. ++ * ++ * @param[in] first VP in linked list. Will add new VP to the start of this list. ++ * @param[in] add VP to add to list. ++ */ ++void fr_pair_prepend(VALUE_PAIR **first, VALUE_PAIR *add) ++{ ++ VALUE_PAIR *i; ++ ++ if (!add) return; ++ ++ VERIFY_VP(add); ++ ++ if (*first == NULL) { ++ *first = add; ++ return; ++ } ++ ++ /* ++ * Find the end of the list to be prepended ++ */ ++ for (i = add; i->next; i = i->next) { ++#ifdef WITH_VERIFY_PTR ++ VERIFY_VP(i); ++ /* ++ * The same VP should never by added multiple times ++ * to the same list. ++ */ ++ fr_assert(*first != i); ++#endif ++ } ++ i->next = *first; ++ *first = add; ++} ++ + /** Replace all matching VPs + * + * Walks over 'first', and replaces the first VP that matches 'replace'. +@@ -851,13 +871,15 @@ void fr_pair_steal(TALLOC_CTX *ctx, VALUE_PAIR *vp) + * @param[in] ctx for talloc + * @param[in,out] to destination list. + * @param[in,out] from source list. ++ * @param[in] op operator for move. + * + * @see radius_pairmove + */ +-void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from) ++void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, FR_TOKEN op) + { + VALUE_PAIR *i, *found; + VALUE_PAIR *head_new, **tail_new; ++ VALUE_PAIR *head_prepend; + VALUE_PAIR **tail_from; + + if (!to || !from || !*from) return; +@@ -871,6 +893,12 @@ void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from) + head_new = NULL; + tail_new = &head_new; + ++ /* ++ * Any attributes that are requested to be prepended ++ * are added to a temporary list here ++ */ ++ head_prepend = NULL; ++ + /* + * We're looping over the "from" list, moving some + * attributes out, but leaving others in place. +@@ -983,13 +1011,36 @@ void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from) + fr_pair_steal(ctx, i); + tail_new = &(i->next); + continue; ++ case T_OP_PREPEND: ++ i->next = head_prepend; ++ head_prepend = i; ++ fr_pair_steal(ctx, i); ++ continue; + } + } /* loop over the "from" list. */ + + /* +- * Take the "new" list, and append it to the "to" list. ++ * If the op parameter was prepend, add the "new" list ++ * attributes first as those whose individual operator ++ * is prepend should be prepended to the resulting list + */ +- fr_pair_add(to, head_new); ++ if (op == T_OP_PREPEND) { ++ fr_pair_prepend(to, head_new); ++ } ++ ++ /* ++ * If there are any items in the prepend list prepend ++ * it to the "to" list ++ */ ++ fr_pair_prepend(to, head_prepend); ++ ++ /* ++ * If the op parameter was not prepend, take the "new" ++ * list, and append it to the "to" list. ++ */ ++ if (op != T_OP_PREPEND) { ++ fr_pair_add(to, head_new); ++ } + } + + /** Move matching pairs between VALUE_PAIR lists +@@ -1823,7 +1874,7 @@ FR_TOKEN fr_pair_raw_from_str(char const **ptr, VALUE_PAIR_RAW *raw) + break; + + default: +- fr_strerror_printf("Failed to find expected value on right hand side"); ++ fr_strerror_printf("Failed to find expected value on right hand side in %s", raw->l_opand); + return T_INVALID; + } + +diff --git a/src/lib/print.c b/src/lib/print.c +index 9ac927358b..57455b6f30 100644 +--- a/src/lib/print.c ++++ b/src/lib/print.c +@@ -495,33 +495,28 @@ char *vp_aprints_type(TALLOC_CTX *ctx, PW_TYPE type) + * @param out Where to write the string. + * @param outlen Length of output buffer. + * @param vp to print. ++ * @param raw_value if true, the raw value is printed and not the enumerated attribute value + * @return the length of data written to out, or a value >= outlen on truncation. + */ +-size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp) ++size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp, bool raw_value) + { + char const *q; + size_t len, freespace = outlen; ++ /* attempt to print raw_value when has_value is false, or raw_value is false, but only ++ if has_tag is also false */ ++ bool raw = (raw_value || !vp->da->flags.has_value) && !vp->da->flags.has_tag; + +- if (!vp->da->flags.has_tag) { ++ if (raw) { + switch (vp->da->type) { + case PW_TYPE_INTEGER: +- if (vp->da->flags.has_value) break; +- + return snprintf(out, freespace, "%u", vp->vp_integer); + + case PW_TYPE_SHORT: +- if (vp->da->flags.has_value) break; +- + return snprintf(out, freespace, "%u", (unsigned int) vp->vp_short); + + case PW_TYPE_BYTE: +- if (vp->da->flags.has_value) break; +- + return snprintf(out, freespace, "%u", (unsigned int) vp->vp_byte); + +- case PW_TYPE_SIGNED: +- return snprintf(out, freespace, "%d", vp->vp_signed); +- + default: + break; + } +@@ -775,7 +770,7 @@ char *vp_aprints(TALLOC_CTX *ctx, VALUE_PAIR const *vp, char quote) + + value = vp_aprints_value(ctx, vp, quote); + +- if (vp->da->flags.has_tag) { ++ if (vp->da->flags.has_tag && (vp->tag != TAG_ANY)) { + if (quote && (vp->da->type == PW_TYPE_STRING)) { + str = talloc_asprintf(ctx, "%s:%d %s %c%s%c", vp->da->name, vp->tag, token, quote, value, quote); + } else { +diff --git a/src/lib/radius.c b/src/lib/radius.c +index 3881111f7d..524e68088e 100644 +--- a/src/lib/radius.c ++++ b/src/lib/radius.c +@@ -28,6 +28,7 @@ RCSID("$Id$") + #include + + #include ++#include + + #include + #include +@@ -528,6 +529,8 @@ static void make_secret(uint8_t *digest, uint8_t const *vector, + for ( i = 0; i < length; i++ ) { + digest[i] ^= value[i]; + } ++ ++ fr_md5_destroy(&context); + } + + #define MAX_PASS_LEN (128) +@@ -562,8 +565,9 @@ static void make_passwd(uint8_t *output, ssize_t *outlen, + *outlen = len; + + fr_md5_init(&context); ++ fr_md5_init(&old); + fr_md5_update(&context, (uint8_t const *) secret, strlen(secret)); +- old = context; ++ fr_md5_copy(old, context); + + /* + * Do first pass. +@@ -572,7 +576,7 @@ static void make_passwd(uint8_t *output, ssize_t *outlen, + + for (n = 0; n < len; n += AUTH_PASS_LEN) { + if (n > 0) { +- context = old; ++ fr_md5_copy(context, old); + fr_md5_update(&context, + passwd + n - AUTH_PASS_LEN, + AUTH_PASS_LEN); +@@ -585,6 +589,9 @@ static void make_passwd(uint8_t *output, ssize_t *outlen, + } + + memcpy(output, passwd, len); ++ ++ fr_md5_destroy(&old); ++ fr_md5_destroy(&context); + } + + +@@ -653,8 +660,9 @@ static void make_tunnel_passwd(uint8_t *output, ssize_t *outlen, + output[2] = inlen; /* length of the password string */ + + fr_md5_init(&context); ++ fr_md5_init(&old); + fr_md5_update(&context, (uint8_t const *) secret, strlen(secret)); +- old = context; ++ fr_md5_copy(old, context); + + fr_md5_update(&context, vector, AUTH_VECTOR_LEN); + fr_md5_update(&context, &output[0], 2); +@@ -663,7 +671,7 @@ static void make_tunnel_passwd(uint8_t *output, ssize_t *outlen, + size_t block_len; + + if (n > 0) { +- context = old; ++ fr_md5_copy(context, old); + fr_md5_update(&context, + output + 2 + n - AUTH_PASS_LEN, + AUTH_PASS_LEN); +@@ -681,6 +689,8 @@ static void make_tunnel_passwd(uint8_t *output, ssize_t *outlen, + output[i + 2 + n] ^= digest[i]; + } + } ++ fr_md5_destroy(&old); ++ fr_md5_destroy(&context); + } + + static int do_next_tlv(VALUE_PAIR const *vp, VALUE_PAIR const *next, int nest) +@@ -1552,6 +1562,8 @@ int rad_vp2rfc(RADIUS_PACKET const *packet, + + VERIFY_VP(vp); + ++ if (room < 2) return -1; ++ + if (vp->da->vendor != 0) { + fr_strerror_printf("rad_vp2rfc called with VSA"); + return -1; +@@ -1594,6 +1606,88 @@ int rad_vp2rfc(RADIUS_PACKET const *packet, + return 18; + } + ++ /* ++ * Hacks for NAS-Filter-Rule. They all get concatenated ++ * with 0x00 bytes in between the values. We rely on the ++ * decoder to do the opposite transformation on incoming ++ * packets. ++ */ ++ if (vp->da->attr == PW_NAS_FILTER_RULE) { ++ uint8_t const *end = ptr + room; ++ uint8_t *p, *attr = ptr; ++ bool zero = false; ++ ++ attr[0] = PW_NAS_FILTER_RULE; ++ attr[1] = 2; ++ p = ptr + 2; ++ ++ while (vp && !vp->da->vendor && (vp->da->attr == PW_NAS_FILTER_RULE)) { ++ if ((p + zero + vp->vp_length) > end) { ++ break; ++ } ++ ++ if (zero) { ++ if (attr[1] == 255) { ++ attr = p; ++ if ((attr + 3) >= end) break; ++ ++ attr[0] = PW_NAS_FILTER_RULE; ++ attr[1] = 2; ++ p = attr + 2; ++ } ++ ++ *(p++) = 0; ++ attr[1]++; ++ } ++ ++ /* ++ * Check for overflow ++ */ ++ if ((attr[1] + vp->vp_length) < 255) { ++ memcpy(p, vp->vp_strvalue, vp->vp_length); ++ attr[1] += vp->vp_length; ++ p += vp->vp_length; ++ ++ } else if (attr + (attr[1] + 2 + vp->vp_length) > end) { ++ break; ++ ++ } else if (vp->vp_length > 253) { ++ /* ++ * Drop VPs which are too long. ++ * We don't (yet) split one VP ++ * across multiple attributes. ++ */ ++ vp = vp->next; ++ continue; ++ ++ } else { ++ size_t first, second; ++ ++ first = 255 - attr[1]; ++ second = vp->vp_length - first; ++ ++ memcpy(p, vp->vp_strvalue, first); ++ p += first; ++ attr[1] = 255; ++ attr = p; ++ ++ attr[0] = PW_NAS_FILTER_RULE; ++ attr[1] = 2; ++ p = attr + 2; ++ ++ memcpy(p, vp->vp_strvalue + first, second); ++ attr[1] += second; ++ p += second; ++ } ++ ++ vp = vp->next; ++ zero = true; ++ } ++ ++ *pvp = vp; ++ return p - ptr; ++ } ++ + /* + * EAP-Message is special. + */ +@@ -2036,6 +2130,7 @@ int rad_sign(RADIUS_PACKET *packet, RADIUS_PACKET const *original, + fr_md5_update(&context, (uint8_t const *) secret, + strlen(secret)); + fr_md5_final(digest, &context); ++ fr_md5_destroy(&context); + + memcpy(hdr->vector, digest, AUTH_VECTOR_LEN); + memcpy(packet->vector, digest, AUTH_VECTOR_LEN); +@@ -2159,6 +2254,7 @@ static int calc_acctdigest(RADIUS_PACKET *packet, char const *secret) + fr_md5_update(&context, packet->data, packet->data_len); + fr_md5_update(&context, (uint8_t const *) secret, strlen(secret)); + fr_md5_final(digest, &context); ++ fr_md5_destroy(&context); + + /* + * Return 0 if OK, 2 if not OK. +@@ -2198,6 +2294,7 @@ static int calc_replydigest(RADIUS_PACKET *packet, RADIUS_PACKET *original, + fr_md5_update(&context, packet->data, packet->data_len); + fr_md5_update(&context, (uint8_t const *) secret, strlen(secret)); + fr_md5_final(calc_digest, &context); ++ fr_md5_destroy(&context); + + /* + * Copy the packet's vector back to the packet. +@@ -2896,6 +2993,115 @@ int rad_verify(RADIUS_PACKET *packet, RADIUS_PACKET *original, char const *secre + } + + ++/** Convert one or more NAS-Filter-Rule attributes to one or more ++ * attributes. ++ * ++ */ ++static ssize_t data2vp_nas_filter_rule(TALLOC_CTX *ctx, ++ DICT_ATTR const *da, uint8_t const *start, ++ size_t const packetlen, VALUE_PAIR **pvp) ++{ ++ uint8_t const *p = start; ++ uint8_t const *attr = start; ++ uint8_t const *end = start + packetlen; ++ uint8_t const *attr_end; ++ uint8_t *q; ++ VALUE_PAIR *vp; ++ uint8_t buffer[253]; ++ ++ q = buffer; ++ ++ /* ++ * The packet has already been sanity checked, so we ++ * don't care about walking off of the end of it. ++ */ ++ while (attr < end) { ++ if ((attr + 2) > end) { ++ fr_strerror_printf("decode NAS-Filter-Rule: Failure (1) to call rad_packet_ok"); ++ return -1; ++ } ++ ++ if (attr[1] < 2) { ++ fr_strerror_printf("decode NAS-Filter-Rule: Failure (2) to call rad_packet_ok"); ++ return -1; ++ } ++ if (attr[0] != PW_NAS_FILTER_RULE) break; ++ ++ /* ++ * Now decode one, or part of one rule. ++ */ ++ p = attr + 2; ++ attr_end = attr + attr[1]; ++ ++ if (attr_end > end) { ++ fr_strerror_printf("decode NAS-Filter-Rule: Failure (3) to call rad_packet_ok"); ++ return -1; ++ } ++ ++ /* ++ * Coalesce data until the zero byte. ++ */ ++ while (p < attr_end) { ++ /* ++ * Once we hit the zero byte, create the ++ * VP, skip the zero byte, and reset the ++ * counters. ++ */ ++ if (*p == 0) { ++ /* ++ * Discard consecutive zeroes. ++ */ ++ if (q > buffer) { ++ vp = fr_pair_afrom_da(ctx, da); ++ if (!vp) { ++ fr_strerror_printf("decode NAS-Filter-Rule: Out of memory"); ++ return -1; ++ } ++ ++ fr_pair_value_bstrncpy(vp, buffer, q - buffer); ++ ++ *pvp = vp; ++ pvp = &(vp->next); ++ q = buffer; ++ } ++ ++ p++; ++ continue; ++ } ++ *(q++) = *(p++); ++ ++ /* ++ * Not much reason to have rules which ++ * are too long. ++ */ ++ if ((size_t) (q - buffer) > sizeof(buffer)) { ++ fr_strerror_printf("decode NAS-Filter-Rule: decoded attribute is too long"); ++ return -1; ++ } ++ } ++ ++ /* ++ * Done this attribute. There MAY be things left ++ * in the buffer. ++ */ ++ attr = attr_end; ++ } ++ ++ if (q == buffer) return attr + attr[2] - start; ++ ++ vp = fr_pair_afrom_da(ctx, da); ++ if (!vp) { ++ fr_strerror_printf("decode NAS-Filter-Rule: Out of memory"); ++ return -1; ++ } ++ ++ fr_pair_value_bstrncpy(vp, buffer, q - buffer); ++ ++ *pvp = vp; ++ ++ return p - start; ++} ++ + /** Convert a "concatenated" attribute to one long VP + * + */ +@@ -3503,7 +3709,7 @@ static ssize_t data2vp_vsas(TALLOC_CTX *ctx, RADIUS_PACKET *packet, + /* + * WiMAX craziness + */ +- if ((vendor == VENDORPEC_WIMAX) && dv->flags) { ++ if (dv->flags) { + rcode = data2vp_wimax(ctx, packet, original, secret, vendor, + data, attrlen, packetlen, pvp); + return rcode; +@@ -3610,19 +3816,12 @@ ssize_t data2vp(TALLOC_CTX *ctx, + return 0; + } + +-#if !defined(NDEBUG) || defined(__clang_analyzer__) + /* +- * Hacks for Coverity. Editing the dictionary +- * will break assumptions about CUI. We know +- * this, but Coverity doesn't. ++ * Create a zero-length attribute. + */ +- if (da->type != PW_TYPE_OCTETS) return -1; +-#endif +- +- data = buffer; +- *buffer = '\0'; +- datalen = 0; +- goto alloc_cui; /* skip everything */ ++ vp = fr_pair_afrom_da(ctx, da); ++ if (!vp) return -1; ++ goto done; + } + + /* +@@ -3926,44 +4125,44 @@ ssize_t data2vp(TALLOC_CTX *ctx, + + default: + raw: ++ /* ++ * If it's already unknown, don't create a new ++ * unknown one. ++ */ ++ if (da->flags.is_unknown) break; ++ + /* + * Re-write the attribute to be "raw". It is + * therefore of type "octets", and will be + * handled below. ++ * ++ * We allocate the VP *first*, and then the da ++ * from it, so that there are no memory leaks. + */ +- da = dict_unknown_afrom_fields(ctx, da->attr, da->vendor); ++ vp = fr_pair_alloc(ctx); ++ if (!vp) return -1; ++ ++ da = dict_unknown_afrom_fields(vp, da->attr, da->vendor); + if (!da) { + fr_strerror_printf("Internal sanity check %d", __LINE__); + return -1; + } + tag = TAG_NONE; +-#ifndef NDEBUG +- /* +- * Fix for Coverity. +- */ +- if (da->type != PW_TYPE_OCTETS) { +- dict_attr_free(&da); +- return -1; +- } +-#endif +- break; ++ vp->da = da; ++ goto alloc_raw; + } + + /* + * And now that we've verified the basic type + * information, decode the actual data. + */ +- alloc_cui: + vp = fr_pair_afrom_da(ctx, da); + if (!vp) return -1; + ++alloc_raw: + vp->vp_length = datalen; + vp->tag = tag; + +-#ifdef __clang_analyzer__ +- if (!datalen && da->type != PW_TYPE_OCTETS) return -1; +-#endif +- + switch (da->type) { + case PW_TYPE_STRING: + p = talloc_array(vp, char, vp->vp_length + 1); +@@ -4072,6 +4271,8 @@ ssize_t data2vp(TALLOC_CTX *ctx, + fr_strerror_printf("Internal sanity check %d", __LINE__); + return -1; + } ++ ++done: + vp->type = VT_DATA; + *pvp = vp; + +@@ -4112,6 +4313,11 @@ ssize_t rad_attr2vp(TALLOC_CTX *ctx, + return data2vp_concat(ctx, da, data, length, pvp); + } + ++ if (!da->vendor && (da->attr == PW_NAS_FILTER_RULE)) { ++ VP_TRACE("attr2vp: NAS-Filter-Rule attribute\n"); ++ return data2vp_nas_filter_rule(ctx, da, data, length, pvp); ++ } ++ + /* + * Note that we pass the entire length, not just the + * length of this attribute. The Extended or WiMAX +@@ -4261,7 +4467,7 @@ int rad_decode(RADIUS_PACKET *packet, RADIUS_PACKET *original, + uint32_t num_attributes; + uint8_t *ptr; + radius_packet_t *hdr; +- VALUE_PAIR *head, **tail, *vp; ++ VALUE_PAIR *head, **tail, *vp = NULL; + + /* + * Extract attribute-value pairs +@@ -4385,8 +4591,9 @@ int rad_pwencode(char *passwd, size_t *pwlen, char const *secret, + secretlen = strlen(secret); + + fr_md5_init(&context); ++ fr_md5_init(&old); + fr_md5_update(&context, (uint8_t const *) secret, secretlen); +- old = context; /* save intermediate work */ ++ fr_md5_copy(old, context); /* save intermediate work */ + + /* + * Encrypt it in place. Don't bother checking +@@ -4397,7 +4604,7 @@ int rad_pwencode(char *passwd, size_t *pwlen, char const *secret, + fr_md5_update(&context, vector, AUTH_PASS_LEN); + fr_md5_final(digest, &context); + } else { +- context = old; ++ fr_md5_copy(context, old); + fr_md5_update(&context, + (uint8_t *) passwd + n - AUTH_PASS_LEN, + AUTH_PASS_LEN); +@@ -4409,6 +4616,9 @@ int rad_pwencode(char *passwd, size_t *pwlen, char const *secret, + } + } + ++ fr_md5_destroy(&old); ++ fr_md5_destroy(&context); ++ + return 0; + } + +@@ -4441,8 +4651,9 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret, + secretlen = strlen(secret); + + fr_md5_init(&context); ++ fr_md5_init(&old); + fr_md5_update(&context, (uint8_t const *) secret, secretlen); +- old = context; /* save intermediate work */ ++ fr_md5_copy(old, context); /* save intermediate work */ + + /* + * The inverse of the code above. +@@ -4452,7 +4663,7 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret, + fr_md5_update(&context, vector, AUTH_VECTOR_LEN); + fr_md5_final(digest, &context); + +- context = old; ++ fr_md5_copy(context, old); + if (pwlen > AUTH_PASS_LEN) { + fr_md5_update(&context, (uint8_t *) passwd, + AUTH_PASS_LEN); +@@ -4460,7 +4671,7 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret, + } else { + fr_md5_final(digest, &context); + +- context = old; ++ fr_md5_copy(context, old); + if (pwlen > (n + AUTH_PASS_LEN)) { + fr_md5_update(&context, (uint8_t *) passwd + n, + AUTH_PASS_LEN); +@@ -4473,6 +4684,9 @@ int rad_pwdecode(char *passwd, size_t pwlen, char const *secret, + } + + done: ++ fr_md5_destroy(&old); ++ fr_md5_destroy(&context); ++ + passwd[pwlen] = '\0'; + return strlen(passwd); + } +@@ -4609,8 +4823,9 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, + secretlen = strlen(secret); + + fr_md5_init(&context); ++ fr_md5_init(&old); + fr_md5_update(&context, (uint8_t const *) secret, secretlen); +- old = context; /* save intermediate work */ ++ fr_md5_copy(old, context); /* save intermediate work */ + + /* + * Set up the initial key: +@@ -4637,7 +4852,7 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, + + fr_md5_final(digest, &context); + +- context = old; ++ fr_md5_copy(context, old); + + /* + * A quick check: decrypt the first octet +@@ -4657,7 +4872,7 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, + + fr_md5_final(digest, &context); + +- context = old; ++ fr_md5_copy(context, old); + fr_md5_update(&context, passwd + n + 2, block_len); + } + +@@ -4669,6 +4884,9 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, + *pwlen = reallen; + passwd[reallen] = 0; + ++ fr_md5_destroy(&old); ++ fr_md5_destroy(&context); ++ + return reallen; + } + +diff --git a/src/lib/token.c b/src/lib/token.c +index 8ae41b392f..a7978622e6 100644 +--- a/src/lib/token.c ++++ b/src/lib/token.c +@@ -42,6 +42,7 @@ const FR_NAME_NUMBER fr_tokens[] = { + { "=*", T_OP_CMP_TRUE, }, + { "!*", T_OP_CMP_FALSE, }, + { "==", T_OP_CMP_EQ, }, ++ { "^=", T_OP_PREPEND, }, + { "=", T_OP_EQ, }, + { "!=", T_OP_NE, }, + { ">=", T_OP_GE, }, +@@ -78,9 +79,10 @@ const bool fr_assignment_op[] = { + false, /* =* 20 */ + false, /* !* */ + false, /* == */ +- false, /* # */ +- false, /* bare word */ +- false, /* "foo" 25 */ ++ true, /* ^= */ ++ false, /* # */ ++ false, /* bare word 25 */ ++ false, /* "foo" */ + false, /* 'foo' */ + false, /* `foo` */ + false +@@ -108,12 +110,13 @@ const bool fr_equality_op[] = { + true, /* < */ + true, /* =~ */ + true, /* !~ */ +- true, /* =* 20 */ ++ true, /* =* 20 */ + true, /* !* */ + true, /* == */ +- false, /* # */ +- false, /* bare word */ +- false, /* "foo" 25 */ ++ false, /* ^= */ ++ false, /* # */ ++ false, /* bare word 25 */ ++ false, /* "foo" */ + false, /* 'foo' */ + false, /* `foo` */ + false +@@ -144,9 +147,10 @@ const bool fr_str_tok[] = { + false, /* =* 20 */ + false, /* !* */ + false, /* == */ +- false, /* # */ +- true, /* bare word */ +- true, /* "foo" 25 */ ++ false, /* ^= */ ++ false, /* # */ ++ true, /* bare word 25 */ ++ true, /* "foo" */ + true, /* 'foo' */ + true, /* `foo` */ + false +diff --git a/src/main/cb.c b/src/main/cb.c +index 4ae14e575b..0796914b41 100644 +--- a/src/main/cb.c ++++ b/src/main/cb.c +@@ -29,45 +29,94 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + #ifdef WITH_TLS + void cbtls_info(SSL const *s, int where, int ret) + { +- char const *str, *state; ++ char const *role, *state; + REQUEST *request = SSL_get_ex_data(s, FR_TLS_EX_INDEX_REQUEST); + + if ((where & ~SSL_ST_MASK) & SSL_ST_CONNECT) { +- str="TLS_connect"; ++ role = "Client "; + } else if (((where & ~SSL_ST_MASK)) & SSL_ST_ACCEPT) { +- str="TLS_accept"; ++ role = "Server "; + } else { +- str="(other)"; ++ role = ""; + } + + state = SSL_state_string_long(s); + state = state ? state : ""; + + if ((where & SSL_CB_LOOP) || (where & SSL_CB_HANDSHAKE_START) || (where & SSL_CB_HANDSHAKE_DONE)) { +- RDEBUG2("%s: %s", str, state); ++ if (RDEBUG_ENABLED3) { ++ char const *abbrv = SSL_state_string(s); ++ size_t len; ++#if OPENSSL_VERSION_NUMBER >= 0x10100000L ++ STACK_OF(SSL_CIPHER) *client_ciphers; ++ STACK_OF(SSL_CIPHER) *server_ciphers; ++#endif ++ ++ /* ++ * Trim crappy OpenSSL state strings... ++ */ ++ len = strlen(abbrv); ++ if ((len > 1) && (abbrv[len - 1] == ' ')) len--; ++ ++ RDEBUG3("(TLS) Handshake state [%.*s] - %s%s (%d)", ++ (int)len, abbrv, role, state, SSL_get_state(s)); ++ ++ /* ++ * After a ClientHello, list all the proposed ciphers from the client ++ */ ++#if OPENSSL_VERSION_NUMBER >= 0x10100000L ++ if (SSL_get_state(s) == TLS_ST_SR_CLNT_HELLO) { ++ int i; ++ int num_ciphers; ++ const SSL_CIPHER *this_cipher; ++ ++ server_ciphers = SSL_get_ciphers(s); ++ if (server_ciphers) { ++ RDEBUG3("Server preferred ciphers (by priority)"); ++ num_ciphers = sk_SSL_CIPHER_num(server_ciphers); ++ for (i = 0; i < num_ciphers; i++) { ++ this_cipher = sk_SSL_CIPHER_value(server_ciphers, i); ++ RDEBUG3("(TLS) [%i] %s", i, SSL_CIPHER_get_name(this_cipher)); ++ } ++ } ++ ++ client_ciphers = SSL_get_client_ciphers(s); ++ if (client_ciphers) { ++ RDEBUG3("Client preferred ciphers (by priority)"); ++ num_ciphers = sk_SSL_CIPHER_num(client_ciphers); ++ for (i = 0; i < num_ciphers; i++) { ++ this_cipher = sk_SSL_CIPHER_value(client_ciphers, i); ++ RDEBUG3("(TLS) [%i] %s", i, SSL_CIPHER_get_name(this_cipher)); ++ } ++ } ++ } ++#endif ++ } else { ++ RDEBUG2("(TLS) Handshake state - %s%s", role, state); ++ } + return; + } + + if (where & SSL_CB_ALERT) { + if ((ret & 0xff) == SSL_AD_CLOSE_NOTIFY) return; + +- RERROR("TLS Alert %s:%s:%s", (where & SSL_CB_READ) ? "read": "write", ++ RERROR("(TLS) Alert %s:%s:%s", (where & SSL_CB_READ) ? "read": "write", + SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); + return; + } + + if (where & SSL_CB_EXIT) { + if (ret == 0) { +- RERROR("%s: Failed in %s", str, state); ++ RERROR("(TLS) %s: Failed in %s", role, state); + return; + } + + if (ret < 0) { + if (SSL_want_read(s)) { +- RDEBUG2("%s: Need to read more data: %s", str, state); ++ RDEBUG2("(TLS) %s: Need to read more data: %s", role, state); + return; + } +- ERROR("tls: %s: Error in %s", str, state); ++ RERROR("(TLS) %s: Error in %s", role, state); + } + } + } +@@ -83,18 +132,18 @@ void cbtls_msg(int write_p, int msg_version, int content_type, + tls_session_t *state = (tls_session_t *)arg; + + /* +- * OpenSSL 1.0.2 calls this function with 'pseudo' +- * content types. Which breaks our tracking of +- * the SSL Session state. ++ * OpenSSL calls this function with 'pseudo' content ++ * types. Which breaks our tracking of the SSL Session ++ * state. + */ +- if ((msg_version == 0) && (content_type > UINT8_MAX)) { +- DEBUG4("Ignoring cbtls_msg call with pseudo content type %i, version %i", ++ if ((msg_version == 0) || (content_type >= UINT8_MAX)) { ++ DEBUG4("(TLS) Ignoring cbtls_msg call with pseudo content type %i, version %i", + content_type, msg_version); + return; + } + + if ((write_p != 0) && (write_p != 1)) { +- DEBUG4("Ignoring cbtls_msg call with invalid write_p %d", write_p); ++ DEBUG4("(TLS) Ignoring cbtls_msg call with invalid write_p %d", write_p); + return; + } + +@@ -104,6 +153,25 @@ void cbtls_msg(int write_p, int msg_version, int content_type, + */ + if (!state) return; + ++ if (rad_debug_lvl > 3) { ++ size_t i, j, data_len = len; ++ char buffer[3*16 + 1]; ++ uint8_t const *in = inbuf; ++ ++ DEBUG("(TLS) Received %zu bytes of TLS data", len); ++ if (data_len > 256) data_len = 256; ++ ++ for (i = 0; i < data_len; i += 16) { ++ for (j = 0; j < 16; j++) { ++ if ((i + j) >= data_len) break; ++ ++ sprintf(buffer + 3 * j, "%02x ", in[i + j]); ++ } ++ ++ DEBUG("(TLS) %s", buffer); ++ } ++ } ++ + /* + * 0 - received (from peer) + * 1 - sending (to peer) +@@ -124,6 +192,12 @@ void cbtls_msg(int write_p, int msg_version, int content_type, + state->info.alert_level = 0x00; + state->info.alert_description = 0x00; + ++#if OPENSSL_VERSION_NUMBER >= 0x10101000L ++ } else if (content_type == SSL3_RT_INNER_CONTENT_TYPE && buf[0] == SSL3_RT_APPLICATION_DATA) { ++ /* let tls_ack_handler set application_data */ ++ state->info.content_type = SSL3_RT_HANDSHAKE; ++#endif ++ + #ifdef SSL3_RT_HEARTBEAT + } else if (content_type == TLS1_RT_HEARTBEAT) { + uint8_t *p = buf; +@@ -141,16 +215,24 @@ void cbtls_msg(int write_p, int msg_version, int content_type, + } + #endif + } ++ + tls_session_information(state); + } + + int cbtls_password(char *buf, +- int num UNUSED, ++ int num, + int rwflag UNUSED, + void *userdata) + { +- strcpy(buf, (char *)userdata); +- return(strlen((char *)userdata)); ++ size_t len; ++ ++ len = strlcpy(buf, (char *)userdata, num); ++ if (len >= (size_t) num) { ++ ERROR("Password too long. Maximum length is %i bytes", num - 1); ++ return 0; ++ } ++ ++ return len; + } + + #endif +diff --git a/src/main/map.c b/src/main/map.c +index 6275ba124d..17988d27f9 100644 +--- a/src/main/map.c ++++ b/src/main/map.c +@@ -245,6 +245,18 @@ int map_afrom_cp(TALLOC_CTX *ctx, vp_map_t **out, CONF_PAIR *cp, + } + break; + ++ case T_BARE_WORD: ++ /* ++ * Foo = %{...} ++ * ++ * Not allowed! ++ */ ++ if ((attr[0] == '%') && (attr[1] == '{')) { ++ cf_log_err_cp(cp, "Bare expansions are not permitted. They must be in a double-quoted string."); ++ goto error; ++ } ++ /* FALL-THROUGH */ ++ + default: + slen = tmpl_afrom_attr_str(ctx, &map->lhs, attr, dst_request_def, dst_list_def, true, true); + if (slen <= 0) { +@@ -285,14 +297,20 @@ int map_afrom_cp(TALLOC_CTX *ctx, vp_map_t **out, CONF_PAIR *cp, + goto error; + } + +- /* +- * We cannot assign a count to an attribute. That must +- * be done in an xlat. +- */ +- if ((map->rhs->type == TMPL_TYPE_ATTR) && +- (map->rhs->tmpl_num == NUM_COUNT)) { +- cf_log_err_cp(cp, "Cannot assign from a count"); +- goto error; ++ if (map->rhs->type == TMPL_TYPE_ATTR) { ++ /* ++ * We cannot assign a count to an attribute. That must ++ * be done in an xlat. ++ */ ++ if (map->rhs->tmpl_num == NUM_COUNT) { ++ cf_log_err_cp(cp, "Cannot assign from a count"); ++ goto error; ++ } ++ ++ if (map->rhs->tmpl_da->flags.virtual) { ++ cf_log_err_cp(cp, "Virtual attributes must be in an expansion such as \"%%{%s}\".", map->rhs->tmpl_da->name); ++ goto error; ++ } + } + + VERIFY_MAP(map); +@@ -1091,6 +1109,12 @@ int map_to_request(REQUEST *request, vp_map_t const *map, radius_map_getvalue_t + */ + if (((map->lhs->tmpl_list == PAIR_LIST_COA) || + (map->lhs->tmpl_list == PAIR_LIST_DM)) && !request->coa) { ++ if ((request->packet->code == PW_CODE_COA_REQUEST) || ++ (request->packet->code == PW_CODE_DISCONNECT_REQUEST)) { ++ REDEBUG("You cannot do 'update coa' when processing a CoA / Disconnect request. Use 'update request' instead."); ++ return -2; ++ } ++ + if (!request_alloc_coa(context)) { + REDEBUG("Failed to create a CoA/Disconnect Request message"); + return -2; +@@ -1173,10 +1197,14 @@ int map_to_request(REQUEST *request, vp_map_t const *map, radius_map_getvalue_t + rad_assert(map->rhs->type == TMPL_TYPE_EXEC); + /* FALL-THROUGH */ + case T_OP_ADD: +- fr_pair_list_move(parent, list, &head); ++ fr_pair_list_move(parent, list, &head, map->op); + fr_pair_list_free(&head); + } + goto finish; ++ case T_OP_PREPEND: ++ fr_pair_list_move(parent, list, &head, T_OP_PREPEND); ++ fr_pair_list_free(&head); ++ goto finish; + + default: + fr_pair_list_free(&head); +@@ -1341,6 +1369,14 @@ int map_to_request(REQUEST *request, vp_map_t const *map, radius_map_getvalue_t + fr_pair_list_free(&head); + break; + ++ /* ++ * ^= - Prepend src_list attributes to the destination ++ */ ++ case T_OP_PREPEND: ++ fr_pair_prepend(list, head); ++ head = NULL; ++ break; ++ + /* + * += - Add all src_list attributes to the destination + */ +diff --git a/src/main/tls.c b/src/main/tls.c +index 78c7370a63..cc8dc53178 100644 +--- a/src/main/tls.c ++++ b/src/main/tls.c +@@ -27,6 +27,7 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + + #include + #include ++#include + #include + + #ifdef HAVE_SYS_STAT_H +@@ -37,6 +38,10 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + #include + #endif + ++#ifdef HAVE_DIRENT_H ++#include ++#endif ++ + #ifdef HAVE_UTIME_H + #include + #endif +@@ -56,8 +61,19 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + # endif + # include + ++#if OPENSSL_VERSION_NUMBER >= 0x30000000L ++# include ++ ++static OSSL_PROVIDER *openssl_default_provider = NULL; ++static OSSL_PROVIDER *openssl_legacy_provider = NULL; ++#endif ++ + #define LOG_PREFIX "tls" + ++#if OPENSSL_VERSION_NUMBER >= 0x30000000L ++#define ERR_get_error_line(_file, _line) ERR_get_error_all(_file, _line, NULL, NULL, NULL) ++#endif ++ + #ifdef ENABLE_OPENSSL_VERSION_CHECK + typedef struct libssl_defect { + uint64_t high; +@@ -153,6 +169,11 @@ static unsigned int record_plus(record_t *buf, void const *ptr, + static unsigned int record_minus(record_t *buf, void *ptr, + unsigned int size); + ++typedef struct { ++ char const *name; ++ SSL_CTX *ctx; ++} fr_realm_ctx_t; ++ + DIAG_OFF(format-nonliteral) + /** Print errors in the TLS thread local error stack + * +@@ -191,9 +212,9 @@ static int tls_verror_log(REQUEST *request, char const *msg, va_list ap) + + /* Extra verbose */ + if ((request && RDEBUG_ENABLED3) || DEBUG_ENABLED3) { +- ROPTIONAL(REDEBUG, ERROR, "%s: %s[%i]:%s", p, file, line, buffer); ++ ROPTIONAL(REDEBUG, ERROR, "(TLS) %s: %s[%i]:%s", p, file, line, buffer); + } else { +- ROPTIONAL(REDEBUG, ERROR, "%s: %s", p, buffer); ++ ROPTIONAL(REDEBUG, ERROR, "(TLS) %s: %s", p, buffer); + } + + talloc_free(p); +@@ -205,7 +226,7 @@ static int tls_verror_log(REQUEST *request, char const *msg, va_list ap) + * Print the error we were given, irrespective + * of whether there were any OpenSSL errors. + */ +- ROPTIONAL(RERROR, ERROR, "%s", p); ++ ROPTIONAL(RERROR, ERROR, "(TLS) %s", p); + talloc_free(p); + } + +@@ -217,9 +238,9 @@ static int tls_verror_log(REQUEST *request, char const *msg, va_list ap) + ERR_error_string_n(error, buffer, sizeof(buffer)); + /* Extra verbose */ + if ((request && RDEBUG_ENABLED3) || DEBUG_ENABLED3) { +- ROPTIONAL(REDEBUG, ERROR, "%s[%i]:%s", file, line, buffer); ++ ROPTIONAL(REDEBUG, ERROR, "(TLS) %s[%i]:%s", file, line, buffer); + } else { +- ROPTIONAL(REDEBUG, ERROR, "%s", buffer); ++ ROPTIONAL(REDEBUG, ERROR, "(TLS) %s", buffer); + } + in_stack++; + } while ((error = ERR_get_error_line(&file, &line))); +@@ -309,11 +330,11 @@ int tls_error_io_log(REQUEST *request, tls_session_t *session, int ret, char con + * being regarded as "dead". + */ + case SSL_ERROR_SYSCALL: +- ROPTIONAL(REDEBUG, ERROR, "System call (I/O) error (%i)", ret); ++ ROPTIONAL(REDEBUG, ERROR, "(TLS) System call (I/O) error (%i)", ret); + return 0; + + case SSL_ERROR_SSL: +- ROPTIONAL(REDEBUG, ERROR, "TLS protocol error (%i)", ret); ++ ROPTIONAL(REDEBUG, ERROR, "(TLS) Protocol error (%i)", ret); + return 0; + + /* +@@ -323,7 +344,7 @@ int tls_error_io_log(REQUEST *request, tls_session_t *session, int ret, char con + * the code needs updating here. + */ + default: +- ROPTIONAL(REDEBUG, ERROR, "TLS session error %i (%i)", error, ret); ++ ROPTIONAL(REDEBUG, ERROR, "(TLS) Session error %i (%i)", error, ret); + return 0; + } + +@@ -376,7 +397,7 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity, + * The passed identity is weird. Deny it. + */ + if (!identity_is_safe(identity)) { +- RWDEBUG("Invalid characters in PSK identity %s", identity); ++ RWDEBUG("(TLS) Invalid characters in PSK identity %s", identity); + return 0; + } + +@@ -386,7 +407,7 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity, + hex_len = radius_xlat(buffer, sizeof(buffer), request, conf->psk_query, + NULL, NULL); + if (!hex_len) { +- RWDEBUG("PSK expansion returned an empty string."); ++ RWDEBUG("(TLS) PSK expansion returned an empty string."); + return 0; + } + +@@ -396,7 +417,7 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity, + * the truncation, and complain about it. + */ + if (hex_len > (2 * max_psk_len)) { +- RWDEBUG("Returned PSK is too long (%u > %u)", ++ RWDEBUG("(TLS) Returned PSK is too long (%u > %u)", + (unsigned int) hex_len, 2 * max_psk_len); + return 0; + } +@@ -419,7 +440,7 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity, + * static identity. + */ + if (strcmp(identity, conf->psk_identity) != 0) { +- ERROR("Supplied PSK identity %s does not match configuration. Rejecting.", ++ ERROR("(TKS) Supplied PSK identity %s does not match configuration. Rejecting.", + identity); + return 0; + } +@@ -475,8 +496,6 @@ void tls_session_id(SSL_SESSION *ssn, char *buffer, size_t bufsize) + #endif + } + +- +- + static int _tls_session_free(tls_session_t *ssn) + { + /* +@@ -492,6 +511,52 @@ static int _tls_session_free(tls_session_t *ssn) + return 0; + } + ++#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER) ++/* ++ * By setting the environment variable SSLKEYLOGFILE to a filename keying ++ * material will be exported that you may use with Wireshark to decode any ++ * TLS flows. Please see the following for more details: ++ * ++ * https://gitlab.com/wireshark/wireshark/-/wikis/TLS#tls-decryption ++ * ++ * An example logging session is (you should delete the file on each run): ++ * ++ * rm -f /tmp/sslkey.log; env SSLKEYLOGFILE=/tmp/sslkey.log freeradius -X | tee /tmp/debug ++ */ ++static void tls_keylog_cb(UNUSED const SSL *ssl, const char *line) ++{ ++ int fd; ++ size_t len; ++ const char *filename; ++ // less than _POSIX_PIPE_BUF (512) guarantees writes are atomic for O_APPEND ++ char buffer[64 + 2*SSL3_RANDOM_SIZE + 2*SSL_MAX_MASTER_KEY_LENGTH]; ++ ++ filename = getenv("SSLKEYLOGFILE"); ++ if (!filename) return; ++ ++ len = strlen(line); ++ if ((len + 1) > sizeof(buffer)) { ++ DEBUG("SSLKEYLOGFILE buffer not large enough, max %lu, required %lu", sizeof(buffer), len + 1); ++ return; ++ } ++ ++ memcpy(buffer, line, len); ++ buffer[len] = '\n'; ++ ++ fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR); ++ if (fd < 0) { ++ fr_strerror_printf("Failed to open file %s: %s", filename, strerror(errno)); ++ return; ++ } ++ ++ if (write(fd, buffer, len + 1) == -1) { ++ DEBUG("Failed to write to file %s: %s", filename, strerror(errno)); ++ } ++ ++ close(fd); ++} ++#endif ++ + tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, int fd, VALUE_PAIR **certs) + { + int ret; +@@ -506,6 +571,7 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con + + ssn->ctx = conf->ctx; + ssn->mtu = conf->fragment_size; ++ ssn->conf = conf; + + SSL_CTX_set_mode(ssn->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_AUTO_RETRY); + +@@ -537,15 +603,14 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con + SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_CONF, (void *)conf); + SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_SSN, (void *)ssn); + if (certs) SSL_set_ex_data(ssn->ssl, fr_tls_ex_index_certs, (void *)certs); ++ + SSL_set_fd(ssn->ssl, fd); +- ret = SSL_connect(ssn->ssl); + ++ ret = SSL_connect(ssn->ssl); + if (ret < 0) { + switch (SSL_get_error(ssn->ssl, ret)) { +- default: +- break; +- +- ++ default: ++ break; + + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: +@@ -555,7 +620,7 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con + } + + if (ret <= 0) { +- tls_error_io_log(NULL, ssn, ret, "Failed in " STRINGIFY(__FUNCTION__) " (SSL_connect)"); ++ tls_error_io_log(NULL, ssn, ret, "Failed in connecting TLS session."); + talloc_free(ssn); + + return NULL; +@@ -575,18 +640,61 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con + * @param conf to use to configure the tls session. + * @param request The current #REQUEST. + * @param client_cert Whether to require a client_cert. ++ * @param allow_tls13 Whether to allow or forbid TLS 1.3. + * @return a new session on success, or NULL on error. + */ +-tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQUEST *request, bool client_cert) ++tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQUEST *request, bool client_cert, ++#ifndef TLS1_3_VERSION ++ UNUSED ++#endif ++ bool allow_tls13) + { + tls_session_t *state = NULL; + SSL *new_tls = NULL; + int verify_mode = 0; + VALUE_PAIR *vp; ++ X509_STORE *new_cert_store; + + rad_assert(request != NULL); + +- RDEBUG2("Initiating new TLS session"); ++ RDEBUG2("(TLS) Initiating new session"); ++ ++ /* ++ * Replace X509 store if it is time to update CRLs/certs in ca_path ++ */ ++ if (conf->ca_path_reload_interval > 0 && conf->ca_path_last_reload + conf->ca_path_reload_interval <= request->timestamp) { ++ pthread_mutex_lock(&conf->mutex); ++ /* recheck conf->ca_path_last_reload because it may be inaccurate without mutex */ ++ if (conf->ca_path_last_reload + conf->ca_path_reload_interval <= request->timestamp) { ++ RDEBUG2("Flushing X509 store to re-read data from ca_path dir"); ++ ++ if ((new_cert_store = fr_init_x509_store(conf)) == NULL) { ++ RERROR("(TLS) Error replacing X509 store, out of memory (?)"); ++ } else { ++ if (conf->old_x509_store) X509_STORE_free(conf->old_x509_store); ++ /* ++ * Swap empty store with the old one. ++ */ ++#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) ++ conf->old_x509_store = SSL_CTX_get_cert_store(conf->ctx); ++ /* Bump refcnt so the store is kept allocated till next store replacement */ ++ X509_STORE_up_ref(conf->old_x509_store); ++ SSL_CTX_set_cert_store(conf->ctx, new_cert_store); ++#else ++ /* ++ * We do not use SSL_CTX_set_cert_store() call here because ++ * we are not sure that old X509 store is not in the use by some ++ * thread (i.e. cert check in progress). ++ * Keep it allocated till next store replacement. ++ */ ++ conf->old_x509_store = conf->ctx->cert_store; ++ conf->ctx->cert_store = new_cert_store; ++#endif ++ conf->ca_path_last_reload = request->timestamp; ++ } ++ } ++ pthread_mutex_unlock(&conf->mutex); ++ } + + new_tls = SSL_new(conf->ctx); + if (new_tls == NULL) { +@@ -594,11 +702,36 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU + return NULL; + } + ++#ifdef TLS1_3_VERSION ++ /* ++ * Disallow TLS 1.3 for TTLS, PEAP, and FAST. ++ * ++ * We need another magic configuration option to allow ++ * it. ++ */ ++ if (!allow_tls13 && (conf->max_version == TLS1_3_VERSION)) { ++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ WARN("!! FORCING MAXIMUM TLS VERSION TO TLS 1.2 !!"); ++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ WARN("!! There is no standard for using this EAP method with TLS 1.3"); ++ WARN("!! Please set tls_max_version = \"1.2\""); ++ WARN("!! FreeRADIUS only supports TLS 1.3 for special builds of wpa_supplicant and Windows"); ++ WARN("!! This limitation is likely to change in late 2021."); ++ WARN("!! If you are using this version of FreeRADIUS after 2021, you will probably need to upgrade"); ++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ ++ if (SSL_set_max_proto_version(new_tls, TLS1_2_VERSION) == 0) { ++ tls_error_log(request, "Failed limiting maximum version to TLS 1.2"); ++ return NULL; ++ } ++ } ++#endif ++ + /* We use the SSL's "app_data" to indicate a call-back */ + SSL_set_app_data(new_tls, NULL); + + if ((state = talloc_zero(ctx, tls_session_t)) == NULL) { +- RERROR("Error allocating memory for SSL state"); ++ RERROR("(TLS) Error allocating memory for SSL state"); + return NULL; + } + session_init(state); +@@ -606,6 +739,14 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU + + state->ctx = conf->ctx; + state->ssl = new_tls; ++ state->conf = conf; ++ ++#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER) ++ /* ++ * Set the keylog file if the admin requested it. ++ */ ++ if (getenv("SSLKEYLOGFILE") != NULL) SSL_CTX_set_keylog_callback(state->ctx, tls_keylog_cb); ++#endif + + /* + * Initialize callbacks +@@ -637,6 +778,85 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU + SSL_set_msg_callback_arg(new_tls, state); + SSL_set_info_callback(new_tls, cbtls_info); + ++#if OPENSSL_VERSION_NUMBER >= 0x10100000L ++ /* ++ * Allow policies to load context-specific certificate chains. ++ */ ++ vp = fr_pair_find_by_num(request->config, PW_TLS_SESSION_CERT_FILE, 0, TAG_ANY); ++ if (vp) { ++ VALUE_PAIR *key = fr_pair_find_by_num(request->config, PW_TLS_SESSION_CERT_PRIVATE_KEY_FILE, 0, TAG_ANY); ++ if (!key) key = vp; ++ ++ RDEBUG2("(TLS) Loading session certificate file \"%s\"", vp->vp_strvalue); ++ ++ if (conf->realms) { ++ fr_realm_ctx_t my_r, *r; ++ ++ /* ++ * Use a pre-existing SSL CTX, if ++ * available. Note that due to OpenSSL ++ * issues, this really changes only the ++ * certificate files, and leaves all ++ * other fields alone. e.g. you can't ++ * select a different TLS version. ++ * ++ * This is fine for our purposes in v3. ++ * Due to how we build them, the various ++ * additional SSL_CTXs are identical to ++ * the main one, except for certs. ++ */ ++ my_r.name = vp->vp_strvalue; ++ r = fr_hash_table_finddata(conf->realms, &my_r); ++ if (r) { ++ (void) SSL_set_SSL_CTX(state->ssl, r->ctx); ++ goto after_chain; ++ } ++ ++ /* ++ * Else fall through to trying to dynamically load the certs. ++ */ ++ } ++ ++ if (conf->file_type) { ++ if (SSL_use_certificate_chain_file(state->ssl, vp->vp_strvalue) != 1) { ++ tls_error_log(request, "Failed loading TLS session certificate \"%s\"", ++ vp->vp_strvalue); ++ error: ++ talloc_free(state); ++ return NULL; ++ } ++ } else { ++ if (SSL_use_certificate_file(state->ssl, vp->vp_strvalue, SSL_FILETYPE_ASN1) != 1) { ++ tls_error_log(request, "Failed loading TLS session certificate \"%s\"", ++ vp->vp_strvalue); ++ goto error; ++ } ++ } ++ ++ /* ++ * Note that there is either no password, or it ++ * has to be the same as what's in the ++ * configuration. ++ * ++ * There is just no additional security to ++ * putting a password into the same file system ++ * as the private key. ++ */ ++ if (SSL_use_PrivateKey_file(state->ssl, key->vp_strvalue, SSL_FILETYPE_PEM) != 1) { ++ tls_error_log(request, "Failed loading TLS session certificate \"%s\"", ++ key->vp_strvalue); ++ goto error; ++ } ++ ++ if (SSL_check_private_key(state->ssl) != 1) { ++ tls_error_log(request, "Failed validating TLS session certificate \"%s\"", ++ vp->vp_strvalue); ++ goto error; ++ } ++ } ++after_chain: ++#endif ++ + /* + * In Server mode we only accept. + */ +@@ -646,7 +866,7 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU + * Verify the peer certificate, if asked. + */ + if (client_cert) { +- RDEBUG2("Setting verify mode to require certificate from client"); ++ RDEBUG2("(TLS) Setting verify mode to require certificate from client"); + verify_mode = SSL_VERIFY_PEER; + verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + verify_mode |= SSL_VERIFY_CLIENT_ONCE; +@@ -670,10 +890,41 @@ tls_session_t *tls_new_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *conf, REQU + * just too much. + */ + state->mtu = conf->fragment_size; ++#define EAP_TLS_MAGIC_OVERHEAD (63) ++ ++ /* ++ * If the packet contains an MTU, then use that. We ++ * trust the admin! ++ */ + vp = fr_pair_find_by_num(request->packet->vps, PW_FRAMED_MTU, 0, TAG_ANY); +- if (vp && (vp->vp_integer > 100) && (vp->vp_integer < state->mtu)) { +- state->mtu = vp->vp_integer; ++ if (vp) { ++ if ((vp->vp_integer > 100) && (vp->vp_integer < state->mtu)) { ++ state->mtu = vp->vp_integer; ++ } ++ ++ } else if (request->parent) { ++ /* ++ * If there's a parent request, we look for what ++ * MTU was set there. Then, we use an MTU which ++ * accounts for the extra overhead of nesting EAP ++ * + TLS inside of EAP + TLS. ++ */ ++ vp = fr_pair_find_by_num(request->parent->state, PW_FRAMED_MTU, 0, TAG_ANY); ++ if (vp && (vp->vp_integer > (100 + EAP_TLS_MAGIC_OVERHEAD)) && (vp->vp_integer <= state->mtu)) { ++ state->mtu = vp->vp_integer - EAP_TLS_MAGIC_OVERHEAD; ++ } ++ } ++ ++ /* ++ * Cache / update the Framed-MTU in the session-state ++ * list. ++ */ ++ vp = fr_pair_find_by_num(request->state, PW_FRAMED_MTU, 0, TAG_ANY); ++ if (!vp) { ++ vp = fr_pair_afrom_num(request->state_ctx, PW_FRAMED_MTU, 0); ++ fr_pair_add(&request->state, vp); + } ++ if (vp) vp->vp_integer = state->mtu; + + if (conf->session_cache_enable) state->allow_session_resumption = true; /* otherwise it's false */ + +@@ -697,12 +948,15 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) + { + int err; + +- if (ssn->invalid_hb_used) return 0; ++ if (ssn->invalid_hb_used) { ++ REDEBUG("(TLS) OpenSSL Heartbeat attack detected. Closing connection"); ++ return 0; ++ } + + if (ssn->dirty_in.used > 0) { + err = BIO_write(ssn->into_ssl, ssn->dirty_in.data, ssn->dirty_in.used); + if (err != (int) ssn->dirty_in.used) { +- REDEBUG("TLS - Failed writing %zd bytes to SSL BIO: %d", ssn->dirty_in.used, err); ++ REDEBUG("(TLS) Failed writing %zd bytes to SSL BIO: %d", ssn->dirty_in.used, err); + record_init(&ssn->dirty_in); + return 0; + } +@@ -716,21 +970,23 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) + return 1; + } + +- if (!tls_error_io_log(request, ssn, err, "Failed in " STRINGIFY(__FUNCTION__) " (SSL_read)")) return 0; ++ if (!tls_error_io_log(request, ssn, err, "Failed reading from OpenSSL")) return 0; + + /* Some Extra STATE information for easy debugging */ + if (!ssn->is_init_finished && SSL_is_init_finished(ssn->ssl)) { + VALUE_PAIR *vp; + char const *str_version; + +- RDEBUG2("TLS - Connection Established"); ++ RDEBUG2("(TLS) Connection Established"); + ssn->is_init_finished = true; + + vp = fr_pair_afrom_num(request->state_ctx, PW_TLS_SESSION_CIPHER_SUITE, 0); + if (vp) { + fr_pair_value_strcpy(vp, SSL_CIPHER_get_name(SSL_get_current_cipher(ssn->ssl))); + fr_pair_add(&request->state, vp); ++ RINDENT(); + rdebug_pair(L_DBG_LVL_2, request, vp, NULL); ++ REXDENT(); + } + + switch (ssn->info.version) { +@@ -767,13 +1023,15 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) + if (vp) { + fr_pair_value_strcpy(vp, str_version); + fr_pair_add(&request->state, vp); ++ RINDENT(); + rdebug_pair(L_DBG_LVL_2, request, vp, NULL); ++ REXDENT(); + } + } +- else if (SSL_in_init(ssn->ssl)) { RDEBUG2("TLS - In Handshake Phase"); } +- else if (SSL_in_before(ssn->ssl)) { RDEBUG2("TLS - Before Handshake Phase"); } +- else if (SSL_in_accept_init(ssn->ssl)) { RDEBUG2("TLS - In Accept mode"); } +- else if (SSL_in_connect_init(ssn->ssl)) { RDEBUG2("TLS - In Connect mode"); } ++ else if (SSL_in_init(ssn->ssl)) { RDEBUG2("(TLS) In Handshake Phase"); } ++ else if (SSL_in_before(ssn->ssl)) { RDEBUG2("(TLS) Before Handshake Phase"); } ++ else if (SSL_in_accept_init(ssn->ssl)) { RDEBUG2("(TLS) In Accept mode"); } ++ else if (SSL_in_connect_init(ssn->ssl)) { RDEBUG2("(TLS) In Connect mode"); } + + #if OPENSSL_VERSION_NUMBER >= 0x10001000L + /* +@@ -791,7 +1049,7 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) + * to get the session is a hard fail. + */ + if (!ssn->ssl_session && ssn->is_init_finished) { +- RDEBUG("TLS - Failed getting session"); ++ RDEBUG("(TLS) Failed getting session"); + return 0; + } + } +@@ -805,23 +1063,21 @@ int tls_handshake_recv(REQUEST *request, tls_session_t *ssn) + err = BIO_read(ssn->from_ssl, ssn->dirty_out.data, + sizeof(ssn->dirty_out.data)); + if (err > 0) { +- RDEBUG2("TLS - got %d bytes of data", err); ++ RDEBUG3("(TLS) got %d bytes of data", err); + ssn->dirty_out.used = err; + + } else if (BIO_should_retry(ssn->from_ssl)) { + record_init(&ssn->dirty_in); +- RDEBUG2("TLS - Asking for more data in tunnel."); ++ RDEBUG2("(TLS) Asking for more data in tunnel."); + return 1; + + } else { +- tls_error_log(NULL, "Error reading from SSL BIO"); ++ tls_error_log(NULL, "Error reading from OpenSSL"); + record_init(&ssn->dirty_in); +- RDEBUG2("TLS - Tunnel data is established."); + return 0; + } + } else { +- +- RDEBUG2("TLS - Application data."); ++ RDEBUG2("(TLS) Application data."); + /* Its clean application data, do whatever we want */ + record_init(&ssn->clean_out); + } +@@ -855,13 +1111,12 @@ int tls_handshake_send(REQUEST *request, tls_session_t *ssn) + record_minus(&ssn->clean_in, NULL, written); + + /* Get the dirty data from Bio to send it */ +- err = BIO_read(ssn->from_ssl, ssn->dirty_out.data, +- sizeof(ssn->dirty_out.data)); ++ err = BIO_read(ssn->from_ssl, ssn->dirty_out.data + ssn->dirty_out.used, ++ sizeof(ssn->dirty_out.data) - ssn->dirty_out.used); + if (err > 0) { +- ssn->dirty_out.used = err; ++ ssn->dirty_out.used += err; + } else { +- if (!tls_error_io_log(request, ssn, err, +- "Failed in " STRINGIFY(__FUNCTION__) " (SSL_write)")) { ++ if (!tls_error_io_log(request, ssn, err, "Failed writing to OpenSSL")) { + return 0; + } + } +@@ -963,7 +1218,10 @@ void tls_session_information(tls_session_t *tls_session) + { + char const *str_write_p, *str_version, *str_content_type = ""; + char const *str_details1 = "", *str_details2= ""; ++ char const *details = NULL; + REQUEST *request; ++ VALUE_PAIR *vp; ++ char content_type[16], alert_buf[16]; + char buffer[32]; + + /* +@@ -972,7 +1230,18 @@ void tls_session_information(tls_session_t *tls_session) + */ + if (rad_debug_lvl == 0) return; + +- str_write_p = tls_session->info.origin ? ">>> send" : "<<< recv"; ++ /* ++ * OpenSSL calls this function with 'pseudo' content ++ * types. The user doesn't care about them, so suppress them. ++ */ ++ if (tls_session->info.content_type > UINT8_MAX) return; ++ ++ request = SSL_get_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST); ++ if (!request) return; ++ ++ str_write_p = tls_session->info.origin ? "(TLS) send" : "(TLS) recv"; ++ ++#define FROM_CLIENT (tls_session->info.origin == 0) + + switch (tls_session->info.version) { + case SSL2_VERSION: +@@ -1006,8 +1275,7 @@ void tls_session_information(tls_session_t *tls_session) + break; + } + +- if (tls_session->info.version == SSL3_VERSION || +- tls_session->info.version == TLS1_VERSION) { ++ if (1) { + switch (tls_session->info.content_type) { + case SSL3_RT_CHANGE_CIPHER_SPEC: + str_content_type = "ChangeCipherSpec"; +@@ -1026,7 +1294,8 @@ void tls_session_information(tls_session_t *tls_session) + break; + + default: +- str_content_type = "UnknownContentType"; ++ snprintf(content_type, sizeof(content_type), "content=%d", tls_session->info.content_type); ++ str_content_type = content_type; + break; + } + +@@ -1045,9 +1314,12 @@ void tls_session_information(tls_session_t *tls_session) + } + + str_details2 = " ???"; ++ details = "there is a failure inside the TLS protocol exchange"; ++ + switch (tls_session->info.alert_description) { + case SSL3_AD_CLOSE_NOTIFY: + str_details2 = " close_notify"; ++ details = "the connection has been closed, and no further TLS exchanges will take place"; + break; + + case SSL3_AD_UNEXPECTED_MESSAGE: +@@ -1074,24 +1346,34 @@ void tls_session_information(tls_session_t *tls_session) + str_details2 = " handshake_failure"; + break; + ++ case SSL3_AD_NO_CERTIFICATE: ++ str_details2 = " no_certificate"; ++ details = "the server did not present a certificate to the client"; ++ break; ++ + case SSL3_AD_BAD_CERTIFICATE: + str_details2 = " bad_certificate"; ++ details = "it believes the server certificate is invalid or malformed"; + break; + + case SSL3_AD_UNSUPPORTED_CERTIFICATE: + str_details2 = " unsupported_certificate"; ++ details = "it does not understand the certificate presented by the server"; + break; + + case SSL3_AD_CERTIFICATE_REVOKED: + str_details2 = " certificate_revoked"; ++ details = "it believes that the server certificate has been revoked"; + break; + + case SSL3_AD_CERTIFICATE_EXPIRED: + str_details2 = " certificate_expired"; ++ details = "it believes that the server certificate has expired. Either renew the server certificate, or check the time on the client"; + break; + + case SSL3_AD_CERTIFICATE_UNKNOWN: + str_details2 = " certificate_unknown"; ++ details = "it does not recognize the server certificate"; + break; + + case SSL3_AD_ILLEGAL_PARAMETER: +@@ -1100,6 +1382,7 @@ void tls_session_information(tls_session_t *tls_session) + + case TLS1_AD_UNKNOWN_CA: + str_details2 = " unknown_ca"; ++ details = "it does not recognize the CA used to issue the server certificate. Please update the client so that it knows about the CA"; + break; + + case TLS1_AD_ACCESS_DENIED: +@@ -1120,6 +1403,18 @@ void tls_session_information(tls_session_t *tls_session) + + case TLS1_AD_PROTOCOL_VERSION: + str_details2 = " protocol_version"; ++ details = "the client does not accept the version of TLS negotiated by the server"; ++ ++#ifdef TLS1_3_VERSION ++ /* ++ * Complain about OpenSSL bugs. ++ */ ++ if ((tls_session->info.version > tls_session->conf->max_version) && ++ (rad_debug_lvl > 0)) { ++ WARN("TLS 1.3 has been negotiated even though it was disabled. This is an OpenSSL Bug."); ++ WARN("Please set: cipher_list = \"DEFAULT@SECLEVEL=1\" in the tls {...} section."); ++ } ++#endif + break; + + case TLS1_AD_INSUFFICIENT_SECURITY: +@@ -1137,12 +1432,69 @@ void tls_session_information(tls_session_t *tls_session) + case TLS1_AD_NO_RENEGOTIATION: + str_details2 = " no_renegotiation"; + break; ++ ++#ifdef TLS13_AD_MISSING_EXTENSIONS ++ case TLS13_AD_MISSING_EXTENSIONS: ++ str_details2 = " missing_extensions"; ++ details = "the server did not present a TLS extension which the client expected to be present. Please check the TLS libraries on the client and server for compatibility"; ++ break; ++#endif ++ ++#ifdef TLS13_AD_CERTIFICATE_REQUIRED ++ case TLS13_AD_CERTIFICATE_REQUIRED: ++ str_details2 = " certificate_required"; ++ details = "the server did not present a certificate"; ++ break; ++#endif ++ ++#ifdef TLS1_AD_UNSUPPORTED_EXTENSION ++ case TLS1_AD_UNSUPPORTED_EXTENSION: ++ str_details2 = " unsupported_extension"; ++ details = "the server has sent a TLS message which the client does not recognize. Please check the TLS libraries on the client and server for compatibility"; ++ break; ++#endif ++ ++#ifdef TLS1_AD_CERTIFICATE_UNOBTAINABLE ++ case TLS1_AD_CERTIFICATE_UNOBTAINABLE: ++ str_details2 = " certificate_unobtainable"; ++ break; ++#endif ++ ++#ifdef TLS1_AD_UNRECOGNIZED_NAME ++ case TLS1_AD_UNRECOGNIZED_NAME: ++ str_details2 = " unrecognized_name"; ++ break; ++#endif ++ ++#ifdef TLS1_AD_BAD_CERTIFICATE_STATUS_RESPONSE ++ case TLS1_AD_BAD_CERTIFICATE_STATUS_RESPONSE: ++ str_details2 = " bad_certificate_status_response"; ++ break; ++#endif ++ ++#ifdef TLS1_AD_BAD_CERTIFICATE_HASH_VALUE ++ case TLS1_AD_BAD_CERTIFICATE_HASH_VALUE: ++ str_details2 = " bad_certificate_hash_value"; ++ break; ++#endif ++ ++#ifdef TLS1_AD_UNKNOWN_PSK_IDENTITY ++ case TLS1_AD_UNKNOWN_PSK_IDENTITY: ++ str_details2 = " unknown_psk_identity"; ++ break; ++#endif ++ ++#ifdef TLS1_AD_NO_APPLICATION_PROTOCOL ++ case TLS1_AD_NO_APPLICATION_PROTOCOL: ++ str_details2 = " no_application_protocol"; ++ break; ++#endif + } + } + } + + if (tls_session->info.content_type == SSL3_RT_HANDSHAKE) { +- str_details1 = "???"; ++ str_details1 = ""; + + if (tls_session->info.record_len > 0) switch (tls_session->info.handshake_type) { + case SSL3_MT_HELLO_REQUEST: +@@ -1157,6 +1509,18 @@ void tls_session_information(tls_session_t *tls_session) + str_details1 = ", ServerHello"; + break; + ++#ifdef SSL3_MT_NEWSESSION_TICKET ++ case SSL3_MT_NEWSESSION_TICKET: ++ str_details1 = ", NewSessionTicket"; ++ break; ++#endif ++ ++#ifdef SSL3_MT_ENCRYPTED_EXTENSIONS ++ case SSL3_MT_ENCRYPTED_EXTENSIONS: ++ str_details1 = ", EncryptedExtensions"; ++ break; ++#endif ++ + case SSL3_MT_CERTIFICATE: + str_details1 = ", Certificate"; + break; +@@ -1184,31 +1548,52 @@ void tls_session_information(tls_session_t *tls_session) + case SSL3_MT_FINISHED: + str_details1 = ", Finished"; + break; ++ ++#ifdef SSL3_MT_KEY_UPDATE ++ case SSL3_MT_KEY_UPDATE: ++ str_content_type = "KeyUpdate"; ++ break; ++#endif ++ ++ default: ++ snprintf(alert_buf, sizeof(alert_buf), ", type=%d", tls_session->info.handshake_type); ++ str_details1 = alert_buf; ++ break; + } + } + } + + snprintf(tls_session->info.info_description, + sizeof(tls_session->info.info_description), +- "%s %s%s [length %04lx]%s%s\n", ++ "%s %s%s%s%s", + str_write_p, str_version, str_content_type, +- (unsigned long)tls_session->info.record_len, + str_details1, str_details2); + +- request = SSL_get_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST); +- if (!request) return; ++ /* ++ * Cache the TLS session information in the session-state ++ * list, so it can be accessed by Post-Auth-Type ++ * Client-Lost { ... } ++ */ ++ vp = fr_pair_afrom_num(request->state_ctx, PW_TLS_SESSION_INFORMATION, 0); ++ if (vp) { ++ fr_pair_value_strcpy(vp, tls_session->info.info_description); ++ fr_pair_add(&request->state, vp); ++ } + + RDEBUG2("%s", tls_session->info.info_description); ++ ++ if (FROM_CLIENT && details) RDEBUG2("(TLS) The client is informing us that %s.", details); + } + + static CONF_PARSER cache_config[] = { + { "enable", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, session_cache_enable), "no" }, + +- { "lifetime", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, session_timeout), "24" }, ++ { "lifetime", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, session_lifetime), "24" }, + { "name", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, session_id_name), NULL }, + + { "max_entries", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, session_cache_size), "255" }, + { "persist_dir", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, session_cache_path), NULL }, ++ { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, session_cache_server), NULL }, + CONF_PARSER_TERMINATOR + }; + +@@ -1256,6 +1641,7 @@ static CONF_PARSER tls_server_config[] = { + #ifdef X509_V_FLAG_CRL_CHECK_ALL + { "check_all_crl", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, check_all_crl), "no" }, + #endif ++ { "ca_path_reload_interval", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, ca_path_reload_interval), "0" }, + { "allow_expired_crl", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, allow_expired_crl), NULL }, + { "check_cert_cn", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, check_cert_cn), NULL }, + { "cipher_list", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, cipher_list), NULL }, +@@ -1263,6 +1649,10 @@ static CONF_PARSER tls_server_config[] = { + { "check_cert_issuer", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, check_cert_issuer), NULL }, + { "require_client_cert", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, require_client_cert), NULL }, + ++#if OPENSSL_VERSION_NUMBER >= 0x10100000L ++ { "reject_unknown_intermediate_ca", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, disallow_untrusted), .dflt = "no", }, ++#endif ++ + #if OPENSSL_VERSION_NUMBER >= 0x0090800fL + #ifndef OPENSSL_NO_ECDH + { "ecdh_curve", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, ecdh_curve), "prime256v1" }, +@@ -1281,9 +1671,23 @@ static CONF_PARSER tls_server_config[] = { + { "disable_tlsv1_2", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, disable_tlsv1_2), NULL }, + #endif + +- { "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), "" }, ++ { "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), NULL }, ++ ++ { "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version), ++#if defined(TLS1_2_VERSION) ++ "1.2" ++#elif defined(TLS1_1_VERSION) ++ "1.1" ++#else ++ "1.0" ++#endif ++ }, ++ ++#ifdef TLS1_3_VERSION ++ { "tls13_enable", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, tls13_enable_magic), NULL }, ++#endif + +- { "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version), "1.0" }, ++ { "realm_dir", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, realm_dir), NULL }, + + { "cache", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) cache_config }, + +@@ -1312,6 +1716,7 @@ static CONF_PARSER tls_client_config[] = { + { "check_cert_cn", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, check_cert_cn), NULL }, + { "cipher_list", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, cipher_list), NULL }, + { "check_cert_issuer", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, check_cert_issuer), NULL }, ++ { "ca_path_reload_interval", FR_CONF_OFFSET(PW_TYPE_INTEGER, fr_tls_server_conf_t, ca_path_reload_interval), "0" }, + + #if OPENSSL_VERSION_NUMBER >= 0x0090800fL + #ifndef OPENSSL_NO_ECDH +@@ -1331,14 +1736,25 @@ static CONF_PARSER tls_client_config[] = { + { "disable_tlsv1_2", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, disable_tlsv1_2), NULL }, + #endif + +- { "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), "" }, ++ { "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), NULL }, ++ ++ { "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version), ++#if defined(TLS1_2_VERSION) ++ "1.2" ++#elif defined(TLS1_1_VERSION) ++ "1.1" ++#else ++ "1.0" ++#endif ++ }, + +- { "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version), "1.0" }, ++ { "hostname", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, client_hostname), NULL }, + + CONF_PARSER_TERMINATOR + }; + + ++#if OPENSSL_VERSION_NUMBER < 0x30000000L + /* + * TODO: Check for the type of key exchange * like conf->dh_key + */ +@@ -1349,6 +1765,23 @@ static int load_dh_params(SSL_CTX *ctx, char *file) + + if (!file) return 0; + ++ /* ++ * Prior to trying to load the file, check what OpenSSL will do with it. ++ * ++ * Certain downstreams (such as RHEL) will ignore user-provided dhparams ++ * in FIPS mode, unless the specified parameters are FIPS-approved. ++ * However, since OpenSSL >= 1.1.1 will automatically select parameters ++ * anyways, there's no point in attempting to load them. ++ * ++ * Change suggested by @t8m ++ */ ++#if OPENSSL_VERSION_NUMBER >= 0x10101000L ++ if (FIPS_mode() > 0) { ++ WARN(LOG_PREFIX ": Ignoring user-selected DH parameters in FIPS mode. Using defaults."); ++ return 0; ++ } ++#endif ++ + if ((bio = BIO_new_file(file, "r")) == NULL) { + ERROR(LOG_PREFIX ": Unable to open DH file - %s", file); + return -1; +@@ -1371,6 +1804,7 @@ static int load_dh_params(SSL_CTX *ctx, char *file) + DH_free(dh); + return 0; + } ++#endif + + + /* +@@ -1422,7 +1856,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) + + conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF); + if (!conf) { +- RWDEBUG("Failed to find TLS configuration in session"); ++ RWDEBUG("(TLS) Failed to find TLS configuration in session"); + return 0; + } + +@@ -1439,7 +1873,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) + blob_len = i2d_SSL_SESSION(sess, NULL); + if (blob_len < 1) { + /* something went wrong */ +- if (request) RWDEBUG("Session serialisation failed, couldn't determine required buffer length"); ++ if (request) RWDEBUG("(TLS) Session serialisation failed, could not determine required buffer length"); + return 0; + } + +@@ -1447,14 +1881,14 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) + /* alloc and convert to ASN.1 */ + sess_blob = malloc(blob_len); + if (!sess_blob) { +- RWDEBUG("Session serialisation failed, couldn't allocate buffer (%d bytes)", blob_len); ++ RWDEBUG("(TLS) Session serialisation failed, couldn't allocate buffer (%d bytes)", blob_len); + return 0; + } + /* openssl mutates &p */ + p = sess_blob; + rv = i2d_SSL_SESSION(sess, &p); + if (rv != blob_len) { +- if (request) RWDEBUG("Session serialisation failed"); ++ if (request) RWDEBUG("(TLS) Session serialisation failed"); + goto error; + } + +@@ -1463,7 +1897,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) + conf->session_cache_path, FR_DIR_SEP, buffer); + fd = open(filename, O_RDWR|O_CREAT|O_EXCL, S_IWUSR); + if (fd < 0) { +- if (request) RERROR("Session serialisation failed, failed opening session file %s: %s", ++ if (request) RERROR("(TLS) Session serialisation failed, failed opening session file %s: %s", + filename, fr_syserror(errno)); + goto error; + } +@@ -1486,7 +1920,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) + while (todo > 0) { + rv = write(fd, p, todo); + if (rv < 1) { +- if (request) RWDEBUG("Failed writing session: %s", fr_syserror(errno)); ++ if (request) RWDEBUG("(TLS) Failed writing session: %s", fr_syserror(errno)); + close(fd); + goto error; + } +@@ -1494,7 +1928,7 @@ static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) + todo -= rv; + } + close(fd); +- if (request) RWDEBUG("Wrote session %s to %s (%d bytes)", buffer, filename, blob_len); ++ if (request) RWDEBUG("(TLS) Wrote session %s to %s (%d bytes)", buffer, filename, blob_len); + } + + error: +@@ -1595,7 +2029,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l + + conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF); + if (!conf) { +- RWDEBUG("Failed to find TLS configuration in session"); ++ RWDEBUG("(TLS) Failed to find TLS configuration in session"); + return NULL; + } + +@@ -1617,20 +2051,20 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l + snprintf(filename, sizeof(filename), "%s%c%s.asn1", conf->session_cache_path, FR_DIR_SEP, buffer); + fd = open(filename, O_RDONLY); + if (fd < 0) { +- RWDEBUG("No persisted session file %s: %s", filename, fr_syserror(errno)); ++ RWDEBUG("(TLS) No persisted session file %s: %s", filename, fr_syserror(errno)); + goto error; + } + + rv = fstat(fd, &st); + if (rv < 0) { +- RWDEBUG("Failed stating persisted session file %s: %s", filename, fr_syserror(errno)); ++ RWDEBUG("(TLS) Failed stating persisted session file %s: %s", filename, fr_syserror(errno)); + close(fd); + goto error; + } + + sess_data = talloc_array(NULL, unsigned char, st.st_size); + if (!sess_data) { +- RWDEBUG("Failed allocating buffer for persisted session (%d bytes)", (int) st.st_size); ++ RWDEBUG("(TLS) Failed allocating buffer for persisted session (%d bytes)", (int) st.st_size); + close(fd); + goto error; + } +@@ -1640,7 +2074,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l + while (todo > 0) { + rv = read(fd, q, todo); + if (rv < 1) { +- RWDEBUG("Failed reading persisted session: %s", fr_syserror(errno)); ++ RWDEBUG("(TLS) Failed reading persisted session: %s", fr_syserror(errno)); + close(fd); + goto error; + } +@@ -1664,7 +2098,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l + memcpy(&o, &p, sizeof(o)); + sess = d2i_SSL_SESSION(NULL, o, st.st_size); + if (!sess) { +- RWDEBUG("Failed loading persisted session: %s", ERR_error_string(ERR_get_error(), NULL)); ++ RWDEBUG("(TLS) Failed loading persisted session: %s", ERR_error_string(ERR_get_error(), NULL)); + goto error; + } + +@@ -1674,7 +2108,7 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l + rv = pairlist_read(talloc_ctx, filename, &pairlist, 1); + if (rv < 0) { + /* not safe to un-persist a session w/o VPs */ +- RWDEBUG("Failed loading persisted VPs for session %s", buffer); ++ RWDEBUG("(TLS) Failed loading persisted VPs for session %s", buffer); + SSL_SESSION_free(sess); + sess = NULL; + goto error; +@@ -1708,12 +2142,27 @@ static SSL_SESSION *cbtls_get_session(SSL *ssl, const unsigned char *data, int l + if (vp) { + if ((request->timestamp + vp->vp_integer) > expires) { + vp->vp_integer = expires - request->timestamp; +- RWDEBUG2("Updating Session-Timeout to %u, due to impending certificate expiration", ++ RWDEBUG2("(TLS) Updating Session-Timeout to %u, due to impending certificate expiration", + vp->vp_integer); + } + } + } + ++ /* ++ * Resumption MUST use the same EAP type as from ++ * the original packet. ++ */ ++ vp = fr_pair_find_by_num(pairlist->reply, PW_EAP_TYPE, 0, TAG_ANY); ++ if (vp) { ++ VALUE_PAIR *type = fr_pair_find_by_num(request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY); ++ ++ if (type && (type->vp_integer != vp->vp_integer)) { ++ REDEBUG("Resumption has changed EAP types for session %s", buffer); ++ REDEBUG("Rejecting session due to protocol violations"); ++ goto error; ++ } ++ } ++ + /* move the cached VPs into the session */ + fr_pair_list_mcopy_by_num(talloc_ctx, &vps, &pairlist->reply, 0, 0, TAG_ANY); + +@@ -1733,46 +2182,390 @@ error: + return sess; + } + +-#ifdef HAVE_OPENSSL_OCSP_H +- +-/** Extract components of OCSP responser URL from a certificate +- * +- * @param[in] cert to extract URL from. +- * @param[out] host_out Portion of the URL (must be freed with free()). +- * @param[out] port_out Port portion of the URL (must be freed with free()). +- * @param[out] path_out Path portion of the URL (must be freed with free()). +- * @param[out] is_https Whether the responder should be contacted using https. +- * @return +- * - 0 if no valid URL is contained in the certificate. +- * - 1 if a URL was found and parsed. +- * - -1 if at least one URL was found, but none could be parsed. +- */ +-static int ocsp_parse_cert_url(X509 *cert, char **host_out, char **port_out, +- char **path_out, int *is_https) ++static size_t tls_session_id_binary(SSL_SESSION *ssn, uint8_t *buffer, size_t bufsize) + { +- int i; +- bool found_uri = false; ++#if OPENSSL_VERSION_NUMBER < 0x10001000L ++ size_t size; + +- AUTHORITY_INFO_ACCESS *aia; +- ACCESS_DESCRIPTION *ad; ++ size = ssn->session_id_length; ++ if (size > bufsize) size = bufsize; + +- aia = X509_get_ext_d2i(cert, NID_info_access, NULL, NULL); ++ memcpy(buffer, ssn->session_id, size); ++ return size; ++#else ++ unsigned int size; ++ uint8_t const *p; + +- for (i = 0; i < sk_ACCESS_DESCRIPTION_num(aia); i++) { +- ad = sk_ACCESS_DESCRIPTION_value(aia, i); +- if (OBJ_obj2nid(ad->method) != NID_ad_OCSP) continue; +- if (ad->location->type != GEN_URI) continue; +- found_uri = true; ++ p = SSL_SESSION_get_id(ssn, &size); ++ if (size > bufsize) size = bufsize; + +- if (OCSP_parse_url((char *) ad->location->d.ia5->data, host_out, +- port_out, path_out, is_https)) return 1; +- } +- return found_uri ? -1 : 0; ++ memcpy(buffer, p, size); ++ return size; ++#endif + } + + /* +- * This function sends a OCSP request to a defined OCSP responder +- * and checks the OCSP response for correctness. ++ * From TLS-Cache-Method ++ * ++ * All of the save / clear / load callbacks are done with any ++ * OpenSSL locks *unlocked*. So says the OpenSSL code. ++ */ ++#define CACHE_SAVE (1) ++#define CACHE_LOAD (2) ++#define CACHE_CLEAR (3) ++#define CACHE_REFRESH (4) ++ ++static REQUEST *cache_init_fake_request(fr_tls_server_conf_t const *conf, SSL_SESSION *sess, SSL *ssl, ++ uint8_t const *data, size_t size) ++{ ++ VALUE_PAIR *vp; ++ REQUEST *fake, *request = NULL; ++ uint8_t buffer[MAX_SESSION_SIZE]; ++ ++ if (sess) { ++ size = tls_session_id_binary(sess, buffer, sizeof(buffer)); ++ data = buffer; ++ } ++ ++ /* ++ * We get called essentially at random by OpenSSL, with ++ * no information other than the session ID. As a ++ * result, we have to manually set up our own request. ++ */ ++ if (ssl) request = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST); ++ ++ if (request) { ++ fake = request_alloc_fake(request); ++ } else { ++ fake = request_alloc(NULL); ++ fake->packet = rad_alloc(fake, false); ++ fake->reply = rad_alloc(fake, false); ++ } ++ ++ vp = fr_pair_afrom_num(fake->packet, PW_TLS_SESSION_ID, 0); ++ if (!vp) { ++ talloc_free(fake); ++ return NULL; ++ } ++ ++ fr_pair_value_memcpy(vp, data, size); ++ fr_pair_add(&fake->packet->vps, vp); ++ ++ fake->server = conf->session_cache_server; ++ ++ return fake; ++} ++ ++/* ++ * Clear cached data ++ */ ++static void cbtls_cache_clear(SSL_CTX *ctx, SSL_SESSION *sess) ++{ ++ fr_tls_server_conf_t *conf; ++ REQUEST *fake; ++ ++ conf = (fr_tls_server_conf_t *)SSL_CTX_get_app_data(ctx); ++ if (!conf) { ++ DEBUG(LOG_PREFIX ": Failed to find TLS configuration in session"); ++ return; ++ } ++ ++ /* ++ * Find the SSL ID from the session, and delete it. ++ * ++ * Don't bother with any parent request. We're in a ++ * timer callback, and there is no request available. ++ */ ++ fake = cache_init_fake_request(conf, sess, NULL, NULL, 0); ++ if (!fake) return; ++ ++ /* ++ * Use &request:TLS-Session-Id to clear the cache entry. ++ */ ++ (void) process_post_auth(CACHE_CLEAR, fake); ++ talloc_free(fake); ++ return; ++} ++ ++/* ++ * OpenSSL calls this function in order to save the session ++ * BEFORE it has sent the final TLS success. So our process here ++ * is to say "yes, we saved it", and then do the *actual* saving ++ * after the TLS success has been sent. ++ */ ++static int cbtls_cache_save(UNUSED SSL *ssl, UNUSED SSL_SESSION *sess) ++{ ++ return 0; ++} ++ ++static int cbtls_cache_save_vps(SSL *ssl, SSL_SESSION *sess, VALUE_PAIR *vps) ++{ ++ fr_tls_server_conf_t *conf; ++ VALUE_PAIR *vp; ++ REQUEST *fake = NULL; ++ size_t size, rv; ++ uint8_t *p, *sess_blob = NULL; ++ ++ conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF); ++ if (!conf) return 0; ++ ++ /* ++ * Find the SSL ID from the session, and save it. ++ * ++ * Save anything from the parent request. ++ */ ++ fake = cache_init_fake_request(conf, sess, ssl, NULL, 0); ++ if (!fake) return 0; ++ ++ /* find out what length data we need */ ++ size = i2d_SSL_SESSION(sess, NULL); ++ if (size < 1) return 0; ++ ++ /* Do not convert to TALLOC - it's passed to OpenSSL */ ++ /* alloc and convert to ASN.1 */ ++ MEM(sess_blob = malloc(size)); ++ ++ /* openssl mutates &p */ ++ p = sess_blob; ++ rv = i2d_SSL_SESSION(sess, &p); ++ if (rv != size) goto error; ++ ++ vp = fr_pair_afrom_num(fake->state_ctx, PW_TLS_SESSION_DATA, 0); ++ if (!vp) goto error; ++ ++ fr_pair_value_memcpy(vp, sess_blob, size); ++ fr_pair_add(&fake->state, vp); ++ ++ if (vps) fr_pair_add(&fake->reply->vps, fr_pair_list_copy(fake->reply, vps)); ++ ++ /* ++ * Use &request:TLS-Session-Id to save the ++ * &session-state:TLS-Session-Data values. ++ * ++ * The current &reply: list is the list of VPs which ++ * should be cached. ++ * ++ * Any other attributes which need to be saved can be ++ * read from the &outer.reply: list. ++ */ ++ (void) process_post_auth(CACHE_SAVE, fake); ++ ++error: ++ if (fake) talloc_free(fake); ++ free(sess_blob); ++ ++ return 0; ++} ++ ++static int cbtls_cache_refresh(SSL *ssl, SSL_SESSION *sess) ++{ ++ fr_tls_server_conf_t *conf; ++ REQUEST *fake = NULL; ++ ++ conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF); ++ if (!conf) return 0; ++ ++ /* ++ * Find the SSL ID from the session, and save it. ++ * ++ * Save anything from the parent request. ++ */ ++ fake = cache_init_fake_request(conf, sess, ssl, NULL, 0); ++ if (!fake) return 0; ++ /* ++ * Use &request:TLS-Session-Id to update the cache ++ * entry so that it doesn't not expire. ++ */ ++ (void) process_post_auth(CACHE_REFRESH, fake); ++ ++ talloc_free(fake); ++ ++ return 0; ++} ++ ++#if OPENSSL_VERSION_NUMBER < 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) ++static SSL_SESSION *cbtls_cache_load(SSL *ssl, unsigned char *data, int len, int *copy) ++#else ++static SSL_SESSION *cbtls_cache_load(SSL *ssl, const unsigned char *data, int len, int *copy) ++#endif ++{ ++ fr_tls_server_conf_t *conf; ++ size_t size; ++ uint8_t const *p; ++ VALUE_PAIR *vp, *vps; ++ TALLOC_CTX *talloc_ctx; ++ SSL_SESSION *sess = NULL; ++ REQUEST *fake = NULL; ++ REQUEST *request = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST); ++ char buffer[2 * MAX_SESSION_SIZE + 1]; ++ ++ conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF); ++ if (!conf) return NULL; ++ ++ rad_assert(request); ++ ++ size = len; ++ if (size > MAX_SESSION_SIZE) size = MAX_SESSION_SIZE; ++ ++ if (fr_debug_lvl > 1) { ++ fr_bin2hex(buffer, data, size); ++ RDEBUG2("Peer requested cached session: %s", buffer); ++ } ++ ++ *copy = 0; ++ ++ /* ++ * Take the given SSL ID, and create a fake request. ++ * ++ * Don't bother parenting it from another request. We do ++ * this for a number of reasons. ++ * ++ * One is that rest of the code expects that the VPs will ++ * be added to fr_tls_ex_index_vps. So we don't want to ++ * be poking the request directly, as that will result in ++ * a change of behavior. ++ * ++ * The larger reason is that we do _not_ want to actually ++ * update the reply, until such time as we know that the ++ * user has been authenticated. ++ */ ++ fake = cache_init_fake_request(conf, NULL, NULL, data, size); ++ if (!fake) return 0; ++ ++ /* ++ * Use &request:TLS-Session-Id to load the cached ++ * session. ++ * ++ * The "cache load { ...}" section should put the reply ++ * attributes into the &reply: list, and the ++ * &session-state:TLS-Session-Data attribute. ++ * ++ * Why? Because v4 does it that way, and there aren't ++ * really good reasons for doing it differently. ++ */ ++ (void) process_post_auth(CACHE_LOAD, fake); ++ ++ /* ++ * Enforce client certificate expiration. ++ */ ++ vp = fr_pair_find_by_num(fake->reply->vps, PW_TLS_CLIENT_CERT_EXPIRATION, 0, TAG_ANY); ++ if (vp) { ++ time_t expires; ++ ++ if (ocsp_asn1time_to_epoch(&expires, vp->vp_strvalue) < 0) { ++ RDEBUG2("Failed getting certificate expiration, removing cache entry for session %s - %s", buffer, fr_strerror()); ++ SSL_SESSION_free(sess); ++ sess = NULL; ++ goto error; ++ } ++ ++ if (expires <= request->timestamp) { ++ RDEBUG2("Certificate has expired, removing cache entry for session %s", buffer); ++ SSL_SESSION_free(sess); ++ sess = NULL; ++ goto error; ++ } ++ ++ /* ++ * Account for Session-Timeout, if it's available. ++ */ ++ vp = fr_pair_find_by_num(request->reply->vps, PW_SESSION_TIMEOUT, 0, TAG_ANY); ++ if (vp) { ++ if ((request->timestamp + vp->vp_integer) > expires) { ++ vp->vp_integer = expires - request->timestamp; ++ RWDEBUG2("(TLS) Updating Session-Timeout to %u, due to impending certificate expiration", ++ vp->vp_integer); ++ } ++ } ++ } ++ ++ /* ++ * Try to de-serialize the session data. ++ */ ++ vp = fr_pair_find_by_num(fake->state, PW_TLS_SESSION_DATA, 0, TAG_ANY); ++ if (!vp) { ++ RWDEBUG("(TLS) Failed to find TLS-Session-Data in 'session-state' list for session %s", buffer); ++ goto error; ++ } ++ ++ /* ++ * OpenSSL mutates what's passed in, so we assign sess_data to q, ++ * so the value of q gets mutated, and not the value of sess_data. ++ * ++ * We then need a pointer to hold &q, but it can't be const, because ++ * clang complains about lack of consting in nested pointer types. ++ * ++ * So we memcpy the value of that pointer, to one that ++ * does have a const, which we then pass into d2i_SSL_SESSION *sigh*. ++ */ ++ p = vp->vp_octets; ++ sess = d2i_SSL_SESSION(NULL, &p, vp->vp_length); ++ if (!sess) { ++ RWDEBUG("(TLS) Failed loading persisted session: %s", ERR_error_string(ERR_get_error(), NULL)); ++ goto error; ++ } ++ ++ talloc_ctx = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_TALLOC); ++ vps = NULL; ++ ++ /* move the cached VPs into the session */ ++ fr_pair_list_mcopy_by_num(talloc_ctx, &vps, &fake->reply->vps, 0, 0, TAG_ANY); ++ ++ SSL_SESSION_set_ex_data(sess, fr_tls_ex_index_vps, vps); ++ RDEBUG("Successfully restored session %s", buffer); ++ rdebug_pair_list(L_DBG_LVL_2, request, vps, "reply:"); ++ ++ /* ++ * The "restore VPs from OpenSSL cache" code is ++ * now in eaptls_process() ++ */ ++ ++error: ++ if (fake) talloc_free(fake); ++ ++ return sess; ++} ++ ++#ifdef HAVE_OPENSSL_OCSP_H ++ ++/** Extract components of OCSP responser URL from a certificate ++ * ++ * @param[in] cert to extract URL from. ++ * @param[out] host_out Portion of the URL (must be freed with free()). ++ * @param[out] port_out Port portion of the URL (must be freed with free()). ++ * @param[out] path_out Path portion of the URL (must be freed with free()). ++ * @param[out] is_https Whether the responder should be contacted using https. ++ * @return ++ * - 0 if no valid URL is contained in the certificate. ++ * - 1 if a URL was found and parsed. ++ * - -1 if at least one URL was found, but none could be parsed. ++ */ ++static int ocsp_parse_cert_url(X509 *cert, char **host_out, char **port_out, ++ char **path_out, int *is_https) ++{ ++ int i; ++ bool found_uri = false; ++ ++ AUTHORITY_INFO_ACCESS *aia; ++ ACCESS_DESCRIPTION *ad; ++ ++ aia = X509_get_ext_d2i(cert, NID_info_access, NULL, NULL); ++ ++ for (i = 0; i < sk_ACCESS_DESCRIPTION_num(aia); i++) { ++ ad = sk_ACCESS_DESCRIPTION_value(aia, i); ++ if (OBJ_obj2nid(ad->method) != NID_ad_OCSP) continue; ++ if (ad->location->type != GEN_URI) continue; ++ found_uri = true; ++ ++ if (OCSP_parse_url((char *) ad->location->d.ia5->data, host_out, ++ port_out, path_out, is_https)) return 1; ++ } ++ return found_uri ? -1 : 0; ++} ++ ++/* ++ * This function sends a OCSP request to a defined OCSP responder ++ * and checks the OCSP response for correctness. + */ + + /* Maximum leeway in validity period: default 5 minutes */ +@@ -1811,7 +2604,7 @@ static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issue + VALUE_PAIR *vp; + + if (issuer_cert == NULL) { +- RWDEBUG("Could not get issuer certificate"); ++ RWDEBUG("(TLS) Could not get issuer certificate"); + goto skipped; + } + +@@ -1836,7 +2629,7 @@ static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issue + /* Reading the libssl src, they do a strdup on the URL, so it could of been const *sigh* */ + OCSP_parse_url(url, &host, &port, &path, &use_ssl); + if (!host || !port || !path) { +- RWDEBUG("ocsp: Host or port or path missing from configured URL \"%s\". Not doing OCSP", url); ++ RWDEBUG("(TLS) ocsp: Host or port or path missing from configured URL \"%s\". Not doing OCSP", url); + goto skipped; + } + } else { +@@ -1845,15 +2638,15 @@ static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issue + ret = ocsp_parse_cert_url(client_cert, &host, &port, &path, &use_ssl); + switch (ret) { + case -1: +- RWDEBUG("ocsp: Invalid URL in certificate. Not doing OCSP"); ++ RWDEBUG("(TLS) ocsp: Invalid URL in certificate. Not doing OCSP"); + break; + + case 0: + if (conf->ocsp_url) { +- RWDEBUG("ocsp: No OCSP URL in certificate, falling back to configured URL"); ++ RWDEBUG("(TLS) ocsp: No OCSP URL in certificate, falling back to configured URL"); + goto use_ocsp_url; + } +- RWDEBUG("ocsp: No OCSP URL in certificate. Not doing OCSP"); ++ RWDEBUG("(TLS) ocsp: No OCSP URL in certificate. Not doing OCSP"); + goto skipped; + + case 1: +@@ -1865,7 +2658,7 @@ static ocsp_status_t ocsp_check(REQUEST *request, X509_STORE *store, X509 *issue + + /* Check host and port length are sane, then create Host: HTTP header */ + if ((strlen(host) + strlen(port) + 2) > sizeof(hostheader)) { +- RWDEBUG("ocsp: Host and port too long"); ++ RWDEBUG("(TLS) ocsp: Host and port too long"); + goto skipped; + } + snprintf(hostheader, sizeof(hostheader), "%s:%s", host, port); +@@ -2038,15 +2831,15 @@ ocsp_end: + vp = pair_make_request("TLS-OCSP-Cert-Valid", NULL, T_OP_SET); + vp->vp_integer = 2; /* skipped */ + if (conf->ocsp_softfail) { +- RWDEBUG("ocsp: Unable to check certificate, assuming it's valid"); +- RWDEBUG("ocsp: This may be insecure"); ++ RWDEBUG("(TLS) ocsp: Unable to check certificate, assuming it's valid"); ++ RWDEBUG("(TLS) ocsp: This may be insecure"); + + /* Remove OpenSSL errors from queue or handshake will fail */ + while (ERR_get_error()); + + ocsp_status = OCSP_STATUS_SKIPPED; + } else { +- REDEBUG("ocsp: Unable to check certificate, failing"); ++ REDEBUG("(TLS) ocsp: Unable to check certificate, failing"); + ocsp_status = OCSP_STATUS_FAILED; + } + break; +@@ -2054,7 +2847,7 @@ ocsp_end: + default: + vp = pair_make_request("TLS-OCSP-Cert-Valid", NULL, T_OP_SET); + vp->vp_integer = 0; /* no */ +- REDEBUG("ocsp: Certificate has been expired/revoked"); ++ REDEBUG("(TLS) ocsp: Certificate has been expired/revoked"); + break; + } + +@@ -2087,6 +2880,10 @@ static char const *cert_attr_names[9][2] = { + #define FR_TLS_SAN_UPN (7) + #define FR_TLS_VALID_SINCE (8) + ++static const char *cert_names[2] = { ++ "client", "server", ++}; ++ + /* + * Before trusting a certificate, you must make sure that the + * certificate is 'valid'. There are several steps that your +@@ -2183,8 +2980,8 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) + buf[0] = '\0'; + sn = X509_get_serialNumber(client_cert); + +- RDEBUG2("TLS - Creating attributes from certificate OIDs"); +- RINDENT(); ++ RDEBUG2("(TLS) Creating attributes from %s certificate", cert_names[lookup]); ++ RINDENT(); + + /* + * For this next bit, we create the attributes *only* if +@@ -2328,8 +3125,14 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) + + if (!my_ok) { + char const *p = X509_verify_cert_error_string(err); +- RERROR("SSL says error %d : %s", err, p); ++ RERROR("(TLS) OpenSSL says error %d : %s", err, p); + REXDENT(); ++ ++ /* ++ * Copy certs even on failure so that they can be logged. ++ */ ++ if (certs && request) fr_pair_add(&request->packet->vps, fr_pair_list_copy(request->packet, *certs)); ++ + return my_ok; + } + +@@ -2405,7 +3208,6 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) + fr_bin2hex(value + 2, srcp, asn1len); + } + +- + vp = fr_pair_make(talloc_ctx, certs, attribute, value, T_OP_ADD); + if (!vp) { + RDEBUG3("Skipping %s += '%s'. Please check that both the " +@@ -2446,20 +3248,28 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) + + switch (X509_STORE_CTX_get_error(ctx)) { + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: +- RERROR("issuer=%s", issuer); ++ RERROR("(TLS) unable to get issuer certificate for issuer=%s", issuer); + break; + + case X509_V_ERR_CERT_NOT_YET_VALID: ++ RERROR("(TLS) Failed with certificate not yet valid."); ++ break; ++ + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: +- RERROR("notBefore="); ++ RERROR("(TLS) Failed with error in certificate 'not before' field."); + #if 0 + ASN1_TIME_print(bio_err, X509_get_notBefore(ctx->current_cert)); + #endif + break; + + case X509_V_ERR_CERT_HAS_EXPIRED: ++ RERROR("(TLS) Failed with certificate has expired."); ++ break; ++ + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: +- RERROR("notAfter="); ++ RERROR("(TLS) Failed with err in certificate 'no after' field.."); ++ break; ++ + #if 0 + ASN1_TIME_print(bio_err, X509_get_notAfter(ctx->current_cert)); + #endif +@@ -2471,12 +3281,49 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) + * checks. + */ + if (depth == 0) { ++ tls_session_t *ssn = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_SSN); ++#if OPENSSL_VERSION_NUMBER >= 0x10100000L ++ STACK_OF(X509)* untrusted = NULL; ++#endif ++ ++ rad_assert(ssn != NULL); ++ ++#if OPENSSL_VERSION_NUMBER >= 0x10100000L ++ /* ++ * See if there are any untrusted certificates. ++ * If so, complain about them. ++ */ ++ untrusted = X509_STORE_CTX_get0_untrusted(ctx); ++ if (untrusted) { ++ if (conf->disallow_untrusted || RDEBUG_ENABLED2) { ++ int i; ++ ++ WARN("Certificate chain - %i cert(s) untrusted", ++ X509_STORE_CTX_get_num_untrusted(ctx)); ++ for (i = sk_X509_num(untrusted); i > 0 ; i--) { ++ X509 *this_cert = sk_X509_value(untrusted, i - 1); ++ ++ X509_NAME_oneline(X509_get_subject_name(this_cert), subject, sizeof(subject)); ++ subject[sizeof(subject) - 1] = '\0'; ++ ++ WARN("(TLS) untrusted certificate with depth [%i] subject name %s", ++ i - 1, subject); ++ } ++ } ++ ++ if (conf->disallow_untrusted) { ++ AUTH(LOG_PREFIX ": There are untrusted certificates in the certificate chain. Rejecting."); ++ my_ok = 0; ++ } ++ } ++#endif ++ + /* + * If the conf tells us to, check cert issuer + * against the specified value and fail + * verification if they don't match. + */ +- if (conf->check_cert_issuer && ++ if (my_ok && conf->check_cert_issuer && + (strcmp(issuer, conf->check_cert_issuer) != 0)) { + AUTH(LOG_PREFIX ": Certificate issuer (%s) does not match specified value (%s)!", + issuer, conf->check_cert_issuer); +@@ -2595,45 +3442,54 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx) + unlink(filename); + break; + } ++ ++ /* ++ * Track that we've verified the client certificate. ++ */ ++ ssn->client_cert_ok = (my_ok == 1); + } /* depth == 0 */ + ++ /* ++ * Copy certs to request even on failure, so that the ++ * user can log them. ++ */ + if (certs && request && !my_ok) { + fr_pair_add(&request->packet->vps, fr_pair_list_copy(request->packet, *certs)); + } + + if (RDEBUG_ENABLED3) { +- RDEBUG3("chain-depth : %d", depth); +- RDEBUG3("error : %d", err); ++ RDEBUG3("(TLS) chain-depth : %d", depth); ++ RDEBUG3("(TLS) error : %d", err); + + if (identity) RDEBUG3("identity : %s", *identity); +- RDEBUG3("common name : %s", common_name); +- RDEBUG3("subject : %s", subject); +- RDEBUG3("issuer : %s", issuer); +- RDEBUG3("verify return : %d", my_ok); ++ RDEBUG3("(TLS) common name : %s", common_name); ++ RDEBUG3("(TLS) subject : %s", subject); ++ RDEBUG3("(TLS) issuer : %s", issuer); ++ RDEBUG3("(TLS) verify return : %d", my_ok); + } + + return (my_ok != 0); + } + + +-#ifdef HAVE_OPENSSL_OCSP_H + /* +- * Create Global X509 revocation store and use it to verify +- * OCSP responses ++ * Configure a X509 CA store to verify OCSP or client repsonses + * + * - Load the trusted CAs + * - Load the trusted issuer certificates ++ * - Configure CRLs check if needed + */ +-static X509_STORE *init_revocation_store(fr_tls_server_conf_t *conf) ++X509_STORE *fr_init_x509_store(fr_tls_server_conf_t *conf) + { +- X509_STORE *store = NULL; ++ X509_STORE *store = X509_STORE_new(); + +- store = X509_STORE_new(); ++ if (store == NULL) return NULL; + + /* Load the CAs we trust */ + if (conf->ca_file || conf->ca_path) + if (!X509_STORE_load_locations(store, conf->ca_file, conf->ca_path)) { + tls_error_log(NULL, "Error reading Trusted root CA list \"%s\"", conf->ca_file); ++ X509_STORE_free(store); + return NULL; + } + +@@ -2647,36 +3503,58 @@ static X509_STORE *init_revocation_store(fr_tls_server_conf_t *conf) + #endif + return store; + } +-#endif /* HAVE_OPENSSL_OCSP_H */ + + #if OPENSSL_VERSION_NUMBER >= 0x0090800fL + #ifndef OPENSSL_NO_ECDH + static int set_ecdh_curve(SSL_CTX *ctx, char const *ecdh_curve, bool disable_single_dh_use) + { +- int nid; +- EC_KEY *ecdh; ++ if (!disable_single_dh_use) { ++ SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE); ++ } + +- if (!ecdh_curve || !*ecdh_curve) return 0; ++ if (!ecdh_curve) return 0; + +- nid = OBJ_sn2nid(ecdh_curve); +- if (!nid) { +- ERROR(LOG_PREFIX ": Unknown ecdh_curve \"%s\"", ecdh_curve); +- return -1; +- } ++#if OPENSSL_VERSION_NUMBER >= 0x1000200fL ++ /* ++ * A colon-separated list of curves. ++ */ ++ if (*ecdh_curve) { ++ char *list; + +- ecdh = EC_KEY_new_by_curve_name(nid); +- if (!ecdh) { +- ERROR(LOG_PREFIX ": Unable to create new curve \"%s\"", ecdh_curve); +- return -1; ++ memcpy(&list, &ecdh_curve, sizeof(list)); /* const issues */ ++ ++ if (SSL_CTX_set1_curves_list(ctx, list) == 0) { ++ ERROR(LOG_PREFIX ": Unknown ecdh_curve \"%s\"", ecdh_curve); ++ return -1; ++ } + } + +- SSL_CTX_set_tmp_ecdh(ctx, ecdh); ++ (void) SSL_CTX_set_ecdh_auto(ctx, 1); ++#else ++ /* ++ * Use APIs for older versions of OpenSSL. ++ */ ++ { ++ int nid; ++ EC_KEY *ecdh; ++ ++ nid = OBJ_sn2nid(ecdh_curve); ++ if (!nid) { ++ ERROR(LOG_PREFIX ": Unknown ecdh_curve \"%s\"", ecdh_curve); ++ return -1; ++ } ++ ++ ecdh = EC_KEY_new_by_curve_name(nid); ++ if (!ecdh) { ++ ERROR(LOG_PREFIX ": Unable to create new curve \"%s\"", ecdh_curve); ++ return -1; ++ } ++ ++ SSL_CTX_set_tmp_ecdh(ctx, ecdh); + +- if (!disable_single_dh_use) { +- SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE); ++ EC_KEY_free(ecdh); + } +- +- EC_KEY_free(ecdh); ++#endif + + return 0; + } +@@ -2708,9 +3586,31 @@ int tls_global_init(bool spawn_flag, bool check) + * and we don't want to have tls.c depend on globals. + */ + if (spawn_flag && !check && (tls_mutexes_init() < 0)) { +- ERROR("FATAL: Failed to set up SSL mutexes"); ++ ERROR("(TLS) FATAL: Failed to set up SSL mutexes"); ++ return -1; ++ } ++ ++#if OPENSSL_VERSION_NUMBER >= 0x30000000L ++ /* ++ * Load the default provider for most algorithms ++ */ ++ openssl_default_provider = OSSL_PROVIDER_load(NULL, "default"); ++ if (!openssl_default_provider) { ++ ERROR("(TLS) Failed loading default provider"); ++ return -1; ++ } ++ ++ /* ++ * Needed for MD4 ++ * ++ * https://www.openssl.org/docs/man3.0/man7/migration_guide.html#Legacy-Algorithms ++ */ ++ openssl_legacy_provider = OSSL_PROVIDER_load(NULL, "legacy"); ++ if (!openssl_legacy_provider) { ++ ERROR("(TLS) Failed loading legacy provider"); + return -1; + } ++#endif + + return 0; + } +@@ -2777,6 +3677,19 @@ void tls_global_cleanup(void) + #ifndef OPENSSL_NO_ENGINE + ENGINE_cleanup(); + #endif ++ ++#if OPENSSL_VERSION_NUMBER >= 0x30000000L ++ if (openssl_default_provider && !OSSL_PROVIDER_unload(openssl_default_provider)) { ++ ERROR("Failed unloading default provider"); ++ } ++ openssl_default_provider = NULL; ++ ++ if (openssl_legacy_provider && !OSSL_PROVIDER_unload(openssl_legacy_provider)) { ++ ERROR("Failed unloading legacy provider"); ++ } ++ openssl_legacy_provider = NULL; ++#endif ++ + CONF_modules_unload(1); + ERR_free_strings(); + EVP_cleanup(); +@@ -2797,9 +3710,6 @@ static const FR_NAME_NUMBER version2int[] = { + #endif + #ifdef TLS1_3_VERSION + { "1.3", TLS1_3_VERSION }, +-#endif +-#ifdef TLS1_4_VERSION +- { "1.4", TLS1_4_VERSION }, + #endif + { NULL, 0 } + }; +@@ -2816,18 +3726,18 @@ static const FR_NAME_NUMBER version2int[] = { + * - Load the Private key & the certificate + * - Set the Context options & Verify options + */ +-SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client) ++SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client, char const *chain_file, char const *private_key_file) + { + SSL_CTX *ctx; + X509_STORE *certstore; + int verify_mode = SSL_VERIFY_NONE; +- int ctx_options = 0; +- int ctx_tls_versions = 0; ++ int ctx_options = 0, ctx_available = 0; + int type; + #ifdef CHECK_FOR_PSK_CERTS + bool psk_and_certs = false; + #endif +- bool insecure_tls_version = false; ++ int min_version; ++ int max_version; + + /* + * SHA256 is in all versions of OpenSSL, but isn't +@@ -2840,7 +3750,7 @@ SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client) + + ctx = SSL_CTX_new(SSLv23_method()); /* which is really "all known SSL / TLS methods". Idiots. */ + if (!ctx) { +- tls_error_log(NULL, "Failed creating TLS context"); ++ tls_error_log(NULL, "Failed creating OpenSSL context"); + return NULL; + } + +@@ -3033,39 +3943,55 @@ SSL_CTX *tls_init_ctx(fr_tls_server_conf_t *conf, int client) + * the cert chain needs to be given in PEM from + * openSSL.org + */ +- if (!conf->certificate_file) goto load_ca; ++ if (!chain_file) chain_file = conf->certificate_file; ++ if (!chain_file) goto load_ca; + + if (type == SSL_FILETYPE_PEM) { +- if (!(SSL_CTX_use_certificate_chain_file(ctx, conf->certificate_file))) { ++ if (!(SSL_CTX_use_certificate_chain_file(ctx, chain_file))) { + tls_error_log(NULL, "Failed reading certificate file \"%s\"", +- conf->certificate_file); ++ chain_file); + return NULL; + } + +- } else if (!(SSL_CTX_use_certificate_file(ctx, conf->certificate_file, type))) { ++ } else if (!(SSL_CTX_use_certificate_file(ctx, chain_file, type))) { + tls_error_log(NULL, "Failed reading certificate file \"%s\"", +- conf->certificate_file); ++ chain_file); + return NULL; + } + +- /* Load the CAs we trust */ + load_ca: ++ /* ++ * Load the CAs we trust and configure CRL checks if needed ++ */ + #if defined(X509_V_FLAG_PARTIAL_CHAIN) + X509_STORE_set_flags(SSL_CTX_get_cert_store(ctx), X509_V_FLAG_PARTIAL_CHAIN); + #endif + if (conf->ca_file || conf->ca_path) { +- if (!SSL_CTX_load_verify_locations(ctx, conf->ca_file, conf->ca_path)) { +- tls_error_log(NULL, "Failed reading Trusted root CA list \"%s\"", +- conf->ca_file); +- return NULL; +- } ++ if ((certstore = fr_init_x509_store(conf)) == NULL ) return NULL; ++ SSL_CTX_set_cert_store(ctx, certstore); + } ++ + if (conf->ca_file && *conf->ca_file) SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(conf->ca_file)); + +- if (conf->private_key_file) { +- if (!(SSL_CTX_use_PrivateKey_file(ctx, conf->private_key_file, type))) { ++ conf->ca_path_last_reload = time(NULL); ++ conf->old_x509_store = NULL; ++ ++ /* ++ * Disable reloading of cert store if we're not using CA path ++ */ ++ if (!conf->ca_path) conf->ca_path_reload_interval = 0; ++ ++ if (conf->ca_path_reload_interval > 0 && conf->ca_path_reload_interval < 300) { ++ DEBUG2("ca_path_reload_interval is set too low, reset it to 300"); ++ conf->ca_path_reload_interval = 300; ++ } ++ ++ /* Load private key */ ++ if (!private_key_file) private_key_file = conf->private_key_file; ++ if (private_key_file) { ++ if (!(SSL_CTX_use_PrivateKey_file(ctx, private_key_file, type))) { + tls_error_log(NULL, "Failed reading private key file \"%s\"", +- conf->private_key_file); ++ private_key_file); + return NULL; + } + +@@ -3088,6 +4014,18 @@ post_ca: + ctx_options |= SSL_OP_NO_SSLv2; + ctx_options |= SSL_OP_NO_SSLv3; + ++ /* ++ * If set then dummy Change Cipher Spec (CCS) messages are sent in ++ * TLSv1.3. This has the effect of making TLSv1.3 look more like TLSv1.2 ++ * so that middleboxes that do not understand TLSv1.3 will not drop ++ * the connection. This isn't needed for EAP-TLS, so we disable it. ++ * ++ * EAP (hopefully) does not have middlebox deployments ++ */ ++#ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT ++ ctx_options &= ~SSL_OP_ENABLE_MIDDLEBOX_COMPAT; ++#endif ++ + /* + * SSL_CTX_set_(min|max)_proto_version was included in OpenSSL 1.1.0 + * +@@ -3095,168 +4033,213 @@ post_ca: + * below, so we don't need to check for them explicitly. + * + * TLS1_3_VERSION is available in OpenSSL 1.1.1. +- * +- * TLS1_4_VERSION in speculative. + */ +- { +- int min_version = 0; +- int max_version = 0; + ++ /* ++ * Get the max version from the configuration files. ++ */ ++ if (conf->tls_max_version && *conf->tls_max_version) { ++ max_version = fr_str2int(version2int, conf->tls_max_version, 0); ++ if (!max_version) { ++ ERROR("Invalid value for tls_max_version '%s'", conf->tls_max_version); ++ return NULL; ++ } ++ } else { + /* +- * Get the max version. ++ * Pick the maximum version available at compile ++ * time. + */ +- if (conf->tls_max_version && *conf->tls_max_version) { +- max_version = fr_str2int(version2int, conf->tls_max_version, 0); +- if (!max_version) { +- ERROR("Invalid value for tls_max_version '%s'", conf->tls_max_version); +- return NULL; +- } +- } else { +- /* +- * Pick the maximum one we know about. +- */ +-#ifdef TLS1_4_VERSION +- max_version = TLS1_2_VERSION; /* NOT a typo! EAP methods for TLS 1.4 are NOT finished */ +-#elif defined(TLS1_3_VERSION) +- max_version = TLS1_2_VERSION; /* NOT a typo! EAP methods for TLS 1.3 are NOT finished */ ++#if defined(TLS1_3_VERSION) ++ max_version = TLS1_2_VERSION; /* yes, we only use TLS 1.3 if it's EXPLICITELY ENABLED */ + #elif defined(TLS1_2_VERSION) +- max_version = TLS1_2_VERSION; ++ max_version = TLS1_2_VERSION; + #elif defined(TLS1_1_VERSION) +- max_version = TLS1_1_VERSION; ++ max_version = TLS1_1_VERSION; + #else +- max_version = TLS1_VERSION; ++ max_version = TLS1_VERSION; + #endif +- } ++ } + ++ /* ++ * Get the min version from the configuration files. ++ */ ++ if (conf->tls_min_version && *conf->tls_min_version) { ++ min_version = fr_str2int(version2int, conf->tls_min_version, 0); ++ if (!min_version) { ++ ERROR("Unknown or unsupported value for tls_min_version '%s'", conf->tls_min_version); ++ return NULL; ++ } ++ } else { + /* +- * Set these for the rest of the code. ++ * Allow TLS 1.0. It is horribly insecure, but ++ * some systems still use it. + */ ++ min_version = TLS1_VERSION; ++ } ++ ++ /* ++ * Compare the two. ++ */ ++ if ((min_version > max_version) || (max_version < min_version)) { ++ ERROR("tls_min_version '%s' must be <= tls_max_version '%s'", ++ conf->tls_min_version, conf->tls_max_version); ++ return NULL; ++ } ++ ++#ifdef CHECK_FOR_PSK_CERTS ++ /* ++ * Disable TLS 1.3 when using PSKs and certs. ++ * This doesn't work. ++ * ++ * It's best to disable the offending ++ * configuration and warn about it. The ++ * alternative is to have the admin wonder why it ++ * doesn't work. ++ * ++ * Note that the admin can over-ride this by ++ * setting "min_version = max_version = 1.3" ++ */ ++ if (psk_and_certs && ++ (min_version < TLS1_3_VERSION) && (max_version >= TLS1_3_VERSION)) { ++ max_version = TLS1_2_VERSION; ++ radlog(L_DBG | L_WARN, "Disabling TLS 1.3 due to PSK and certificates being configured simultaneously. This is not supported by the standards."); ++ } ++#endif ++ ++ /* ++ * No one should be using TLS 1.0 or TLS 1.1 any more ++ * ++ * If TLS1.2 isn't defined by OpenSSL, then we _know_ ++ * it's an insecure version of OpenSSL. ++ */ + #ifdef TLS1_2_VERSION +- if (max_version < TLS1_2_VERSION) { +- conf->disable_tlsv1_2 = true; +- } ++ if (max_version < TLS1_2_VERSION) + #endif +-#ifdef TLS1_1_VERSION +- if (max_version < TLS1_1_VERSION) { +- conf->disable_tlsv1_1 = true; ++ { ++ if (rad_debug_lvl) { ++ WARN(LOG_PREFIX ": The configuration allows TLS 1.0 and/or TLS 1.1. We STRONGLY recommned using only TLS 1.2 for security"); ++ WARN(LOG_PREFIX ": Please set: tls_min_version = '1.2'"); + } +-#endif ++ } + +- /* +- * Get the min version. +- */ +- if (conf->tls_min_version && *conf->tls_min_version) { +- min_version = fr_str2int(version2int, conf->tls_min_version, 0); +- if (!min_version) { +- ERROR("Unknown or unsupported value for tls_min_version '%s'", conf->tls_min_version); +- return NULL; +- } +- } else { +- min_version = TLS1_VERSION; ++#ifdef SSL_OP_NO_TLSv1 ++ /* ++ * Check min / max against the old-style "disable" flag. ++ */ ++ if (conf->disable_tlsv1) { ++ if (min_version == TLS1_VERSION) { ++ ERROR(LOG_PREFIX ": 'disable_tlsv1' is set, but 'min_version = 1.0'. These cannot both be true."); ++ return NULL; + } +- +- /* +- * Compare the two. +- */ +- if (min_version > max_version) { +- ERROR("tls_min_version '%s' must be <= tls_max_version '%s'", +- conf->tls_min_version, conf->tls_max_version); ++ if (max_version == TLS1_VERSION) { ++ ERROR(LOG_PREFIX ": 'disable_tlsv1' is set, but 'max_version = 1.0'. These cannot both be true."); + return NULL; + } ++ ctx_options |= SSL_OP_NO_TLSv1; ++ } + +-#if OPENSSL_VERSION_NUMBER >= 0x10100000L +-#ifdef CHECK_FOR_PSK_CERTS +- /* +- * Disable TLS 1.3 when using PSKs and certs. +- * This doesn't work. +- * +- * It's best to disable the offending +- * configuration and warn about it. The +- * alternative is to have the admin wonder why it +- * doesn't work. +- * +- * Note that the admin can over-ride this by +- * setting "min_version = max_version = 1.3" +- */ +- if (psk_and_certs && +- (min_version < TLS1_3_VERSION) && (max_version >= TLS1_3_VERSION)) { +- max_version = TLS1_2_VERSION; +- radlog(L_DBG | L_WARN, "Disabling TLS 1.3 due to PSK and certificates being configured simultaneously. This is not supported by the standards."); +- } ++ if (min_version > TLS1_VERSION) ctx_options |= SSL_OP_NO_TLSv1; ++ ++ ctx_available |= SSL_OP_NO_TLSv1; + #endif + +- if (!SSL_CTX_set_max_proto_version(ctx, max_version)) { +- ERROR("Failed setting TLS maximum version"); ++#ifdef SSL_OP_NO_TLSv1_1 ++ /* ++ * Check min / max against the old-style "disable" flag. ++ */ ++ if (conf->disable_tlsv1_1) { ++ if (min_version <= TLS1_1_VERSION) { ++ ERROR(LOG_PREFIX ": 'disable_tlsv1_1' is set, but 'min_version <= 1.1'. These cannot both be true."); + return NULL; + } +- +- if (!SSL_CTX_set_min_proto_version(ctx, min_version)) { +- ERROR("Failed setting TLS minimum version"); ++ if (max_version == TLS1_1_VERSION) { ++ ERROR(LOG_PREFIX ": 'disable_tlsv1_1' is set, but 'max_version = 1.1'. These cannot both be true."); + return NULL; + } ++ ctx_options |= SSL_OP_NO_TLSv1_1; ++ } + +- /* +- * No one should be using TLS 1.0 or TLS 1.1 any more +- */ +- if (min_version < TLS1_2_VERSION) insecure_tls_version = true; +-#else /* OpenSSL version < 1.1.0 */ ++ if (min_version > TLS1_1_VERSION) ctx_options |= SSL_OP_NO_TLSv1_1; ++ if (max_version < TLS1_1_VERSION) ctx_options |= SSL_OP_NO_TLSv1_1; + +-#ifdef SSL_OP_NO_TLSv1 +- insecure_tls_version |= (conf->disable_tlsv1 == false); +-#endif +-#ifdef SSL_OP_NO_TLSv1_1 +- insecure_tls_version |= (conf->disable_tlsv1_1 == false); ++ ctx_available |= SSL_OP_NO_TLSv1_1; + #endif +-#endif /* OpenSSL version ? 1.1.0 */ + +- if (rad_debug_lvl && insecure_tls_version) { +- WARN("The configuration allows TLS 1.0 and/or TLS 1.1. We STRONGLY recommned using only TLS 1.2 for security"); +- WARN("Please set: tls_min_version = \"1.2\""); ++#ifdef SSL_OP_NO_TLSv1_2 ++ /* ++ * Check min / max against the old-style "disable" flag. ++ */ ++ if (conf->disable_tlsv1_2) { ++ if (min_version <= TLS1_2_VERSION) { ++ ERROR(LOG_PREFIX ": 'disable_tlsv1_2' is set, but 'min_version <= 1.2'. These cannot both be true."); ++ return NULL; + } ++ if (max_version == TLS1_2_VERSION) { ++ ERROR(LOG_PREFIX ": 'disable_tlsv1_1' is set, but 'max_version = 1.2'. These cannot both be true."); ++ return NULL; ++ } ++ ctx_options |= SSL_OP_NO_TLSv1_2; + } ++ ctx_available |= SSL_OP_NO_TLSv1_2; ++ ++ if (min_version > TLS1_2_VERSION) ctx_options |= SSL_OP_NO_TLSv1_2; ++ if (max_version < TLS1_2_VERSION) ctx_options |= SSL_OP_NO_TLSv1_2; ++#endif ++ ++#ifdef SSL_OP_NO_TLSv1_3 ++ ctx_available |= SSL_OP_NO_TLSv1_3; ++ if (min_version > TLS1_3_VERSION) ctx_options |= SSL_OP_NO_TLSv1_3; ++ if (max_version < TLS1_3_VERSION) ctx_options |= SSL_OP_NO_TLSv1_3; ++#endif + + /* +- * For historical config compatibility, we also allow +- * these, but complain if the admin uses them. ++ * Tell OpenSSL PRETTY PLEASE MAY WE USE TLS 1.1. ++ * ++ * Because saying "use TLS 1.1" isn't enough. We have to ++ * send it flowers and cake. + */ +-#ifdef SSL_OP_NO_TLSv1 +- if (conf->disable_tlsv1) { +- ctx_options |= SSL_OP_NO_TLSv1; +-#if OPENSSL_VERSION_NUMBER >= 0x10100000L +- WARN("Please use tls_min_version and tls_max_version instead of disable_tlsv1"); +-#endif ++ if ((min_version <= TLS1_1_VERSION) && conf->cipher_list && ++ !strstr(conf->cipher_list, "DEFAULT@SECLEVEL=1")) { ++ WARN(LOG_PREFIX ": In order to use TLS 1.0 and/or TLS 1.1, you likely need to set: cipher_list = \"DEFAULT@SECLEVEL=1\""); + } + +- ctx_tls_versions |= SSL_OP_NO_TLSv1; +-#endif +-#ifdef SSL_OP_NO_TLSv1_1 +- if (conf->disable_tlsv1_1) { +- ctx_options |= SSL_OP_NO_TLSv1_1; + #if OPENSSL_VERSION_NUMBER >= 0x10100000L +- WARN("Please use tls_min_version and tls_max_version instead of disable_tlsv1_2"); +-#endif ++ if (conf->disable_tlsv1) { ++ WARN(LOG_PREFIX ": Please use 'tls_min_version' and 'tls_max_version' instead of 'disable_tlsv1'"); ++ } ++ if (conf->disable_tlsv1_1) { ++ WARN(LOG_PREFIX ": Please use 'tls_min_version' and 'tls_max_version' instead of 'disable_tlsv1_1'"); + } +- +- ctx_tls_versions |= SSL_OP_NO_TLSv1_1; +-#endif +-#ifdef SSL_OP_NO_TLSv1_2 +- + if (conf->disable_tlsv1_2) { +- ctx_options |= SSL_OP_NO_TLSv1_2; +-#if OPENSSL_VERSION_NUMBER >= 0x10100000L +- WARN("Please use tls_min_version and tls_max_version instead of disable_tlsv1_2"); +-#endif ++ WARN(LOG_PREFIX ": Please use 'tls_min_version' and 'tls_max_version' instead of 'disable_tlsv1_2'"); + } + +- ctx_tls_versions |= SSL_OP_NO_TLSv1_2; ++ ctx_options &= ~(ctx_available); /* clear these flags, as they're not needed. */ + +-#endif ++ if (!SSL_CTX_set_max_proto_version(ctx, max_version)) { ++ ERROR("Failed setting TLS maximum version"); ++ return NULL; ++ } ++ ++ if (!SSL_CTX_set_min_proto_version(ctx, min_version)) { ++ ERROR("Failed setting TLS minimum version"); ++ return NULL; ++ } ++#endif /* OpenSSL version < 1.1.0 */ + +- if ((ctx_options & ctx_tls_versions) == ctx_tls_versions) { ++ if ((ctx_options & ctx_available) == ctx_available) { + ERROR(LOG_PREFIX ": You have disabled all available TLS versions. EAP will not work"); + return NULL; + } + ++ /* ++ * Cache min / max TLS version so that we can ++ * programatically disable TLS 1.3 for TTLS, PEAP, and ++ * FAST. ++ */ ++ conf->min_version = min_version; ++ conf->max_version = max_version; ++ + #ifdef SSL_OP_NO_TICKET + ctx_options |= SSL_OP_NO_TICKET; + #endif +@@ -3291,6 +4274,19 @@ post_ca: + + SSL_CTX_set_options(ctx, ctx_options); + ++ /* ++ * TLS 1.3 introduces the concept of early data (also known as zero ++ * round trip data or 0-RTT data). Early data allows a client to send ++ * data to a server in the first round trip of a connection, without ++ * waiting for the TLS handshake to complete if the client has spoken ++ * to the same server recently. This doesn't work for EAP, so we ++ * disable early data. ++ * ++ */ ++#if OPENSSL_VERSION_NUMBER >= 0x10101000L ++ SSL_CTX_set_max_early_data(ctx, 0); ++#endif ++ + /* + * TODO: Set the RSA & DH + * SSL_CTX_set_tmp_rsa_callback(ctx, cbtls_rsa); +@@ -3336,12 +4332,21 @@ post_ca: + /* + * Cache sessions on disk if requested. + */ +- if (conf->session_cache_path) { ++ if (conf->session_cache_path && *conf->session_cache_path) { + SSL_CTX_sess_set_new_cb(ctx, cbtls_new_session); + SSL_CTX_sess_set_get_cb(ctx, cbtls_get_session); + SSL_CTX_sess_set_remove_cb(ctx, cbtls_remove_session); + } + ++ /* ++ * Or run the cache through a virtual server. ++ */ ++ if (conf->session_cache_server && *conf->session_cache_server) { ++ SSL_CTX_sess_set_new_cb(ctx, cbtls_cache_save); ++ SSL_CTX_sess_set_get_cb(ctx, cbtls_cache_load); ++ SSL_CTX_sess_set_remove_cb(ctx, cbtls_cache_clear); ++ } ++ + SSL_CTX_set_quiet_shutdown(ctx, 1); + if (fr_tls_ex_index_vps < 0) + fr_tls_ex_index_vps = SSL_SESSION_get_ex_new_index(0, NULL, NULL, NULL, NULL); +@@ -3359,6 +4364,17 @@ post_ca: + } + X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK); + ++#ifdef X509_V_FLAG_USE_DELTAS ++ /* ++ * If set, delta CRLs (if present) are used to ++ * determine certificate status. If not set ++ * deltas are ignored. ++ * ++ * So it's safe to always set this flag. ++ */ ++ X509_STORE_set_flags(certstore, X509_V_FLAG_USE_DELTAS); ++#endif ++ + #ifdef X509_V_FLAG_CRL_CHECK_ALL + if (conf->check_all_crl) + X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK_ALL); +@@ -3424,9 +4440,9 @@ post_ca: + (unsigned int) strlen(conf->session_context_id)); + + /* +- * Our timeout is in hours, this is in seconds. ++ * Our lifetime is in hours, this is in seconds. + */ +- SSL_CTX_set_timeout(ctx, conf->session_timeout * 3600); ++ SSL_CTX_set_timeout(ctx, conf->session_lifetime * 3600); + + /* + * Set the maximum number of entries in the +@@ -3468,11 +4484,15 @@ static int _tls_server_conf_free(fr_tls_server_conf_t *conf) + + if (conf->cache_ht) fr_hash_table_free(conf->cache_ht); + ++ pthread_mutex_destroy(&conf->mutex); ++ + #ifdef HAVE_OPENSSL_OCSP_H + if (conf->ocsp_store) X509_STORE_free(conf->ocsp_store); + conf->ocsp_store = NULL; + #endif + ++ if (conf->realms) fr_hash_table_free(conf->realms); ++ + #ifndef NDEBUG + memset(conf, 0, sizeof(*conf)); + #endif +@@ -3505,9 +4525,105 @@ static int store_cmp(void const *a, void const *b) + DICT_ATTR const *one = a; + DICT_ATTR const *two = b; + +- return one - two; ++ return (one < two) - (one > two); ++} ++ ++static uint32_t realm_hash(void const *data) ++{ ++ fr_realm_ctx_t const *r = data; ++ ++ return fr_hash_string(r->name); ++} ++ ++static int realm_cmp(void const *a, void const *b) ++{ ++ fr_realm_ctx_t const *one = a; ++ fr_realm_ctx_t const *two = b; ++ ++ return strcmp(one->name, two->name); ++} ++ ++static void realm_free(void *data) ++{ ++ fr_realm_ctx_t *r = data; ++ ++ SSL_CTX_free(r->ctx); ++} ++ ++static int tls_realms_load(fr_tls_server_conf_t *conf) ++{ ++ fr_hash_table_t *ht; ++ DIR *dir; ++ struct dirent *dp; ++ char buffer[PATH_MAX]; ++ char buffer2[PATH_MAX]; ++ ++ ht = fr_hash_table_create(realm_hash, realm_cmp, realm_free); ++ if (!ht) return -1; ++ ++ dir = opendir(conf->realm_dir); ++ if (!dir) { ++ ERROR("Error reading directory %s: %s", conf->realm_dir, fr_syserror(errno)); ++ error: ++ fr_hash_table_free(ht); ++ return -1; ++ } ++ ++ /* ++ * Read only the PEM files ++ */ ++ while ((dp = readdir(dir)) != NULL) { ++ char *p; ++ struct stat stat_buf; ++ SSL_CTX *ctx; ++ fr_realm_ctx_t *r; ++ char const *private_key_file = buffer; ++ ++ if (dp->d_name[0] == '.') continue; ++ ++ p = strrchr(dp->d_name, '.'); ++ if (!p) continue; ++ ++ if (memcmp(p, ".pem", 5) != 0) continue; /* must END in .pem */ ++ ++ snprintf(buffer, sizeof(buffer), "%s/%s", conf->realm_dir, dp->d_name); /* ignore directories */ ++ if ((stat(buffer, &stat_buf) != 0) || ++ S_ISDIR(stat_buf.st_mode)) continue; ++ ++ strcpy(buffer2, buffer); ++ p = strchr(buffer2, '.'); /* which must be there... */ ++ if (!p) continue; ++ ++ /* ++ * If there's a key file, then use that. ++ * Otherwise assume that the private key is in ++ * the chain file. ++ */ ++ strcpy(p, ".key"); ++ if (stat(buffer2, &stat_buf) != 0) private_key_file = buffer2; ++ ++ ctx = tls_init_ctx(conf, 1, buffer, private_key_file); ++ if (!ctx) goto error; ++ ++ r = talloc_zero(conf, fr_realm_ctx_t); ++ if (!r) { ++ SSL_CTX_free(ctx); ++ goto error; ++ } ++ ++ r->name = talloc_strdup(r, buffer); ++ r->ctx = ctx; ++ ++ if (fr_hash_table_insert(ht, r) < 0) { ++ ERROR("Failed inserting certificate file %s into hash table", buffer); ++ goto error; ++ } ++ } ++ ++ return 0; + } + ++ + fr_tls_server_conf_t *tls_server_conf_parse(CONF_SECTION *cs) + { + fr_tls_server_conf_t *conf; +@@ -3535,6 +4651,16 @@ fr_tls_server_conf_t *tls_server_conf_parse(CONF_SECTION *cs) + */ + if (conf->fragment_size < 100) conf->fragment_size = 100; + ++ /* ++ * Disallow sessions of more than 7 days, as per RFC ++ * 8446. ++ * ++ * Note that we also enforce this on TLS 1.2, etc. ++ * Because there's just no reason to have month-long TLS ++ * sessions. ++ */ ++ if (conf->session_lifetime > (7 * 24)) conf->session_lifetime = 7 * 24; ++ + /* + * Only check for certificate things if we don't have a + * PSK query. +@@ -3563,10 +4689,15 @@ fr_tls_server_conf_t *tls_server_conf_parse(CONF_SECTION *cs) + } + } + ++ /* ++ * Initialize configuration mutex ++ */ ++ pthread_mutex_init(&conf->mutex, NULL); ++ + /* + * Initialize TLS + */ +- conf->ctx = tls_init_ctx(conf, 0); ++ conf->ctx = tls_init_ctx(conf, 0, NULL, NULL); + if (conf->ctx == NULL) { + goto error; + } +@@ -3633,10 +4764,12 @@ skip_list: + * Initialize OCSP Revocation Store + */ + if (conf->ocsp_enable) { +- conf->ocsp_store = init_revocation_store(conf); ++ conf->ocsp_store = fr_init_x509_store(conf); + if (conf->ocsp_store == NULL) goto error; + } + #endif /*HAVE_OPENSSL_OCSP_H*/ ++ ++#if OPENSSL_VERSION_NUMBER < 0x30000000L + { + char *dh_file; + +@@ -3645,6 +4778,9 @@ skip_list: + goto error; + } + } ++#else ++ if (!SSL_CTX_set_dh_auto(conf->ctx, 1)) goto error; ++#endif + + if (conf->verify_tmp_dir) { + if (chmod(conf->verify_tmp_dir, S_IRWXU) < 0) { +@@ -3663,12 +4799,17 @@ skip_list: + /* + * OpenSSL 1.0.1f and 1.0.1g get the MS-MPPE keys wrong. + */ +-#if (OPENSSL_VERSION_NUMBER >= 0x10010060L) && (OPENSSL_VERSION_NUMBER < 0x10010060L) ++#if (OPENSSL_VERSION_NUMBER >= 0x1010106L) && (OPENSSL_VERSION_NUMBER <= 0x1010107L) + conf->disable_tlsv1_2 = true; + WARN(LOG_PREFIX ": Disabling TLSv1.2 due to OpenSSL bugs"); + #endif + #endif + ++ /* ++ * Load certificates and private keys from the realm directory. ++ */ ++ if (conf->realm_dir && (tls_realms_load(conf) < 0)) goto error; ++ + /* + * Cache conf in cs in case we're asked to parse this again. + */ +@@ -3703,11 +4844,12 @@ fr_tls_server_conf_t *tls_client_conf_parse(CONF_SECTION *cs) + /* + * Initialize TLS + */ +- conf->ctx = tls_init_ctx(conf, 1); ++ conf->ctx = tls_init_ctx(conf, 1, NULL, NULL); + if (conf->ctx == NULL) { + goto error; + } + ++#if OPENSSL_VERSION_NUMBER < 0x30000000L + { + char *dh_file; + +@@ -3716,6 +4858,9 @@ fr_tls_server_conf_t *tls_client_conf_parse(CONF_SECTION *cs) + goto error; + } + } ++#else ++ if (!SSL_CTX_set_dh_auto(conf->ctx, 1)) goto error; ++#endif + + cf_data_add(cs, "tls-conf", conf, NULL); + +@@ -3755,7 +4900,7 @@ int tls_success(tls_session_t *ssn, REQUEST *request) + * not allowed, + */ + if (SSL_session_reused(ssn->ssl)) { +- RDEBUG("Forcibly stopping session resumption as it is not allowed"); ++ RDEBUG("(TLS) cache - Forcibly stopping session resumption as it is administratively disabled."); + return -1; + } + +@@ -3763,12 +4908,14 @@ int tls_success(tls_session_t *ssn, REQUEST *request) + * Else resumption IS allowed, so we store the + * user data in the cache. + */ +- } else if (!SSL_session_reused(ssn->ssl)) { ++ } else if ((!SSL_session_reused(ssn->ssl)) || ssn->session_not_resumed) { + VALUE_PAIR **certs; + char buffer[2 * MAX_SESSION_SIZE + 1]; + + tls_session_id(ssn->ssl_session, buffer, MAX_SESSION_SIZE); + ++ RDEBUG("(TLS) cache - Setting up attributes for session resumption"); ++ + vp = fr_pair_list_copy_by_num(talloc_ctx, request->reply->vps, PW_USER_NAME, 0, TAG_ANY); + if (vp) fr_pair_add(&vps, vp); + +@@ -3778,6 +4925,9 @@ int tls_success(tls_session_t *ssn, REQUEST *request) + vp = fr_pair_list_copy_by_num(talloc_ctx, request->packet->vps, PW_STRIPPED_USER_DOMAIN, 0, TAG_ANY); + if (vp) fr_pair_add(&vps, vp); + ++ vp = fr_pair_list_copy_by_num(talloc_ctx, request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY); ++ if (vp) fr_pair_add(&vps, vp); ++ + vp = fr_pair_list_copy_by_num(talloc_ctx, request->reply->vps, PW_CHARGEABLE_USER_IDENTITY, 0, TAG_ANY); + if (vp) fr_pair_add(&vps, vp); + +@@ -3836,7 +4986,7 @@ int tls_success(tls_session_t *ssn, REQUEST *request) + if (vp) { + if ((request->timestamp + vp->vp_integer) > expires) { + vp->vp_integer = expires - request->timestamp; +- RWDEBUG2("Updating Session-Timeout to %u, due to impending certificate expiration", ++ RWDEBUG2("(TLS) Updating Session-Timeout to %u, due to impending certificate expiration", + vp->vp_integer); + } + } +@@ -3858,7 +5008,7 @@ int tls_success(tls_session_t *ssn, REQUEST *request) + FR_DIR_SEP, buffer); + vp_file = fopen(filename, "w"); + if (vp_file == NULL) { +- RWDEBUG("Could not write session VPs to persistent cache: %s", ++ RWDEBUG("(TLS) Could not write session VPs to persistent cache: %s", + fr_syserror(errno)); + } else { + VALUE_PAIR *prev = NULL; +@@ -3889,6 +5039,10 @@ int tls_success(tls_session_t *ssn, REQUEST *request) + fprintf(vp_file, "\n"); + fclose(vp_file); + } ++ ++ } else if (conf->session_cache_server) { ++ cbtls_cache_save_vps(ssn->ssl, ssn->ssl_session, vps); ++ + } else { + RDEBUG("Failed to find 'persist_dir' in TLS configuration. Session will not be cached on disk."); + } +@@ -3901,15 +5055,27 @@ int tls_success(tls_session_t *ssn, REQUEST *request) + * Else the session WAS allowed. Copy the cached reply. + */ + } else { +- char buffer[2 * MAX_SESSION_SIZE + 1]; +- +- tls_session_id(ssn->ssl_session, buffer, MAX_SESSION_SIZE); ++ RDEBUG("(TLS) cache - Refreshing entry for session resumption"); + + /* + * The "restore VPs from OpenSSL cache" code is + * now in eaptls_process() + */ + if (conf->session_cache_path) { ++ char buffer[2 * MAX_SESSION_SIZE + 1]; ++ ++#if OPENSSL_VERSION_NUMBER >= 0x10001000L ++#ifdef TLS1_3_VERSION ++ /* ++ * OpenSSL frees the underlying session out from ++ * under us in TLS 1.3. ++ */ ++ if (ssn->info.version == TLS1_3_VERSION) ssn->ssl_session = SSL_get_session(ssn->ssl); ++#endif ++#endif ++ ++ tls_session_id(ssn->ssl_session, buffer, MAX_SESSION_SIZE); ++ + /* "touch" the cached session/vp file */ + char filename[3 * MAX_SESSION_SIZE + 1]; + +@@ -3921,6 +5087,10 @@ int tls_success(tls_session_t *ssn, REQUEST *request) + utime(filename, NULL); + } + ++ if (conf->session_cache_server) { ++ cbtls_cache_refresh(ssn->ssl, ssn->ssl_session); ++ } ++ + /* + * Mark the request as resumed. + */ +@@ -3975,27 +5145,30 @@ fr_tls_status_t tls_application_data(tls_session_t *ssn, REQUEST *request) + if (err <= 0) { + int code; + +- RDEBUG("SSL_read Error"); ++ RDEBUG3("SSL_read Error"); + + code = SSL_get_error(ssn->ssl, err); + switch (code) { + case SSL_ERROR_WANT_READ: +- RDEBUG("Error in fragmentation logic: SSL_WANT_READ"); ++ RDEBUG("(TLS) OpenSSL says that it needs to read more data."); + return FR_TLS_MORE_FRAGMENTS; + + case SSL_ERROR_WANT_WRITE: +- RDEBUG("Error in fragmentation logic: SSL_WANT_WRITE"); ++ RDEBUG("(TLS) Error in fragmentation logic: SSL_WANT_WRITE"); + return FR_TLS_FAIL; + + case SSL_ERROR_NONE: +- RDEBUG2("No application data received. Assuming handshake is continuing..."); ++ RDEBUG2("(TLS) No application data received. Assuming handshake is continuing..."); + err = 0; + break; + ++ case SSL_ERROR_ZERO_RETURN: ++ RDEBUG2("(TLS) Other end closed the TLS tunnel."); ++ return FR_TLS_FAIL; ++ + default: +- REDEBUG("Error in fragmentation logic"); +- tls_error_io_log(request, ssn, err, +- "Failed in " STRINGIFY(__FUNCTION__) " (SSL_read)"); ++ REDEBUG("(TLS) Error in fragmentation logic - code %d", code); ++ tls_error_io_log(request, ssn, err, "Failed reading application data from OpenSSL"); + return FR_TLS_FAIL; + } + } +@@ -4026,27 +5199,27 @@ fr_tls_status_t tls_application_data(tls_session_t *ssn, REQUEST *request) + fr_tls_status_t tls_ack_handler(tls_session_t *ssn, REQUEST *request) + { + if (ssn == NULL){ +- REDEBUG("Unexpected ACK received: No ongoing SSL session"); ++ REDEBUG("(TLS) Unexpected ACK received: No ongoing SSL session"); + return FR_TLS_INVALID; + } + if (!ssn->info.initialized) { +- RDEBUG("No SSL info available. Waiting for more SSL data"); ++ RDEBUG("(TLS) No SSL info available. Waiting for more SSL data"); + return FR_TLS_REQUEST; + } + + if ((ssn->info.content_type == handshake) && (ssn->info.origin == 0)) { +- REDEBUG("Unexpected ACK received: We sent no previous messages"); ++ REDEBUG("(TLS) Unexpected ACK received: We sent no previous messages"); + return FR_TLS_INVALID; + } + + switch (ssn->info.content_type) { + case alert: +- RDEBUG2("Peer ACKed our alert"); ++ RDEBUG2("(TLS) Peer ACKed our alert"); + return FR_TLS_FAIL; + + case handshake: + if ((ssn->is_init_finished) && (ssn->dirty_out.used == 0)) { +- RDEBUG2("Peer ACKed our handshake fragment. handshake is finished"); ++ RDEBUG2("(TLS) Peer ACKed our handshake fragment. handshake is finished"); + + /* + * From now on all the content is +@@ -4057,12 +5230,12 @@ fr_tls_status_t tls_ack_handler(tls_session_t *ssn, REQUEST *request) + return FR_TLS_SUCCESS; + } /* else more data to send */ + +- RDEBUG2("Peer ACKed our handshake fragment"); ++ RDEBUG2("(TLS) Peer ACKed our handshake fragment"); + /* Fragmentation handler, send next fragment */ + return FR_TLS_REQUEST; + + case application_data: +- RDEBUG2("Peer ACKed our application data fragment"); ++ RDEBUG2("(TLS) Peer ACKed our application data fragment"); + return FR_TLS_REQUEST; + + /* +@@ -4070,7 +5243,7 @@ fr_tls_status_t tls_ack_handler(tls_session_t *ssn, REQUEST *request) + * to the default section below. + */ + default: +- REDEBUG("Invalid ACK received: %d", ssn->info.content_type); ++ REDEBUG("(TLS) Invalid ACK received: %d", ssn->info.content_type); + return FR_TLS_INVALID; + } + } +diff --git a/src/main/tls_listen.c b/src/main/tls_listen.c +index 0eed87b64f..c846507492 100644 +--- a/src/main/tls_listen.c ++++ b/src/main/tls_listen.c +@@ -81,7 +81,7 @@ static void tls_socket_close(rad_listen_t *listener) + /* + * Tell the event handler that an FD has disappeared. + */ +- DEBUG("Client has closed connection"); ++ DEBUG("(TLS) Client has closed connection"); + radius_update_listener(listener); + + /* +@@ -102,11 +102,11 @@ static int CC_HINT(nonnull) tls_socket_write(rad_listen_t *listener, REQUEST *re + p = sock->ssn->dirty_out.data; + + while (p < (sock->ssn->dirty_out.data + sock->ssn->dirty_out.used)) { +- RDEBUG3("Writing to socket %d", request->packet->sockfd); +- rcode = write(request->packet->sockfd, p, ++ RDEBUG3("(TLS) Writing to socket %d", listener->fd); ++ rcode = write(listener->fd, p, + (sock->ssn->dirty_out.data + sock->ssn->dirty_out.used) - p); + if (rcode <= 0) { +- RDEBUG("Error writing to TLS socket: %s", fr_syserror(errno)); ++ RDEBUG("(TLS) Error writing to socket: %s", fr_syserror(errno)); + + tls_socket_close(listener); + return 0; +@@ -118,8 +118,6 @@ static int CC_HINT(nonnull) tls_socket_write(rad_listen_t *listener, REQUEST *re + + return 1; + } +- +- + static int tls_socket_recv(rad_listen_t *listener) + { + bool doing_init = false; +@@ -165,7 +163,7 @@ static int tls_socket_recv(rad_listen_t *listener) + rad_assert(sock->ssn == NULL); + + sock->ssn = tls_new_session(sock, listener->tls, sock->request, +- listener->tls->require_client_cert); ++ listener->tls->require_client_cert, true); + if (!sock->ssn) { + TALLOC_FREE(sock->request); + sock->packet = NULL; +@@ -175,6 +173,7 @@ static int tls_socket_recv(rad_listen_t *listener) + SSL_set_ex_data(sock->ssn->ssl, FR_TLS_EX_INDEX_REQUEST, (void *)request); + SSL_set_ex_data(sock->ssn->ssl, fr_tls_ex_index_certs, (void *) &sock->certs); + SSL_set_ex_data(sock->ssn->ssl, FR_TLS_EX_INDEX_TALLOC, sock); ++ sock->ssn->quick_session_tickets = true; /* we don't have inner-tunnel authentication */ + + doing_init = true; + } +@@ -186,7 +185,18 @@ static int tls_socket_recv(rad_listen_t *listener) + + request = sock->request; + +- RDEBUG3("Reading from socket %d", request->packet->sockfd); ++ if (sock->state == LISTEN_TLS_SETUP) { ++ RDEBUG3("(TLS) Setting connection state to RUNNING"); ++ sock->state = LISTEN_TLS_RUNNING; ++ ++ if (sock->ssn->clean_out.used < 20) { ++ goto get_application_data; ++ } ++ ++ goto read_application_data; ++ } ++ ++ RDEBUG3("(TLS) Reading from socket %d", request->packet->sockfd); + PTHREAD_MUTEX_LOCK(&sock->mutex); + + /* +@@ -195,9 +205,9 @@ static int tls_socket_recv(rad_listen_t *listener) + * the socket. + */ + if (SSL_pending(sock->ssn->ssl)) { +- RDEBUG3("Reading pending buffered data"); ++ RDEBUG3("(TLS) Reading pending buffered data"); + sock->ssn->dirty_in.used = 0; +- goto get_application_data; ++ goto check_for_setup; + } + + rcode = read(request->packet->sockfd, +@@ -205,14 +215,14 @@ static int tls_socket_recv(rad_listen_t *listener) + sizeof(sock->ssn->dirty_in.data)); + if ((rcode < 0) && (errno == ECONNRESET)) { + do_close: +- DEBUG("Closing TLS socket from client port %u", sock->other_port); ++ DEBUG("(TLS) Closing socket from client port %u", sock->other_port); + tls_socket_close(listener); + PTHREAD_MUTEX_UNLOCK(&sock->mutex); + return 0; + } + + if (rcode < 0) { +- RDEBUG("Error reading TLS socket: %s", fr_syserror(errno)); ++ RDEBUG("(TLS) Error reading socket: %s", fr_syserror(errno)); + goto do_close; + } + +@@ -222,23 +232,23 @@ static int tls_socket_recv(rad_listen_t *listener) + if (rcode == 0) goto do_close; + + sock->ssn->dirty_in.used = rcode; +- + dump_hex("READ FROM SSL", sock->ssn->dirty_in.data, sock->ssn->dirty_in.used); + + /* + * Catch attempts to use non-SSL. + */ + if (doing_init && (sock->ssn->dirty_in.data[0] != handshake)) { +- RDEBUG("Non-TLS data sent to TLS socket: closing"); ++ RDEBUG("(TLS) Non-TLS data sent to TLS socket: closing"); + goto do_close; + } + + /* + * If we need to do more initialization, do that here. + */ ++check_for_setup: + if (!sock->ssn->is_init_finished) { + if (!tls_handshake_recv(request, sock->ssn)) { +- RDEBUG("FAILED in TLS handshake receive"); ++ RDEBUG("(TLS) Failed in TLS handshake receive"); + goto do_close; + } + +@@ -252,10 +262,49 @@ static int tls_socket_recv(rad_listen_t *listener) + } + + /* +- * FIXME: Run the request through a virtual +- * server in order to see if we like the +- * certificate presented by the client. ++ * Else we MUST be finished the SSL setup. ++ */ ++ } ++ ++ /* ++ * Run the request through a virtual server in ++ * order to see if we like the certificate ++ * presented by the client. ++ */ ++ if (sock->state == LISTEN_TLS_INIT) { ++ if (!SSL_is_init_finished(sock->ssn->ssl)) { ++ RDEBUG("(TLS) OpenSSL says that the TLS session is still negotiating, but there's no more data to send!"); ++ goto do_close; ++ } ++ ++ sock->ssn->is_init_finished = true; ++ if (!listener->check_client_connections) { ++ sock->state = LISTEN_TLS_RUNNING; ++ goto get_application_data; ++ } ++ ++ request->packet->vps = fr_pair_list_copy(request->packet, sock->certs); ++ ++ /* ++ * Fake out a Status-Server packet, which ++ * does NOT have a Message-Authenticator, ++ * or any other contents. ++ */ ++ request->packet->code = PW_CODE_STATUS_SERVER; ++ request->packet->data = talloc_zero_array(request->packet, uint8_t, 20); ++ request->packet->data[0] = PW_CODE_STATUS_SERVER; ++ request->packet->data[3] = 20; ++ sock->state = LISTEN_TLS_CHECKING; ++ PTHREAD_MUTEX_UNLOCK(&sock->mutex); ++ ++ /* ++ * Don't read from the socket until the request ++ * returns. + */ ++ listener->status = RAD_LISTEN_STATUS_PAUSE; ++ radius_update_listener(listener); ++ ++ return 1; + } + + /* +@@ -263,7 +312,7 @@ static int tls_socket_recv(rad_listen_t *listener) + */ + get_application_data: + status = tls_application_data(sock->ssn, request); +- RDEBUG("Application data status %d", status); ++ RDEBUG3("(TLS) Application data status %d", status); + + if (status == FR_TLS_MORE_FRAGMENTS) { + PTHREAD_MUTEX_UNLOCK(&sock->mutex); +@@ -275,6 +324,16 @@ get_application_data: + return 0; + } + ++ /* ++ * Hold application data if we're not yet in the RUNNING ++ * state. ++ */ ++ if (sock->state != LISTEN_TLS_RUNNING) { ++ RDEBUG3("(TLS) Holding application data until setup is complete"); ++ return 0; ++ } ++ ++read_application_data: + /* + * We now have a bunch of application data. + */ +@@ -286,7 +345,7 @@ get_application_data: + */ + if ((sock->ssn->clean_out.used < 20) || + (((sock->ssn->clean_out.data[2] << 8) | sock->ssn->clean_out.data[3]) != (int) sock->ssn->clean_out.used)) { +- RDEBUG("Received bad packet: Length %zd contents %d", ++ RDEBUG("(TLS) Received bad packet: Length %zd contents %d", + sock->ssn->clean_out.used, + (sock->ssn->clean_out.data[2] << 8) | sock->ssn->clean_out.data[3]); + goto do_close; +@@ -301,7 +360,7 @@ get_application_data: + + if (!rad_packet_ok(packet, 0, NULL)) { + if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror()); +- DEBUG("Closing TLS socket from client"); ++ DEBUG("(TLS) Closing TLS socket from client"); + PTHREAD_MUTEX_LOCK(&sock->mutex); + tls_socket_close(listener); + PTHREAD_MUTEX_UNLOCK(&sock->mutex); +@@ -315,7 +374,7 @@ get_application_data: + char host_ipaddr[128]; + + if (is_radius_code(packet->code)) { +- RDEBUG("tls_recv: %s packet from host %s port %d, id=%d, length=%d", ++ RDEBUG("(TLS): %s packet from host %s port %d, id=%d, length=%d", + fr_packet_codes[packet->code], + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, +@@ -323,7 +382,7 @@ get_application_data: + packet->src_port, + packet->id, (int) packet->data_len); + } else { +- RDEBUG("tls_recv: Packet from host %s port %d code=%d, id=%d, length=%d", ++ RDEBUG("(TLS): Packet from host %s port %d code=%d, id=%d, length=%d", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + host_ipaddr, sizeof(host_ipaddr)), +@@ -359,6 +418,7 @@ redo: + rad_assert(client != NULL); + + packet = talloc_steal(NULL, sock->packet); ++ sock->request->packet = NULL; + sock->packet = NULL; + + /* +@@ -391,8 +451,26 @@ redo: + break; + #endif + ++#ifdef WITH_COA ++ case PW_CODE_COA_REQUEST: ++ if (listener->type != RAD_LISTEN_COA) goto bad_packet; ++ FR_STATS_INC(coa, total_requests); ++ fun = rad_coa_recv; ++ break; ++ ++ case PW_CODE_DISCONNECT_REQUEST: ++ if (listener->type != RAD_LISTEN_COA) goto bad_packet; ++ FR_STATS_INC(dsc, total_requests); ++ fun = rad_coa_recv; ++ break; ++#endif ++ + case PW_CODE_STATUS_SERVER: +- if (!main_config.status_server) { ++ if (!main_config.status_server ++#ifdef WITH_TLS ++ && !listener->check_client_connections ++#endif ++ ) { + FR_STATS_INC(auth, total_unknown_types); + WARN("Ignoring Status-Server request due to security configuration"); + rad_free(&packet); +@@ -405,7 +483,7 @@ redo: + bad_packet: + FR_STATS_INC(auth, total_unknown_types); + +- DEBUG("Invalid packet code %d sent from client %s port %d : IGNORED", ++ DEBUG("(TLS) Invalid packet code %d sent from client %s port %d : IGNORED", + packet->code, client->shortname, packet->src_port); + rad_free(&packet); + return 0; +@@ -432,7 +510,7 @@ redo: + int peek = SSL_peek(sock->ssn->ssl, buf, 1); + + if (peek > 0) { +- DEBUG("more TLS records after dual_tls_recv"); ++ DEBUG("(TLS) more TLS records after dual_tls_recv"); + goto redo; + } + } +@@ -455,6 +533,34 @@ int dual_tls_send(rad_listen_t *listener, REQUEST *request) + + if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0; + ++ /* ++ * See if the policies allowed this connection. ++ */ ++ if (sock->state == LISTEN_TLS_CHECKING) { ++ if (request->reply->code != PW_CODE_ACCESS_ACCEPT) { ++ listener->status = RAD_LISTEN_STATUS_EOL; ++ listener->tls = NULL; /* parent owns this! */ ++ ++ /* ++ * Tell the event handler that an FD has disappeared. ++ */ ++ radius_update_listener(listener); ++ return 0; ++ } ++ ++ /* ++ * Resume reading from the listener. ++ */ ++ listener->status = RAD_LISTEN_STATUS_RESUME; ++ radius_update_listener(listener); ++ ++ rad_assert(sock->request->packet != request->packet); ++ ++ sock->state = LISTEN_TLS_SETUP; ++ (void) dual_tls_recv(listener); ++ return 0; ++ } ++ + /* + * Accounting reject's are silently dropped. + * +@@ -727,6 +833,15 @@ int proxy_tls_recv(rad_listen_t *listener) + break; + #endif + ++#ifdef WITH_COA ++ case PW_CODE_COA_ACK: ++ case PW_CODE_COA_NAK: ++ case PW_CODE_DISCONNECT_ACK: ++ case PW_CODE_DISCONNECT_NAK: ++ break; ++ ++#endif ++ + default: + /* + * FIXME: Update MIB for packet types? +@@ -765,8 +880,8 @@ int proxy_tls_send(rad_listen_t *listener, REQUEST *request) + * if there's no packet, encode it here. + */ + if (!request->proxy->data) { +- request->proxy_listener->encode(request->proxy_listener, +- request); ++ request->proxy_listener->proxy_encode(request->proxy_listener, ++ request); + } + + if (!sock->ssn->connected) { +diff --git a/src/modules/proto_dhcp/rlm_dhcp.c b/src/modules/proto_dhcp/rlm_dhcp.c +index 9fed166e62..1cd73ff246 100644 +--- a/src/modules/proto_dhcp/rlm_dhcp.c ++++ b/src/modules/proto_dhcp/rlm_dhcp.c +@@ -97,7 +97,7 @@ static ssize_t dhcp_options_xlat(UNUSED void *instance, REQUEST *request, + decoded++; + } + +- fr_pair_list_move(request->packet, &(request->packet->vps), &head); ++ fr_pair_list_move(request->packet, &(request->packet->vps), &head, T_OP_ADD); + + /* Free any unmoved pairs */ + fr_pair_list_free(&head); +diff --git a/src/modules/rlm_eap/libeap/eap_tls.c b/src/modules/rlm_eap/libeap/eap_tls.c +index 83e7252fa8..76c38c6d3f 100644 +--- a/src/modules/rlm_eap/libeap/eap_tls.c ++++ b/src/modules/rlm_eap/libeap/eap_tls.c +@@ -1,3 +1,4 @@ ++ + /* + * eap_tls.c + * +@@ -61,7 +62,7 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + * + * Fragment length is Framed-MTU - 4. + */ +-tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert) ++tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert, bool allow_tls13) + { + tls_session_t *ssn; + REQUEST *request = handler->request; +@@ -75,7 +76,7 @@ tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_ + * in Opaque. So that we can use these data structures + * when we get the response + */ +- ssn = tls_new_session(handler, tls_conf, request, client_cert); ++ ssn = tls_new_session(handler, tls_conf, request, client_cert, allow_tls13); + if (!ssn) { + return NULL; + } +@@ -139,7 +140,7 @@ int eaptls_start(EAP_DS *eap_ds, int peap_flag) + */ + int eaptls_success(eap_handler_t *handler, int peap_flag) + { +- EAPTLS_PACKET reply; ++ EAPTLS_PACKET reply; + REQUEST *request = handler->request; + tls_session_t *tls_session = handler->opaque; + +@@ -160,15 +161,42 @@ int eaptls_success(eap_handler_t *handler, int peap_flag) + /* + * Automatically generate MPPE keying material. + */ +- if (tls_session->prf_label) { +- eaptls_gen_mppe_keys(handler->request, +- tls_session->ssl, tls_session->prf_label); ++ if (tls_session->label) { ++ uint8_t const *context = NULL; ++ size_t context_size = 0; ++#ifdef TLS1_3_VERSION ++ uint8_t const context_tls13[] = { handler->type }; ++#endif ++ ++ switch (tls_session->info.version) { ++#ifdef TLS1_3_VERSION ++ case TLS1_3_VERSION: ++ context = context_tls13; ++ context_size = sizeof(context_tls13); ++ tls_session->label = "EXPORTER_EAP_TLS_Key_Material"; ++ break; ++#endif ++ case TLS1_2_VERSION: ++ case TLS1_1_VERSION: ++ case TLS1_VERSION: ++ break; ++ case SSL2_VERSION: ++ case SSL3_VERSION: ++ default: ++ /* Should never happen */ ++ rad_assert(0); ++ return 0; ++ break; ++ } ++ eaptls_gen_mppe_keys(request, ++ tls_session->ssl, tls_session->label, ++ context, context_size); + } else if (handler->type != PW_EAP_FAST) { +- RWDEBUG("Not adding MPPE keys because there is no PRF label"); ++ RWDEBUG("(TLS) EAP Not adding MPPE keys because there is no PRF label"); + } + +- eaptls_gen_eap_key(handler->request->reply, tls_session->ssl, +- handler->type); ++ eaptls_gen_eap_key(handler); ++ + return 1; + } + +@@ -291,7 +319,7 @@ static int eaptls_send_ack(eap_handler_t *handler, int peap_flag) + EAPTLS_PACKET reply; + REQUEST *request = handler->request; + +- RDEBUG2("ACKing Peer's TLS record fragment"); ++ RDEBUG2("(TLS) EAP ACKing fragment, the peer should send more data."); + reply.code = FR_TLS_ACK; + reply.length = TLS_HEADER_LEN + 1/*flags*/; + reply.flags = peap_flag; +@@ -343,7 +371,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) + /* + * First output the flags (for debugging) + */ +- RDEBUG3("Peer sent flags %c%c%c", ++ RDEBUG3("(TLS) EAP Peer sent flags %c%c%c", + TLS_START(eaptls_packet->flags) ? 'S' : '-', + TLS_MORE_FRAGMENTS(eaptls_packet->flags) ? 'M' : '-', + TLS_LENGTH_INCLUDED(eaptls_packet->flags) ? 'L' : '-'); +@@ -364,7 +392,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) + if (prev_eap_ds && (prev_eap_ds->request->id == eap_ds->response->id)) { + return tls_ack_handler(handler->opaque, request); + } else { +- REDEBUG("Received Invalid TLS ACK"); ++ REDEBUG("(TLS) EAP Received Unexpected ACK - rejection the connection"); + return FR_TLS_INVALID; + } + } +@@ -373,7 +401,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) + * We send TLS_START, but do not receive it. + */ + if (TLS_START(eaptls_packet->flags)) { +- REDEBUG("Peer sent EAP-TLS Start message (only the server is allowed to do this)"); ++ REDEBUG("(TLS) EAP Peer sent EAP-TLS Start message (only the server is allowed to do this)"); + return FR_TLS_INVALID; + } + +@@ -400,11 +428,11 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) + size_t total_len = eaptls_packet->data[2] * 256 | eaptls_packet->data[3]; + + if (frag_len > total_len) { +- RWDEBUG("TLS fragment length (%zu bytes) greater than TLS record length (%zu bytes)", frag_len, ++ RWDEBUG("(TLS) EAP Fragment length (%zu bytes) is greater than TLS record length (%zu bytes)", frag_len, + total_len); + } + +- RDEBUG2("Peer indicated complete TLS record size will be %zu bytes", total_len); ++ RDEBUG2("(TLS) EAP Peer says that the final record size will be %zu bytes", total_len); + if (TLS_MORE_FRAGMENTS(eaptls_packet->flags)) { + /* + * The supplicant is free to send fragments of wildly varying +@@ -415,7 +443,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) + * as they won't contain the length field. + */ + if (frag_len + 4) { /* check for wrap, else clang scan gets excited */ +- RDEBUG2("Expecting %i TLS record fragments", ++ RDEBUG2("(TLS) EAP Expecting %i fragments", + (int)((((total_len - frag_len) + ((frag_len + 4) - 1)) / (frag_len + 4)) + 1)); + } + +@@ -427,24 +455,24 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) + */ + if (!prev_eap_ds || (!prev_eap_ds->response) || (!eaptls_prev) || + !TLS_MORE_FRAGMENTS(eaptls_prev->flags)) { +- RDEBUG2("Got first TLS record fragment (%zu bytes). Peer indicated more fragments " +- "to follow", frag_len); ++ RDEBUG2("(TLS) EAP Got first TLS fragment (%zu bytes). Peer says more fragments " ++ "will follow", frag_len); + tls_session->tls_record_in_total_len = total_len; + tls_session->tls_record_in_recvd_len = frag_len; + + return FR_TLS_FIRST_FRAGMENT; + } + +- RDEBUG2("Got additional TLS record fragment with length (%zu bytes). " +- "Peer indicated more fragments to follow", frag_len); ++ RDEBUG2("(TLS) EAP Got additional fragment with length (%zu bytes). " ++ "Peer says more fragments will follow", frag_len); + + /* + * Check we've not exceeded the originally indicated TLS record size. + */ + tls_session->tls_record_in_recvd_len += frag_len; + if (tls_session->tls_record_in_recvd_len > tls_session->tls_record_in_total_len) { +- RWDEBUG("Total received TLS record fragments (%zu bytes), exceeds " +- "total TLS record length (%zu bytes)", frag_len, total_len); ++ RWDEBUG("(TLS) EAP Total received fragments (%zu bytes), exceeds " ++ "total data length (%zu bytes)", frag_len, total_len); + } + + return FR_TLS_MORE_FRAGMENTS_WITH_LENGTH; +@@ -455,13 +483,13 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) + * value of the four octet TLS length field. + */ + if (total_len != frag_len) { +- RWDEBUG("Peer indicated no more fragments, but TLS record length (%zu bytes) " +- "does not match EAP-TLS data length (%zu bytes)", total_len, frag_len); ++ RWDEBUG("(TLS) EAP Peer says no more fragments, but expected data length (%zu bytes) " ++ "does not match expected data length (%zu bytes)", total_len, frag_len); + } + + tls_session->tls_record_in_total_len = total_len; + tls_session->tls_record_in_recvd_len = frag_len; +- RDEBUG2("Got complete TLS record (%zu bytes)", frag_len); ++ RDEBUG2("(TLS) EAP Got all data (%zu bytes)", frag_len); + return FR_TLS_LENGTH_INCLUDED; + } + +@@ -470,22 +498,22 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) + * this must be the final record fragment + */ + if ((eaptls_prev && TLS_MORE_FRAGMENTS(eaptls_prev->flags)) && !TLS_MORE_FRAGMENTS(eaptls_packet->flags)) { +- RDEBUG2("Got final TLS record fragment (%zu bytes)", frag_len); ++ RDEBUG2("(TLS) EAP Got final fragment (%zu bytes)", frag_len); + tls_session->tls_record_in_recvd_len += frag_len; + if (tls_session->tls_record_in_recvd_len != tls_session->tls_record_in_total_len) { +- RWDEBUG("Total received TLS record fragments (%zu bytes), does not equal indicated " +- "TLS record length (%zu bytes)", ++ RWDEBUG("(TLS) EAP Total received record fragments (%zu bytes), does not equal expected " ++ "expected data length (%zu bytes)", + tls_session->tls_record_in_recvd_len, tls_session->tls_record_in_total_len); + } + } + + if (TLS_MORE_FRAGMENTS(eaptls_packet->flags)) { +- RDEBUG2("Got additional TLS record fragment (%zu bytes). Peer indicated more fragments to follow", ++ RDEBUG2("(TLS) EAP Got additional fragment (%zu bytes). Peer says more fragments will follow", + frag_len); + tls_session->tls_record_in_recvd_len += frag_len; + if (tls_session->tls_record_in_recvd_len > tls_session->tls_record_in_total_len) { +- RWDEBUG("Total received TLS record fragments (%zu bytes), exceeds " +- "indicated TLS record length (%zu bytes)", ++ RWDEBUG("(TLS) EAP Total received fragments (%zu bytes), exceeds " ++ "expected length (%zu bytes)", + tls_session->tls_record_in_recvd_len, tls_session->tls_record_in_total_len); + } + return FR_TLS_MORE_FRAGMENTS; +@@ -576,7 +604,7 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st + */ + if (TLS_LENGTH_INCLUDED(tlspacket->flags) && + (tlspacket->length < 5)) { /* flags + TLS message length */ +- REDEBUG("Invalid EAP-TLS packet received: Length bit is set, " ++ REDEBUG("(TLS) EAP Invalid packet received: Length bit is set," + "but packet too short to contain length field"); + talloc_free(tlspacket); + return NULL; +@@ -593,8 +621,8 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st + memcpy(&data_len, &eap_ds->response->type.data[1], 4); + data_len = ntohl(data_len); + if (data_len > MAX_RECORD_SIZE) { +- REDEBUG("Reassembled TLS record will be %u bytes, " +- "greater than our maximum record size (" STRINGIFY(MAX_RECORD_SIZE) " bytes)", ++ REDEBUG("(TLS) EAP Reassembled data will be %u bytes, " ++ "greater than the size that we can handle (" STRINGIFY(MAX_RECORD_SIZE) " bytes)", + data_len); + talloc_free(tlspacket); + return NULL; +@@ -615,7 +643,7 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st + case FR_TLS_LENGTH_INCLUDED: + case FR_TLS_MORE_FRAGMENTS_WITH_LENGTH: + if (tlspacket->length < 5) { /* flags + TLS message length */ +- REDEBUG("Invalid EAP-TLS packet received: Expected length, got none"); ++ REDEBUG("(TLS) EAP Invalid packet received: Expected length, got none"); + talloc_free(tlspacket); + return NULL; + } +@@ -648,7 +676,7 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st + break; + + default: +- REDEBUG("Invalid EAP-TLS packet received"); ++ REDEBUG("(TLS) EAP Invalid packet received"); + talloc_free(tlspacket); + return NULL; + } +@@ -719,11 +747,37 @@ static fr_tls_status_t eaptls_operation(fr_tls_status_t status, eap_handler_t *h + * is required then send another request. + */ + if (!tls_handshake_recv(handler->request, tls_session)) { +- REDEBUG("TLS receive handshake failed during operation"); ++ REDEBUG("(TLS) EAP Receive handshake failed during operation"); + tls_fail(tls_session); + return FR_TLS_FAIL; + } + ++#ifdef TLS1_3_VERSION ++ /* ++ * https://tools.ietf.org/html/draft-ietf-emu-eap-tls13#section-2.5 ++ * ++ * We need to signal the other end that TLS negotiation ++ * is done. We can't send a zero-length application data ++ * message, so we send application data which is one byte ++ * of zero. ++ * ++ * Note this is only done for when there is no application ++ * data to be sent. So this is done always for EAP-TLS but ++ * notibly not for PEAP even on resumption. ++ */ ++ if ((tls_session->info.version == TLS1_3_VERSION) && ++ (tls_session->client_cert_ok || tls_session->authentication_success || SSL_session_reused(tls_session->ssl))) { ++ if ((handler->type == PW_EAP_TLS) || SSL_session_reused(tls_session->ssl)) { ++ tls_session->authentication_success = true; ++ ++ RDEBUG("(TLS) EAP Sending final Commitment Message."); ++ tls_session->record_plus(&tls_session->clean_in, "\0", 1); ++ } ++ ++ tls_handshake_send(request, tls_session); ++ } ++#endif ++ + /* + * FIXME: return success/fail. + * +@@ -737,23 +791,28 @@ static fr_tls_status_t eaptls_operation(fr_tls_status_t status, eap_handler_t *h + /* + * If there is no data to send i.e + * dirty_out.used <=0 and if the SSL +- * handshake is finished, then return a +- * EPTLS_SUCCESS ++ * handshake is finished. + */ ++ if (tls_session->is_init_finished) return FR_TLS_SUCCESS; + +- if (tls_session->is_init_finished) { ++ /* ++ * If session is established, skip round-trip and ++ * try to process any inner tunnel data if present. ++ * ++ * This occurs for EAP-TTLS/PAP with TLSv1.3. ++ */ ++ if (!tls_session->is_init_finished && SSL_is_init_finished(tls_session->ssl)) { + /* +- * Init is finished. The rest is +- * application data. ++ * Don't set is_init_finished, as that causes the ++ * rest of the code to make too many assumptions. + */ +- tls_session->info.content_type = application_data; +- return FR_TLS_SUCCESS; ++ return FR_TLS_OK; + } + + /* + * Who knows what happened... + */ +- REDEBUG("TLS failed during operation"); ++ REDEBUG("(TLS) Cannot continue, as the peer is misbehaving."); + return FR_TLS_FAIL; + } + +@@ -794,7 +853,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) + + if (!request) return FR_TLS_FAIL; + +- RDEBUG2("Continuing EAP-TLS"); ++ RDEBUG3("(TLS) EAP Continuing ..."); + + SSL_set_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST, request); + +@@ -807,9 +866,9 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) + */ + status = eaptls_verify(handler); + if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { +- REDEBUG("[eaptls verify] = %s", fr_int2str(fr_tls_status_table, status, "")); ++ REDEBUG("(TLS) EAP Verification failed with %s", fr_int2str(fr_tls_status_table, status, "")); + } else { +- RDEBUG2("[eaptls verify] = %s", fr_int2str(fr_tls_status_table, status, "")); ++ RDEBUG3("(TLS) EAP Verification says %s", fr_int2str(fr_tls_status_table, status, "")); + } + + switch (status) { +@@ -840,7 +899,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) + * data" phase. + */ + case FR_TLS_OK: +- RDEBUG2("Done initial handshake"); ++ RDEBUG2("(TLS) EAP Done initial handshake"); + + /* + * Get the rest of the fragments. +@@ -856,6 +915,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) + * Extract the TLS packet from the buffer. + */ + if ((tlspacket = eaptls_extract(request, handler->eap_ds, status)) == NULL) { ++ REDEBUG("(TLS) EAP Failed extracting TLS packet from EAP-Message"); + status = FR_TLS_FAIL; + goto done; + } +@@ -873,7 +933,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) + if (tlspacket->dlen != + (tls_session->record_plus)(&tls_session->dirty_in, tlspacket->data, tlspacket->dlen)) { + talloc_free(tlspacket); +- REDEBUG("Exceeded maximum record size"); ++ REDEBUG("(TLS) EAP Exceeded maximum record size"); + status = FR_TLS_FAIL; + goto done; + } +@@ -902,7 +962,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) + * Send the ACK. + */ + eaptls_send_ack(handler, tls_session->peap_flag); +- RDEBUG2("Init is done, but tunneled data is fragmented"); ++ RDEBUG2("(TLS) EAP Init is done, but tunneled data is fragmented"); + status = FR_TLS_HANDLED; + goto done; + } +@@ -928,13 +988,13 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) + + vps = SSL_SESSION_get_ex_data(tls_session->ssl_session, fr_tls_ex_index_vps); + if (!vps) { +- RWDEBUG("No information in cached session %s", buffer); ++ RWDEBUG("(TLS) EAP No information in cached session %s", buffer); + } else { + vp_cursor_t cursor; + VALUE_PAIR *vp; + fr_tls_server_conf_t *conf; + +- RDEBUG("Adding cached attributes from session %s", buffer); ++ RDEBUG("(TLS) EAP Adding cached attributes from session %s", buffer); + + conf = (fr_tls_server_conf_t *)SSL_get_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_CONF); + rad_assert(conf != NULL); +@@ -970,7 +1030,19 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) + rdebug_pair(L_DBG_LVL_2, request, vp, "&request:"); + fr_pair_add(&request->packet->vps, fr_pair_copy(request->packet, vp)); + } ++ ++ } else if ((vp->da->vendor == 0) && ++ (vp->da->attr == PW_EAP_TYPE)) { ++ /* ++ * EAP-Type gets added to ++ * the control list, so ++ * that we can sanity check it. ++ */ ++ rdebug_pair(L_DBG_LVL_2, request, vp, "&control:"); ++ fr_pair_add(&request->config, fr_pair_copy(request, vp)); ++ + } else { ++ + rdebug_pair(L_DBG_LVL_2, request, vp, "&reply:"); + fr_pair_add(&request->reply->vps, fr_pair_copy(request->reply, vp)); + } +diff --git a/src/modules/rlm_eap/libeap/eap_tls.h b/src/modules/rlm_eap/libeap/eap_tls.h +index 73c7fdd53b..8e5fc773d6 100644 +--- a/src/modules/rlm_eap/libeap/eap_tls.h ++++ b/src/modules/rlm_eap/libeap/eap_tls.h +@@ -62,11 +62,11 @@ int eaptls_fail(eap_handler_t *handler, int peap_flag) CC_HINT(nonnull); + int eaptls_request(EAP_DS *eap_ds, tls_session_t *ssn) CC_HINT(nonnull); + + +-void T_PRF(unsigned char const *secret, unsigned int secret_len, char const *prf_label, unsigned char const *seed, unsigned int seed_len, unsigned char *out, unsigned int out_len) CC_HINT(nonnull(1,3,6)); +-void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label); ++void T_PRF(unsigned char const *secret, unsigned int secret_len, char const *prf_label, unsigned char const *seed, unsigned int seed_len, unsigned char *out, unsigned int out_len) CC_HINT(nonnull(1,3,6)); ++void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *label, uint8_t const *context, size_t context_size); + void eapttls_gen_challenge(SSL *s, uint8_t *buffer, size_t size); +-void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *s, uint32_t header); +-void eap_fast_tls_gen_challenge(SSL *ssl, uint8_t *buffer, uint8_t *scratch, size_t size, char const *prf_label) CC_HINT(nonnull); ++void eaptls_gen_eap_key(eap_handler_t *handler); ++void eap_fast_tls_gen_challenge(SSL *ssl, int version, uint8_t *buffer, size_t size, char const *prf_label) CC_HINT(nonnull); + + #define BUFFER_SIZE 1024 + +@@ -100,7 +100,7 @@ typedef struct tls_packet { + /* EAP-TLS framework */ + EAPTLS_PACKET *eaptls_alloc(void); + void eaptls_free(EAPTLS_PACKET **eaptls_packet_ptr); +-tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert); ++tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert, bool allow_tls13); + int eaptls_start(EAP_DS *eap_ds, int peap); + int eaptls_compose(EAP_DS *eap_ds, EAPTLS_PACKET *reply); + +diff --git a/src/modules/rlm_eap/libeap/mppe_keys.c b/src/modules/rlm_eap/libeap/mppe_keys.c +index 3a9e864104..e16756583c 100644 +--- a/src/modules/rlm_eap/libeap/mppe_keys.c ++++ b/src/modules/rlm_eap/libeap/mppe_keys.c +@@ -26,11 +26,12 @@ RCSID("$Id$") + USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + + #include "eap_tls.h" ++#include + #include +- ++#include + + /* +- * TLS PRF from RFC 2246 ++ * TLS P_hash from RFC 2246/5246 section 5 + */ + static void P_hash(EVP_MD const *evp_md, + unsigned char const *secret, unsigned int secret_len, +@@ -38,8 +39,9 @@ static void P_hash(EVP_MD const *evp_md, + unsigned char *out, unsigned int out_len) + { + HMAC_CTX *ctx_a, *ctx_out; +- unsigned char a[HMAC_MAX_MD_CBLOCK]; +- unsigned int size; ++ unsigned char a[EVP_MAX_MD_SIZE]; ++ unsigned int size = EVP_MAX_MD_SIZE; ++ unsigned int digest_len; + + ctx_a = HMAC_CTX_new(); + ctx_out = HMAC_CTX_new(); +@@ -50,11 +52,9 @@ static void P_hash(EVP_MD const *evp_md, + HMAC_Init_ex(ctx_a, secret, secret_len, evp_md, NULL); + HMAC_Init_ex(ctx_out, secret, secret_len, evp_md, NULL); + +- size = HMAC_size(ctx_out); +- + /* Calculate A(1) */ + HMAC_Update(ctx_a, seed, seed_len); +- HMAC_Final(ctx_a, a, NULL); ++ HMAC_Final(ctx_a, a, &size); + + while (1) { + /* Calculate next part of output */ +@@ -63,13 +63,15 @@ static void P_hash(EVP_MD const *evp_md, + + /* Check if last part */ + if (out_len < size) { +- HMAC_Final(ctx_out, a, NULL); ++ digest_len = EVP_MAX_MD_SIZE; ++ HMAC_Final(ctx_out, a, &digest_len); + memcpy(out, a, out_len); + break; + } + + /* Place digest in output buffer */ +- HMAC_Final(ctx_out, out, NULL); ++ digest_len = EVP_MAX_MD_SIZE; ++ HMAC_Final(ctx_out, out, &digest_len); + HMAC_Init_ex(ctx_out, NULL, 0, NULL, NULL); + out += size; + out_len -= size; +@@ -77,7 +79,8 @@ static void P_hash(EVP_MD const *evp_md, + /* Calculate next A(i) */ + HMAC_Init_ex(ctx_a, NULL, 0, NULL, NULL); + HMAC_Update(ctx_a, a, size); +- HMAC_Final(ctx_a, a, NULL); ++ digest_len = EVP_MAX_MD_SIZE; ++ HMAC_Final(ctx_a, a, &digest_len); + } + + HMAC_CTX_free(ctx_a); +@@ -85,6 +88,38 @@ static void P_hash(EVP_MD const *evp_md, + memset(a, 0, sizeof(a)); + } + ++/* ++ * TLS PRF from RFC 2246 section 5 ++ */ ++static void PRF(unsigned char const *secret, unsigned int secret_len, ++ unsigned char const *seed, unsigned int seed_len, ++ unsigned char *out, unsigned int out_len) ++{ ++ uint8_t buf[out_len + (out_len % SHA_DIGEST_LENGTH)]; ++ unsigned int i; ++ ++ unsigned int len = (secret_len + 1) / 2; ++ uint8_t const *s1 = secret; ++ uint8_t const *s2 = secret + (secret_len - len); ++ ++ P_hash(EVP_md5(), s1, len, seed, seed_len, out, out_len); ++ P_hash(EVP_sha1(), s2, len, seed, seed_len, buf, out_len); ++ ++ for (i = 0; i < out_len; i++) { ++ out[i] ^= buf[i]; ++ } ++} ++ ++/* ++ * TLS 1.2 PRF from RFC 5246 section 5 ++ */ ++static void PRFv12(unsigned char const *secret, unsigned int secret_len, ++ unsigned char const *seed, unsigned int seed_len, ++ unsigned char *out, unsigned int out_len) ++{ ++ P_hash(EVP_sha256(), secret, secret_len, seed, seed_len, out, out_len); ++} ++ + /* EAP-FAST Pseudo-Random Function (T-PRF): RFC 4851, Section 5.5 */ + void T_PRF(unsigned char const *secret, unsigned int secret_len, + char const *prf_label, +@@ -128,60 +163,55 @@ void T_PRF(unsigned char const *secret, unsigned int secret_len, + talloc_free(buf); + } + +-static void PRF(unsigned char const *secret, unsigned int secret_len, +- unsigned char const *seed, unsigned int seed_len, +- unsigned char *out, unsigned char *buf, unsigned int out_len) +-{ +- unsigned int i; +- unsigned int len = (secret_len + 1) / 2; +- uint8_t const *s1 = secret; +- uint8_t const *s2 = secret + (secret_len - len); +- +- P_hash(EVP_md5(), s1, len, seed, seed_len, out, out_len); +- P_hash(EVP_sha1(), s2, len, seed, seed_len, buf, out_len); +- +- for (i=0; i < out_len; i++) { +- out[i] ^= buf[i]; +- } +-} +- + #define EAPTLS_MPPE_KEY_LEN 32 + + /* + * Generate keys according to RFC 2716 and add to reply + */ +-void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label) ++void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *label, uint8_t const *context, UNUSED size_t context_size) + { + uint8_t out[4 * EAPTLS_MPPE_KEY_LEN]; + uint8_t *p; +- size_t prf_size; ++ size_t len; + +- prf_size = strlen(prf_label); ++ len = strlen(label); + + #if OPENSSL_VERSION_NUMBER >= 0x10001000L +- if (SSL_export_keying_material(s, out, sizeof(out), prf_label, prf_size, NULL, 0, 0) != 1) { ++ if (SSL_export_keying_material(s, out, sizeof(out), label, len, context, context_size, context != NULL) != 1) { + ERROR("Failed generating keying material"); + return; + } + #else + { +- uint8_t seed[64 + (2 * SSL3_RANDOM_SIZE)]; ++ uint8_t seed[64 + (2 * SSL3_RANDOM_SIZE) + (context ? 2 + context_size : 0)]; + uint8_t buf[4 * EAPTLS_MPPE_KEY_LEN]; + + p = seed; + +- memcpy(p, prf_label, prf_size); +- p += prf_size; ++ memcpy(p, label, len); ++ p += len; + + memcpy(p, s->s3->client_random, SSL3_RANDOM_SIZE); + p += SSL3_RANDOM_SIZE; +- prf_size += SSL3_RANDOM_SIZE; ++ len += SSL3_RANDOM_SIZE; + + memcpy(p, s->s3->server_random, SSL3_RANDOM_SIZE); +- prf_size += SSL3_RANDOM_SIZE; ++ p += SSL3_RANDOM_SIZE; ++ len += SSL3_RANDOM_SIZE; ++ ++ if (context) { ++ /* cloned and reversed FR_PUT_LE16 */ ++ p[0] = ((uint16_t) (context_size)) >> 8; ++ p[1] = ((uint16_t) (context_size)) & 0xff; ++ p += 2; ++ len += 2; ++ memcpy(p, context, context_size); ++ p += context_size; ++ len += context_size; ++ } + + PRF(s->session->master_key, s->session->master_key_length, +- seed, prf_size, out, buf, sizeof(out)); ++ seed, len, out, buf, sizeof(out)); + } + #endif + +@@ -195,7 +225,7 @@ void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label) + } + + +-#define FR_TLS_PRF_CHALLENGE "ttls challenge" ++#define FR_TLS_PRF_CHALLENGE "ttls challenge" + + /* + * Generate the TTLS challenge +@@ -206,9 +236,10 @@ void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label) + void eapttls_gen_challenge(SSL *s, uint8_t *buffer, size_t size) + { + #if OPENSSL_VERSION_NUMBER >= 0x10001000L +- SSL_export_keying_material(s, buffer, size, FR_TLS_PRF_CHALLENGE, +- sizeof(FR_TLS_PRF_CHALLENGE) - 1, NULL, 0, 0); +- ++ if (SSL_export_keying_material(s, buffer, size, FR_TLS_PRF_CHALLENGE, ++ sizeof(FR_TLS_PRF_CHALLENGE)-1, NULL, 0, 0) != 1) { ++ ERROR("Failed generating keying material"); ++ } + #else + uint8_t out[32], buf[32]; + uint8_t seed[sizeof(FR_TLS_PRF_CHALLENGE)-1 + 2*SSL3_RANDOM_SIZE]; +@@ -226,14 +257,20 @@ void eapttls_gen_challenge(SSL *s, uint8_t *buffer, size_t size) + #endif + } + ++#define FR_TLS_EXPORTER_METHOD_ID "EXPORTER_EAP_TLS_Method-Id" ++ + /* + * Actually generates EAP-Session-Id, which is an internal server + * attribute. Not all systems want to send EAP-Key-Name. + */ +-void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *ssl, uint32_t header) ++void eaptls_gen_eap_key(eap_handler_t *handler) + { ++ RADIUS_PACKET *packet = handler->request->reply; ++ tls_session_t *tls_session = handler->opaque; ++ SSL *s = tls_session->ssl; + VALUE_PAIR *vp; + uint8_t *buff, *p; ++ uint8_t type = handler->type & 0xff; + + vp = fr_pair_afrom_num(packet, PW_EAP_SESSION_ID, 0); + if (!vp) return; +@@ -241,11 +278,33 @@ void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *ssl, uint32_t header) + vp->vp_length = 1 + 2 * SSL3_RANDOM_SIZE; + buff = p = talloc_array(vp, uint8_t, vp->vp_length); + +- *p++ = header & 0xff; ++ *p++ = type; + +- SSL_get_client_random(ssl, p, SSL3_RANDOM_SIZE); +- p += SSL3_RANDOM_SIZE; +- SSL_get_server_random(ssl, p, SSL3_RANDOM_SIZE); ++ switch (tls_session->info.version) { ++ case TLS1_VERSION: ++ case TLS1_1_VERSION: ++ case TLS1_2_VERSION: ++ SSL_get_client_random(s, p, SSL3_RANDOM_SIZE); ++ p += SSL3_RANDOM_SIZE; ++ SSL_get_server_random(s, p, SSL3_RANDOM_SIZE); ++ break; ++#if OPENSSL_VERSION_NUMBER >= 0x10100000L ++#ifdef TLS1_3_VERSION ++ case TLS1_3_VERSION: ++#endif ++ default: ++ { ++ uint8_t const context[] = { type }; ++ ++ if (SSL_export_keying_material(s, p, 2 * SSL3_RANDOM_SIZE, ++ FR_TLS_EXPORTER_METHOD_ID, sizeof(FR_TLS_EXPORTER_METHOD_ID)-1, ++ context, sizeof(context), 1) != 1) { ++ ERROR("Failed generating keying material"); ++ return; ++ } ++ } ++#endif ++ } + + vp->vp_octets = buff; + fr_pair_add(&packet->vps, vp); +@@ -254,7 +313,7 @@ void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *ssl, uint32_t header) + /* + * Same as before, but for EAP-FAST the order of {server,client}_random is flipped + */ +-void eap_fast_tls_gen_challenge(SSL *s, uint8_t *buffer, uint8_t *scratch, size_t size, char const *prf_label) ++void eap_fast_tls_gen_challenge(SSL *s, int version, uint8_t *buffer, size_t size, char const *prf_label) + { + uint8_t *p; + size_t len, master_key_len; +@@ -273,7 +332,9 @@ void eap_fast_tls_gen_challenge(SSL *s, uint8_t *buffer, uint8_t *scratch, size_ + p += SSL3_RANDOM_SIZE; + + master_key_len = SSL_SESSION_get_master_key(SSL_get_session(s), master_key, sizeof(master_key)); +- PRF(master_key, master_key_len, seed, p - seed, buffer, scratch, size); +-} +- + ++ if (version == TLS1_2_VERSION) ++ PRFv12(master_key, master_key_len, seed, p - seed, buffer, size); ++ else ++ PRF(master_key, master_key_len, seed, p - seed, buffer, size); ++} +diff --git a/src/modules/rlm_eap/radeapclient.c b/src/modules/rlm_eap/radeapclient.c +index 553a6a6a57..cd504a8363 100644 +--- a/src/modules/rlm_eap/radeapclient.c ++++ b/src/modules/rlm_eap/radeapclient.c +@@ -182,6 +182,14 @@ static void rc_get_port(PW_CODE type, uint16_t *port); + static void rc_evprep_packet_timeout(rc_transaction_t *trans); + static void rc_deallocate_id(rc_transaction_t *trans); + ++/* ++ * For cbtls_cache_*() ++ */ ++rlm_rcode_t process_post_auth(UNUSED int postauth_type, UNUSED REQUEST *request) ++{ ++ return RLM_MODULE_FAIL; ++} ++ + + static void NEVER_RETURNS usage(void) + { +diff --git a/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c b/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c +index b0953aa1d4..8c63498586 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c ++++ b/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c +@@ -61,33 +61,51 @@ static int openssl_get_keyblock_size(REQUEST *request, SSL *ssl) + return -1; + + RDEBUG2("OpenSSL: keyblock size: key_len=%d MD_size=%d " +- "IV_len=%d", EVP_CIPHER_key_length(c), md_size, +- EVP_CIPHER_iv_length(c)); ++ "IV_len=%d", EVP_CIPHER_key_length(c), md_size, ++ EVP_CIPHER_iv_length(c)); + return 2 * (EVP_CIPHER_key_length(c) + + md_size + + EVP_CIPHER_iv_length(c)); + #else + const SSL_CIPHER *ssl_cipher; + int cipher, digest; ++ int mac_key_len, enc_key_len, fixed_iv_len; + + ssl_cipher = SSL_get_current_cipher(ssl); + if (!ssl_cipher) + return -1; + cipher = SSL_CIPHER_get_cipher_nid(ssl_cipher); + digest = SSL_CIPHER_get_digest_nid(ssl_cipher); +- RDEBUG2("OpenSSL: cipher nid %d digest nid %d", cipher, digest); ++ RDEBUG3("OpenSSL: cipher nid %d digest nid %d", ++ cipher, digest); + if (cipher < 0 || digest < 0) + return -1; ++ if (cipher == NID_undef) { ++ RDEBUG3("OpenSSL: no cipher in use?!"); ++ return -1; ++ } + c = EVP_get_cipherbynid(cipher); +- h = EVP_get_digestbynid(digest); +- if (!c || !h) ++ if (!c) + return -1; ++ enc_key_len = EVP_CIPHER_key_length(c); ++ if (EVP_CIPHER_mode(c) == EVP_CIPH_GCM_MODE || ++ EVP_CIPHER_mode(c) == EVP_CIPH_CCM_MODE) ++ fixed_iv_len = 4; /* only part of IV from PRF */ ++ else ++ fixed_iv_len = EVP_CIPHER_iv_length(c); ++ if (digest == NID_undef) { ++ RDEBUG3("OpenSSL: no digest in use (e.g., AEAD)"); ++ mac_key_len = 0; ++ } else { ++ h = EVP_get_digestbynid(digest); ++ if (!h) ++ return -1; ++ mac_key_len = EVP_MD_size(h); ++ } + +- RDEBUG2("OpenSSL: keyblock size: key_len=%d MD_size=%d IV_len=%d", +- EVP_CIPHER_key_length(c), EVP_MD_size(h), +- EVP_CIPHER_iv_length(c)); +- return 2 * (EVP_CIPHER_key_length(c) + EVP_MD_size(h) + +- EVP_CIPHER_iv_length(c)); ++ RDEBUG2("OpenSSL: keyblock size: mac_key_len=%d enc_key_len=%d fixed_iv_len=%d", ++ mac_key_len, enc_key_len, fixed_iv_len); ++ return 2 * (mac_key_len + enc_key_len + fixed_iv_len); + #endif + } + +@@ -98,7 +116,6 @@ static void eap_fast_init_keys(REQUEST *request, tls_session_t *tls_session) + { + eap_fast_tunnel_t *t = tls_session->opaque; + uint8_t *buf; +- uint8_t *scratch; + size_t ksize; + + RDEBUG2("Deriving EAP-FAST keys"); +@@ -108,11 +125,10 @@ static void eap_fast_init_keys(REQUEST *request, tls_session_t *tls_session) + ksize = openssl_get_keyblock_size(request, tls_session->ssl); + rad_assert(ksize > 0); + buf = talloc_size(request, ksize + sizeof(*t->keyblock)); +- scratch = talloc_size(request, ksize + sizeof(*t->keyblock)); + + t->keyblock = talloc(t, eap_fast_keyblock_t); + +- eap_fast_tls_gen_challenge(tls_session->ssl, buf, scratch, ksize + sizeof(*t->keyblock), "key expansion"); ++ eap_fast_tls_gen_challenge(tls_session->ssl, tls_session->info.version, buf, ksize + sizeof(*t->keyblock), "key expansion"); + memcpy(t->keyblock, &buf[ksize], sizeof(*t->keyblock)); + memset(buf, 0, ksize + sizeof(*t->keyblock)); + +@@ -123,7 +139,6 @@ static void eap_fast_init_keys(REQUEST *request, tls_session_t *tls_session) + t->imckc = 0; + + talloc_free(buf); +- talloc_free(scratch); + } + + /** +@@ -256,6 +271,7 @@ static void eap_fast_send_pac_tunnel(REQUEST *request, tls_session_t *tls_sessio + dlen = eap_fast_encrypt((unsigned const char *)&opaque_plaintext, sizeof(opaque_plaintext), + t->a_id, PAC_A_ID_LENGTH, t->pac_opaque_key, pac.opaque.iv, + pac.opaque.data, pac.opaque.tag); ++ if (dlen < 0) return; + + pac.opaque.hdr.type = htons(EAP_FAST_TLV_MANDATORY | PAC_INFO_PAC_OPAQUE); + pac.opaque.hdr.length = htons(sizeof(pac.opaque) - sizeof(pac.opaque.hdr) - sizeof(pac.opaque.data) + dlen); +@@ -765,6 +781,7 @@ static rlm_rcode_t CC_HINT(nonnull) process_reply( eap_handler_t *eap_session, + switch (reply->code) { + case PW_CODE_ACCESS_ACCEPT: + RDEBUG("Got tunneled Access-Accept"); ++ tls_session->authentication_success = true; + rcode = RLM_MODULE_OK; + + for (vp = fr_cursor_init(&cursor, &reply->vps); vp; vp = fr_cursor_next(&cursor)) { +@@ -1203,8 +1220,12 @@ PW_CODE eap_fast_process(eap_handler_t *eap_session, tls_session_t *tls_session) + t->mode = EAP_FAST_PROVISIONING_AUTH; + } + +- if (!t->pac.expires || t->pac.expired || t->pac.expires - time(NULL) < t->pac_lifetime * 0.6) ++ /* ++ * Send a new pac at ~0.6 times the lifetime. ++ */ ++ if (!t->pac.expires || t->pac.expired || t->pac.expires < (time(NULL) + (t->pac_lifetime >> 1) + (t->pac_lifetime >> 3))) { + t->pac.send = true; ++ } + } + + eap_fast_init_keys(request, tls_session); +diff --git a/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c b/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c +index 2ce2dd0c3b..7c91d34050 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c ++++ b/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c +@@ -131,6 +131,25 @@ static int mod_instantiate(CONF_SECTION *cs, void **instance) + return -1; + } + ++#ifdef TLS1_3_VERSION ++ if (inst->tls_conf->min_version == TLS1_3_VERSION) { ++ ERROR("There are no standards for using TLS 1.3 with EAP-FAST."); ++ ERROR("You MUST enable TLS 1.2 for EAP-FAST to work."); ++ return -1; ++ } ++ ++ if ((inst->tls_conf->max_version == TLS1_3_VERSION) || ++ (inst->tls_conf->min_version == TLS1_3_VERSION)) { ++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ WARN("!! There is no standard for using EAP-FAST with TLS 1.3"); ++ WARN("!! Please set tls_max_version = \"1.2\""); ++ WARN("!! FreeRADIUS only supports TLS 1.3 for special builds of wpa_supplicant and Windows"); ++ WARN("!! This limitation is likely to change in late 2021."); ++ WARN("!! If you are using this version of FreeRADIUS after 2021, you will probably need to upgrade"); ++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ } ++#endif ++ + rad_assert(PAC_A_ID_LENGTH == MD5_DIGEST_LENGTH); + FR_MD5_CTX ctx; + fr_md5_init(&ctx); +@@ -241,7 +260,7 @@ static int _session_ticket(SSL *s, uint8_t const *data, int len, void *arg) + DICT_ATTR const *fast_da; + char const *errmsg; + int dlen, plen; +- uint16_t length; ++ int length; + eap_fast_attr_pac_opaque_t const *opaque = (eap_fast_attr_pac_opaque_t const *) data; + eap_fast_attr_pac_opaque_t opaque_plaintext; + +@@ -274,7 +293,7 @@ error: + * so we have to use the length in the PAC-Opaque header + */ + length = ntohs(opaque->hdr.length); +- if (len - sizeof(opaque->hdr) < length) { ++ if (len < (int) (length + sizeof(opaque->hdr))) { + errmsg = "PAC has bad length in header"; + goto error; + } +@@ -293,7 +312,7 @@ error: + plen = eap_fast_decrypt(opaque->data, dlen, opaque->aad, PAC_A_ID_LENGTH, + (uint8_t const *) opaque->tag, t->pac_opaque_key, opaque->iv, + (uint8_t *)&opaque_plaintext); +- if (plen == -1) { ++ if (plen < 0) { + errmsg = "PAC failed to decrypt"; + goto error; + } +@@ -314,8 +333,8 @@ error: + break; + case PAC_INFO_PAC_LIFETIME: + rad_assert(t->pac.expires == 0); +- t->pac.expires = vp->vp_integer; +- t->pac.expired = (vp->vp_integer <= time(NULL)); ++ t->pac.expires = vp->vp_integer + time(NULL); ++ t->pac.expired = false; + break; + case PAC_INFO_PAC_KEY: + rad_assert(t->pac.key == NULL); +@@ -392,7 +411,7 @@ static int mod_process(void *arg, eap_handler_t *handler) + if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { + REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); + } else { +- RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); ++ RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); + } + + /* +@@ -553,7 +572,12 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) + } else { + client_cert = inst->req_client_cert; + } +- handler->opaque = tls_session = eaptls_session(handler, inst->tls_conf, client_cert); ++ ++ /* ++ * Don't allow TLS 1.3 for us, even if it's allowed ++ * elsewhere. ++ */ ++ handler->opaque = tls_session = eaptls_session(handler, inst->tls_conf, client_cert, false); + + if (!tls_session) return 0; + +@@ -566,16 +590,20 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) + } + } + +-#ifdef SSL_OP_NO_TLSv1_2 +- /* +- * Forcibly disable TLSv1.2 +- * +- * @fixme - TLSv1.2 uses a different PRF and +- * SSL_export_keying_material("key expansion") is +- * forbidden +- */ +- SSL_set_options(tls_session->ssl, SSL_OP_NO_TLSv1_2); ++#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) ++ { ++ int i; ++ for (i = 0; ; i++) { ++ const char *cipher = SSL_get_cipher_list(tls_session->ssl, i); ++ if (!cipher) break; ++ if (!strstr(cipher, "ADH-")) continue; ++ RDEBUG("Setting security level to 0 to allow anonymous cipher suites"); ++ SSL_set_security_level(tls_session->ssl, 0); ++ break; ++ } ++ } + #endif ++ + #ifdef SSL_OP_NO_TLSv1_3 + /* + * Forcibly disable TLSv1.3 +diff --git a/src/modules/rlm_eap/types/rlm_eap_peap/peap.c b/src/modules/rlm_eap/types/rlm_eap_peap/peap.c +index deaf702d61..5647f613af 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_peap/peap.c ++++ b/src/modules/rlm_eap/types/rlm_eap_peap/peap.c +@@ -442,6 +442,7 @@ static rlm_rcode_t CC_HINT(nonnull) process_reply(eap_handler_t *handler, tls_se + switch (reply->code) { + case PW_CODE_ACCESS_ACCEPT: + RDEBUG2("Tunneled authentication was successful"); ++ tls_session->authentication_success = true; + t->status = PEAP_STATUS_SENT_TLV_SUCCESS; + eappeap_success(handler, tls_session); + rcode = RLM_MODULE_HANDLED; +@@ -790,6 +791,7 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session, + /* send an identity request */ + t->session_resumption_state = PEAP_RESUMPTION_NO; + t->status = PEAP_STATUS_INNER_IDENTITY_REQ_SENT; ++ tls_session->session_not_resumed = true; + eappeap_identity(handler, tls_session); + } + return RLM_MODULE_HANDLED; +@@ -903,15 +905,15 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session, + + return RLM_MODULE_REJECT; + +- case PEAP_STATUS_PHASE2_INIT: +- RDEBUG("In state machine in phase2 init?"); ++ case PEAP_STATUS_PHASE2_INIT: ++ RDEBUG("In state machine in phase2 init?"); + +- case PEAP_STATUS_PHASE2: +- break; ++ case PEAP_STATUS_PHASE2: ++ break; + +- default: +- REDEBUG("Unhandled state in peap"); +- return RLM_MODULE_REJECT; ++ default: ++ REDEBUG("Unhandled state in peap"); ++ return RLM_MODULE_REJECT; + } + + fake = request_alloc_fake(request); +@@ -1040,6 +1042,10 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session, + + if (vp) { + eap_tunnel_data_t *tunnel; ++ bool proxy_as_eap = t->proxy_tunneled_request_as_eap; ++ VALUE_PAIR *flag = fr_pair_find_by_num(fake->config, PW_PROXY_TUNNELED_REQUEST_AS_EAP, 0, TAG_ANY); ++ ++ if (flag) proxy_as_eap = flag->vp_integer; + + /* + * The tunneled request was NOT handled, +@@ -1056,7 +1062,7 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session, + * Once the tunneled EAP session is ALMOST + * done, THEN we proxy it... + */ +- if (!t->proxy_tunneled_request_as_eap) { ++ if (!proxy_as_eap) { + fake->options |= RAD_REQUEST_OPTION_PROXY_EAP; + + /* +diff --git a/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c b/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c +index 3d23322663..4bbf57330f 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c ++++ b/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c +@@ -135,6 +135,25 @@ static int mod_instantiate(CONF_SECTION *cs, void **instance) + inst->auth_type_eap = dv->value; + } + ++#ifdef TLS1_3_VERSION ++ if ((inst->tls_conf->min_version == TLS1_3_VERSION) && !inst->tls_conf->tls13_enable_magic) { ++ ERROR("There are no standards for using TLS 1.3 with PEAP."); ++ ERROR("You MUST enable TLS 1.2 for PEAP to work."); ++ return -1; ++ } ++ ++ if ((inst->tls_conf->max_version == TLS1_3_VERSION) || ++ (inst->tls_conf->min_version == TLS1_3_VERSION)) { ++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ WARN("!! There is no standard for using PEAP with TLS 1.3"); ++ WARN("!! Please set tls_max_version = \"1.2\""); ++ WARN("!! FreeRADIUS only supports TLS 1.3 for special builds of wpa_supplicant and Windows"); ++ WARN("!! This limitation is likely to change in late 2021."); ++ WARN("!! If you are using this version of FreeRADIUS after 2021, you will probably need to upgrade"); ++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++} ++#endif ++ + return 0; + } + +@@ -192,7 +211,11 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) + client_cert = inst->req_client_cert; + } + +- ssn = eaptls_session(handler, inst->tls_conf, client_cert); ++ /* ++ * Don't allow TLS 1.3 for us, even if it's allowed ++ * elsewhere. ++ */ ++ ssn = eaptls_session(handler, inst->tls_conf, client_cert, inst->tls_conf->tls13_enable_magic); + if (!ssn) { + return 0; + } +@@ -200,9 +223,13 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) + handler->opaque = ((void *)ssn); + + /* +- * Set up type-specific information. ++ * Set the label to a fixed string. For TLS 1.3, the ++ * label is the same for all TLS-based EAP methods. If ++ * the client is using TLS 1.3, then eaptls_success() ++ * will over-ride this label with the correct label for ++ * TLS 1.3. + */ +- ssn->prf_label = "client EAP encryption"; ++ ssn->label = "client EAP encryption"; + + /* + * As it is a poorly designed protocol, PEAP uses +@@ -230,7 +257,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) + if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { + REDEBUG("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); + } else { +- RDEBUG2("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); ++ RDEBUG3("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); + } + if (status == 0) return 0; + +@@ -274,7 +301,7 @@ static int mod_process(void *arg, eap_handler_t *handler) + if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { + REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); + } else { +- RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); ++ RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); + } + + /* +@@ -311,6 +338,10 @@ static int mod_process(void *arg, eap_handler_t *handler) + * data. + */ + case FR_TLS_OK: ++ /* ++ * TLSv1.3 makes application data immediately avaliable ++ */ ++ if (tls_session->is_init_finished && (peap->status == PEAP_STATUS_INVALID)) peap->status = PEAP_STATUS_TUNNEL_ESTABLISHED; + break; + + /* +diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h b/src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h +new file mode 100644 +index 0000000000..b717dd51b3 +--- /dev/null ++++ b/src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h +@@ -0,0 +1,190 @@ ++/* ++ * Helper functions for constant time operations ++ * Copyright (c) 2019, The Linux Foundation ++ * ++ * This software may be distributed under the terms of the BSD license. ++ * See README for more details. ++ * ++ * These helper functions can be used to implement logic that needs to minimize ++ * externally visible differences in execution path by avoiding use of branches, ++ * avoiding early termination or other time differences, and forcing same memory ++ * access pattern regardless of values. ++ */ ++ ++#ifndef CONST_TIME_H ++#define CONST_TIME_H ++ ++ ++#if defined(__clang__) ++#define NO_UBSAN_UINT_OVERFLOW \ ++ __attribute__((no_sanitize("unsigned-integer-overflow"))) ++#else ++#define NO_UBSAN_UINT_OVERFLOW ++#endif ++ ++/** ++ * const_time_fill_msb - Fill all bits with MSB value ++ * @param val Input value ++ * @return Value with all the bits set to the MSB of the input val ++ */ ++static inline unsigned int const_time_fill_msb(unsigned int val) ++{ ++ /* Move the MSB to LSB and multiple by -1 to fill in all bits. */ ++ return (val >> (sizeof(val) * 8 - 1)) * ~0U; ++} ++ ++ ++/* @return -1 if val is zero; 0 if val is not zero */ ++static inline unsigned int const_time_is_zero(unsigned int val) ++ NO_UBSAN_UINT_OVERFLOW ++{ ++ /* Set MSB to 1 for 0 and fill rest of bits with the MSB value */ ++ return const_time_fill_msb(~val & (val - 1)); ++} ++ ++ ++/* @return -1 if a == b; 0 if a != b */ ++static inline unsigned int const_time_eq(unsigned int a, unsigned int b) ++{ ++ return const_time_is_zero(a ^ b); ++} ++ ++ ++/* @return -1 if a == b; 0 if a != b */ ++static inline unsigned char const_time_eq_u8(unsigned int a, unsigned int b) ++{ ++ return (unsigned char) const_time_eq(a, b); ++} ++ ++ ++/** ++ * const_time_eq_bin - Constant time memory comparison ++ * @param a First buffer to compare ++ * @param b Second buffer to compare ++ * @param len Number of octets to compare ++ * @return -1 if buffers are equal, 0 if not ++ * ++ * This function is meant for comparing passwords or hash values where ++ * difference in execution time or memory access pattern could provide external ++ * observer information about the location of the difference in the memory ++ * buffers. The return value does not behave like memcmp(), i.e., ++ * const_time_eq_bin() cannot be used to sort items into a defined order. Unlike ++ * memcmp(), the execution time of const_time_eq_bin() does not depend on the ++ * contents of the compared memory buffers, but only on the total compared ++ * length. ++ */ ++static inline unsigned int const_time_eq_bin(const void *a, const void *b, ++ size_t len) ++{ ++ const unsigned char *aa = a; ++ const unsigned char *bb = b; ++ size_t i; ++ unsigned char res = 0; ++ ++ for (i = 0; i < len; i++) ++ res |= aa[i] ^ bb[i]; ++ ++ return const_time_is_zero(res); ++} ++ ++ ++/** ++ * const_time_select - Constant time unsigned int selection ++ * @param mask 0 (false) or -1 (true) to identify which value to select ++ * @param true_val Value to select for the true case ++ * @param false_val Value to select for the false case ++ * @return true_val if mask == -1, false_val if mask == 0 ++ */ ++static inline unsigned int const_time_select(unsigned int mask, ++ unsigned int true_val, ++ unsigned int false_val) ++{ ++ return (mask & true_val) | (~mask & false_val); ++} ++ ++ ++/** ++ * const_time_select_int - Constant time int selection ++ * @param mask 0 (false) or -1 (true) to identify which value to select ++ * @param true_val Value to select for the true case ++ * @param false_val Value to select for the false case ++ * @return true_val if mask == -1, false_val if mask == 0 ++ */ ++static inline int const_time_select_int(unsigned int mask, int true_val, ++ int false_val) ++{ ++ return (int) const_time_select(mask, (unsigned int) true_val, ++ (unsigned int) false_val); ++} ++ ++ ++/** ++ * const_time_select_u8 - Constant time u8 selection ++ * @param mask 0 (false) or -1 (true) to identify which value to select ++ * @param true_val Value to select for the true case ++ * @param false_val Value to select for the false case ++ * @return true_val if mask == -1, false_val if mask == 0 ++ */ ++static inline unsigned char const_time_select_u8(unsigned char mask, unsigned char true_val, unsigned char false_val) ++{ ++ return (unsigned char) const_time_select(mask, true_val, false_val); ++} ++ ++ ++/** ++ * const_time_select_s8 - Constant time s8 selection ++ * @param mask 0 (false) or -1 (true) to identify which value to select ++ * @param true_val Value to select for the true case ++ * @param false_val Value to select for the false case ++ * @return true_val if mask == -1, false_val if mask == 0 ++ */ ++static inline char const_time_select_s8(char mask, char true_val, char false_val) ++{ ++ return (char) const_time_select(mask, (unsigned int) true_val, ++ (unsigned int) false_val); ++} ++ ++ ++/** ++ * const_time_select_bin - Constant time binary buffer selection copy ++ * @param mask 0 (false) or -1 (true) to identify which value to copy ++ * @param true_val Buffer to copy for the true case ++ * @param false_val Buffer to copy for the false case ++ * @param len Number of octets to copy ++ * @param dst Destination buffer for the copy ++ * ++ * This function copies the specified buffer into the destination buffer using ++ * operations with identical memory access pattern regardless of which buffer ++ * is being copied. ++ */ ++static inline void const_time_select_bin(unsigned char mask, const unsigned char *true_val, ++ const unsigned char *false_val, size_t len, ++ unsigned char *dst) ++{ ++ size_t i; ++ ++ for (i = 0; i < len; i++) ++ dst[i] = const_time_select_u8(mask, true_val[i], false_val[i]); ++} ++ ++ ++static inline int const_time_memcmp(const void *a, const void *b, size_t len) ++{ ++ const unsigned char *aa = a; ++ const unsigned char *bb = b; ++ int diff, res = 0; ++ unsigned int mask; ++ ++ if (len == 0) ++ return 0; ++ do { ++ len--; ++ diff = (int) aa[len] - (int) bb[len]; ++ mask = const_time_is_zero((unsigned int) diff); ++ res = const_time_select_int(mask, res, diff); ++ } while (len); ++ ++ return res; ++} ++ ++#endif /* CONST_TIME_H */ +diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c +index d94851c3aa..d428644539 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c ++++ b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c +@@ -1,7 +1,5 @@ +-/* +- * Copyright (c) Dan Harkins, 2012 +- * +- * Copyright holder grants permission for redistribution and use in source ++/** ++ * copyright holder grants permission for redistribution and use in source + * and binary forms, with or without modification, provided that the + * following conditions are met: + * 1. Redistribution of source code must retain the above copyright +@@ -29,94 +27,233 @@ + * This license and distribution terms cannot be changed. In other words, + * this code cannot simply be copied and put under a different distribution + * license (including the GNU public license). ++ * ++ * @copyright (c) Dan Harkins, 2012 + */ + + RCSID("$Id: d94851c3aa0fc31db9be2d01a4fb94c1a6c81e00 $") + USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + + #include "eap_pwd.h" ++#include "const_time.h" ++#include + +-#include +-#include ++static uint8_t allzero[SHA256_DIGEST_LENGTH] = { 0x00 }; + + /* The random function H(x) = HMAC-SHA256(0^32, x) */ +-static void H_Init(HMAC_CTX *ctx) ++static void pwd_hmac_final(HMAC_CTX *hmac_ctx, uint8_t *digest) + { +- uint8_t allzero[SHA256_DIGEST_LENGTH]; ++ unsigned int mdlen = SHA256_DIGEST_LENGTH; ++ HMAC_Final(hmac_ctx, digest, &mdlen); ++// HMAC_CTX_reset(hmac_ctx); ++} + +- memset(allzero, 0, SHA256_DIGEST_LENGTH); ++/* a counter-based KDF based on NIST SP800-108 */ ++static void eap_pwd_kdf(uint8_t *key, int keylen, char const *label, ++ int label_len, uint8_t *result, int result_bit_len) ++{ ++ HMAC_CTX *hmac_ctx; ++ uint8_t digest[SHA256_DIGEST_LENGTH]; ++ uint16_t i, ctr, L; ++ int result_byte_len, len = 0; ++ unsigned int mdlen = SHA256_DIGEST_LENGTH; ++ uint8_t mask = 0xff; ++ ++ MEM(hmac_ctx = HMAC_CTX_new()); ++ result_byte_len = (result_bit_len + 7) / 8; ++ ++ ctr = 0; ++ L = htons(result_bit_len); ++ while (len < result_byte_len) { ++ ctr++; i = htons(ctr); + +- HMAC_Init_ex(ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL); ++ HMAC_Init_ex(hmac_ctx, key, keylen, EVP_sha256(), NULL); ++ if (ctr > 1) HMAC_Update(hmac_ctx, digest, mdlen); ++ HMAC_Update(hmac_ctx, (uint8_t *) &i, sizeof(uint16_t)); ++ HMAC_Update(hmac_ctx, (uint8_t const *)label, label_len); ++ HMAC_Update(hmac_ctx, (uint8_t *) &L, sizeof(uint16_t)); ++ HMAC_Final(hmac_ctx, digest, &mdlen); ++ if ((len + (int) mdlen) > result_byte_len) { ++ memcpy(result + len, digest, result_byte_len - len); ++ } else { ++ memcpy(result + len, digest, mdlen); ++ } ++ len += mdlen; ++// HMAC_CTX_reset(hmac_ctx); ++ } ++ ++ /* since we're expanding to a bit length, mask off the excess */ ++ if (result_bit_len % 8) { ++ mask <<= (8 - (result_bit_len % 8)); ++ result[result_byte_len - 1] &= mask; ++ } ++ ++ HMAC_CTX_free(hmac_ctx); + } + +-static void H_Update(HMAC_CTX *ctx, uint8_t const *data, int len) ++static BIGNUM *consttime_BN (void) + { +- HMAC_Update(ctx, data, len); ++ BIGNUM *bn; ++ ++ bn = BN_new(); ++ if (bn) BN_set_flags(bn, BN_FLG_CONSTTIME); ++ return bn; + } + +-static void H_Final(HMAC_CTX *ctx, uint8_t *digest) ++/* ++ * compute the legendre symbol in constant time ++ */ ++static int legendre(BIGNUM *a, BIGNUM *p, BN_CTX *bnctx) + { +- unsigned int mdlen = SHA256_DIGEST_LENGTH; ++ int symbol; ++ unsigned int mask; ++ BIGNUM *res, *pm1over2; ++ ++ pm1over2 = consttime_BN(); ++ res = consttime_BN(); ++ ++ if (!BN_sub(pm1over2, p, BN_value_one()) || ++ !BN_rshift1(pm1over2, pm1over2) || ++ !BN_mod_exp_mont_consttime(res, a, pm1over2, p, bnctx, NULL)) { ++ BN_free(pm1over2); ++ BN_free(res); ++ return -2; ++ } + +- HMAC_Final(ctx, digest, &mdlen); ++ symbol = -1; ++ mask = const_time_eq(BN_is_word(res, 1), 1); ++ symbol = const_time_select_int(mask, 1, symbol); ++ mask = const_time_eq(BN_is_zero(res), 1); ++ symbol = const_time_select_int(mask, -1, symbol); ++ ++ BN_free(pm1over2); ++ BN_free(res); ++ ++ return symbol; + } + +-/* a counter-based KDF based on NIST SP800-108 */ +-static int eap_pwd_kdf(uint8_t *key, int keylen, char const *label, int labellen, uint8_t *result, int resultbitlen) ++static void do_equation(EC_GROUP *group, BIGNUM *y2, BIGNUM *x, BN_CTX *bnctx) + { +- HMAC_CTX *hctx = NULL; +- uint8_t digest[SHA256_DIGEST_LENGTH]; +- uint16_t i, ctr, L; +- int resultbytelen, len = 0; +- unsigned int mdlen = SHA256_DIGEST_LENGTH; +- uint8_t mask = 0xff; ++ BIGNUM *p, *a, *b, *tmp1, *pm1; + +- hctx = HMAC_CTX_new(); +- if (hctx == NULL) { +- DEBUG("failed allocating HMAC context"); +- return -1; ++ tmp1 = BN_new(); ++ pm1 = BN_new(); ++ p = BN_new(); ++ a = BN_new(); ++ b = BN_new(); ++ EC_GROUP_get_curve(group, p, a, b, bnctx); ++ ++ BN_sub(pm1, p, BN_value_one()); ++ ++ /* ++ * y2 = x^3 + ax + b ++ */ ++ BN_mod_sqr(tmp1, x, p, bnctx); ++ BN_mod_mul(y2, tmp1, x, p, bnctx); ++ BN_mod_mul(tmp1, a, x, p, bnctx); ++ BN_mod_add_quick(y2, y2, tmp1, p); ++ BN_mod_add_quick(y2, y2, b, p); ++ ++ BN_free(tmp1); ++ BN_free(pm1); ++ BN_free(p); ++ BN_free(a); ++ BN_free(b); ++ ++ return; ++} ++ ++static int is_quadratic_residue(BIGNUM *val, BIGNUM *p, BIGNUM *qr, BIGNUM *qnr, BN_CTX *bnctx) ++{ ++ int offset, check, ret = 0; ++ BIGNUM *r = NULL, *pm1 = NULL, *res = NULL, *qr_or_qnr = NULL; ++ unsigned int mask; ++ unsigned char *qr_bin = NULL, *qnr_bin = NULL, *qr_or_qnr_bin = NULL; ++ ++ if (((r = consttime_BN()) == NULL) || ++ ((res = consttime_BN()) == NULL) || ++ ((qr_or_qnr = consttime_BN()) == NULL) || ++ ((pm1 = consttime_BN()) == NULL)) { ++ ret = -2; ++ goto fail; + } +- resultbytelen = (resultbitlen + 7)/8; +- ctr = 0; +- L = htons(resultbitlen); +- while (len < resultbytelen) { +- ctr++; i = htons(ctr); +- HMAC_Init_ex(hctx, key, keylen, EVP_sha256(), NULL); +- if (ctr > 1) { +- HMAC_Update(hctx, digest, mdlen); +- } +- HMAC_Update(hctx, (uint8_t *) &i, sizeof(uint16_t)); +- HMAC_Update(hctx, (uint8_t const *)label, labellen); +- HMAC_Update(hctx, (uint8_t *) &L, sizeof(uint16_t)); +- HMAC_Final(hctx, digest, &mdlen); +- if ((len + (int) mdlen) > resultbytelen) { +- memcpy(result + len, digest, resultbytelen - len); +- } else { +- memcpy(result + len, digest, mdlen); +- } +- len += mdlen; ++ ++ if (((qr_bin = (unsigned char *)malloc(BN_num_bytes(p))) == NULL) || ++ ((qnr_bin = (unsigned char *)malloc(BN_num_bytes(p))) == NULL) || ++ ((qr_or_qnr_bin = (unsigned char *)malloc(BN_num_bytes(p))) == NULL)) { ++ ret = -2; ++ goto fail; + } +- HMAC_CTX_free(hctx); + +- /* since we're expanding to a bit length, mask off the excess */ +- if (resultbitlen % 8) { +- mask <<= (8 - (resultbitlen % 8)); +- result[resultbytelen - 1] &= mask; ++ /* ++ * we select binary in constant time so make them binary ++ */ ++ memset(qr_bin, 0, BN_num_bytes(p)); ++ memset(qnr_bin, 0, BN_num_bytes(p)); ++ memset(qr_or_qnr_bin, 0, BN_num_bytes(p)); ++ ++ offset = BN_num_bytes(p) - BN_num_bytes(qr); ++ BN_bn2bin(qr, qr_bin + offset); ++ ++ offset = BN_num_bytes(p) - BN_num_bytes(qnr); ++ BN_bn2bin(qnr, qnr_bin + offset); ++ ++ /* ++ * r = (random() mod p-1) + 1 ++ */ ++ BN_sub(pm1, p, BN_value_one()); ++ BN_rand_range(r, pm1); ++ BN_add(r, r, BN_value_one()); ++ ++ BN_copy(res, val); ++ ++ /* ++ * res = val * r * r which ensures res != val but has same quadratic residocity ++ */ ++ BN_mod_mul(res, res, r, p, bnctx); ++ BN_mod_mul(res, res, r, p, bnctx); ++ ++ /* ++ * if r is even (mask is -1) then multiply by qnr and our check is qnr ++ * otherwise multiply by qr and our check is qr ++ */ ++ mask = const_time_is_zero(BN_is_odd(r)); ++ const_time_select_bin(mask, qnr_bin, qr_bin, BN_num_bytes(p), qr_or_qnr_bin); ++ BN_bin2bn(qr_or_qnr_bin, BN_num_bytes(p), qr_or_qnr); ++ BN_mod_mul(res, res, qr_or_qnr, p, bnctx); ++ check = const_time_select_int(mask, -1, 1); ++ ++ if ((ret = legendre(res, p, bnctx)) == -2) { ++ ret = -1; /* just say no it's not */ ++ goto fail; + } ++ mask = const_time_eq(ret, check); ++ ret = const_time_select_int(mask, 1, 0); + +- return 0; ++fail: ++ if (qr_bin != NULL) free(qr_bin); ++ if (qnr_bin != NULL) free(qnr_bin); ++ if (qr_or_qnr_bin != NULL) free(qr_or_qnr_bin); ++ BN_free(r); ++ BN_free(res); ++ BN_free(qr_or_qnr); ++ BN_free(pm1); ++ ++ return ret; + } + +-int compute_password_element (pwd_session_t *session, uint16_t grp_num, ++int compute_password_element (REQUEST *request, pwd_session_t *session, uint16_t grp_num, + char const *password, int password_len, + char const *id_server, int id_server_len, + char const *id_peer, int id_peer_len, + uint32_t *token) + { +- BIGNUM *x_candidate = NULL, *rnd = NULL, *cofactor = NULL; ++ BIGNUM *x_candidate = NULL, *rnd = NULL, *y_sqrd = NULL, *qr = NULL, *qnr = NULL; + HMAC_CTX *ctx = NULL; +- uint8_t pwe_digest[SHA256_DIGEST_LENGTH], *prfbuf = NULL, ctr; +- int nid, is_odd, primebitlen, primebytelen, ret = 0; ++ uint8_t pwe_digest[SHA256_DIGEST_LENGTH], *prfbuf = NULL, *xbuf = NULL, *pm1buf = NULL, ctr; ++ int nid, is_odd, primebitlen, primebytelen, ret = 0, found = 0, mask; ++ int save, i, rbits, qr_or_qnr, save_is_odd = 0, cmp; ++ unsigned int skip; + + ctx = HMAC_CTX_new(); + if (ctx == NULL) { +@@ -159,17 +296,19 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num, + goto fail; + } + +- if (((rnd = BN_new()) == NULL) || +- ((cofactor = BN_new()) == NULL) || ++ if (((rnd = consttime_BN()) == NULL) || + ((session->pwe = EC_POINT_new(session->group)) == NULL) || +- ((session->order = BN_new()) == NULL) || +- ((session->prime = BN_new()) == NULL) || +- ((x_candidate = BN_new()) == NULL)) { ++ ((session->order = consttime_BN()) == NULL) || ++ ((session->prime = consttime_BN()) == NULL) || ++ ((qr = consttime_BN()) == NULL) || ++ ((qnr = consttime_BN()) == NULL) || ++ ((x_candidate = consttime_BN()) == NULL) || ++ ((y_sqrd = consttime_BN()) == NULL)) { + DEBUG("unable to create bignums"); + goto fail; + } + +- if (!EC_GROUP_get_curve_GFp(session->group, session->prime, NULL, NULL, NULL)) { ++ if (!EC_GROUP_get_curve(session->group, session->prime, NULL, NULL, NULL)) { + DEBUG("unable to get prime for GFp curve"); + goto fail; + } +@@ -179,46 +318,61 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num, + goto fail; + } + +- if (!EC_GROUP_get_cofactor(session->group, cofactor, NULL)) { +- DEBUG("unable to get cofactor for curve"); +- goto fail; +- } +- + primebitlen = BN_num_bits(session->prime); + primebytelen = BN_num_bytes(session->prime); + if ((prfbuf = talloc_zero_array(session, uint8_t, primebytelen)) == NULL) { + DEBUG("unable to alloc space for prf buffer"); + goto fail; + } ++ if ((xbuf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) { ++ DEBUG("unable to alloc space for x buffer"); ++ goto fail; ++ } ++ if ((pm1buf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) { ++ DEBUG("unable to alloc space for pm1 buffer"); ++ goto fail; ++ } ++ ++ /* ++ * derive random quadradic residue and quadratic non-residue ++ */ ++ do { ++ BN_rand_range(qr, session->prime); ++ } while (legendre(qr, session->prime, session->bnctx) != 1); ++ ++ do { ++ BN_rand_range(qnr, session->prime); ++ } while (legendre(qnr, session->prime, session->bnctx) != -1); ++ ++ if (!BN_sub(rnd, session->prime, BN_value_one())) { ++ goto fail; ++ } ++ BN_bn2bin(rnd, pm1buf); ++ ++ save_is_odd = 0; ++ found = 0; ++ memset(xbuf, 0, primebytelen); + ctr = 0; +- while (1) { +- if (ctr > 100) { +- DEBUG("unable to find random point on curve for group %d, something's fishy", grp_num); +- goto fail; +- } ++ while (ctr < 40) { + ctr++; + + /* + * compute counter-mode password value and stretch to prime +- * pwd-seed = H(token | peer-id | server-id | password | +- * counter) ++ * pwd-seed = H(token | peer-id | server-id | password | ++ * counter) + */ +- H_Init(ctx); +- H_Update(ctx, (uint8_t *)token, sizeof(*token)); +- H_Update(ctx, (uint8_t const *)id_peer, id_peer_len); +- H_Update(ctx, (uint8_t const *)id_server, id_server_len); +- H_Update(ctx, (uint8_t const *)password, password_len); +- H_Update(ctx, (uint8_t *)&ctr, sizeof(ctr)); +- H_Final(ctx, pwe_digest); ++ HMAC_Init_ex(ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(),NULL); ++ HMAC_Update(ctx, (uint8_t *)token, sizeof(*token)); ++ HMAC_Update(ctx, (uint8_t const *)id_peer, id_peer_len); ++ HMAC_Update(ctx, (uint8_t const *)id_server, id_server_len); ++ HMAC_Update(ctx, (uint8_t const *)password, password_len); ++ HMAC_Update(ctx, (uint8_t *)&ctr, sizeof(ctr)); ++ pwd_hmac_final(ctx, pwe_digest); + + BN_bin2bn(pwe_digest, SHA256_DIGEST_LENGTH, rnd); +- if (eap_pwd_kdf(pwe_digest, SHA256_DIGEST_LENGTH, "EAP-pwd Hunting And Pecking", +- strlen("EAP-pwd Hunting And Pecking"), prfbuf, primebitlen) != 0) { +- DEBUG("key derivation function failed"); +- goto fail; +- } ++ eap_pwd_kdf(pwe_digest, SHA256_DIGEST_LENGTH, "EAP-pwd Hunting And Pecking", ++ strlen("EAP-pwd Hunting And Pecking"), prfbuf, primebitlen); + +- BN_bin2bn(prfbuf, primebytelen, x_candidate); + /* + * eap_pwd_kdf() returns a string of bits 0..primebitlen but + * BN_bin2bn will treat that string of bits as a big endian +@@ -226,49 +380,73 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num, + * then excessive bits-- those _after_ primebitlen-- so now + * we have to shift right the amount we masked off. + */ +- if (primebitlen % 8) BN_rshift(x_candidate, x_candidate, (8 - (primebitlen % 8))); +- if (BN_ucmp(x_candidate, session->prime) >= 0) continue; ++ if (primebitlen % 8) { ++ rbits = 8 - (primebitlen % 8); ++ for (i = primebytelen - 1; i > 0; i--) { ++ prfbuf[i] = (prfbuf[i - 1] << (8 - rbits)) | (prfbuf[i] >> rbits); ++ } ++ prfbuf[0] >>= rbits; ++ } ++ BN_bin2bn(prfbuf, primebytelen, x_candidate); + + /* +- * need to unambiguously identify the solution, if there is +- * one... +- */ ++ * it would've been better if the spec reduced the candidate ++ * modulo the prime but it didn't. So if the candidate >= prime ++ * we need to skip it but still run through the operations below ++ */ ++ cmp = const_time_memcmp(pm1buf, prfbuf, primebytelen); ++ skip = const_time_fill_msb((unsigned int)cmp); ++ ++ /* ++ * need to unambiguously identify the solution, if there is ++ * one.. ++ */ + is_odd = BN_is_odd(rnd) ? 1 : 0; + + /* +- * solve the quadratic equation, if it's not solvable then we +- * don't have a point +- */ +- if (!EC_POINT_set_compressed_coordinates_GFp(session->group, session->pwe, x_candidate, is_odd, NULL)) { +- continue; +- } ++ * check whether x^3 + a*x + b is a quadratic residue ++ * ++ * save the first quadratic residue we find in the loop but do ++ * it in constant time. ++ */ ++ do_equation(session->group, y_sqrd, x_candidate, session->bnctx); ++ qr_or_qnr = is_quadratic_residue(y_sqrd, session->prime, qr, qnr, session->bnctx); + + /* +- * If there's a solution to the equation then the point must be +- * on the curve so why check again explicitly? OpenSSL code +- * says this is required by X9.62. We're not X9.62 but it can't +- * hurt just to be sure. +- */ +- if (!EC_POINT_is_on_curve(session->group, session->pwe, NULL)) { +- DEBUG("EAP-pwd: point is not on curve"); +- continue; +- } ++ * if the candidate >= prime then we want to skip it ++ */ ++ qr_or_qnr = const_time_select(skip, 0, qr_or_qnr); + +- if (BN_cmp(cofactor, BN_value_one())) { +- /* make sure the point is not in a small sub-group */ +- if (!EC_POINT_mul(session->group, session->pwe, NULL, session->pwe, +- cofactor, NULL)) { +- DEBUG("EAP-pwd: cannot multiply generator by order"); +- continue; +- } ++ /* ++ * if we haven't found PWE yet (found = 0) then mask will be true, ++ * if we have found PWE then mask will be false ++ */ ++ mask = const_time_select(found, 0, -1); + +- if (EC_POINT_is_at_infinity(session->group, session->pwe)) { +- DEBUG("EAP-pwd: point is at infinity"); +- continue; +- } +- } +- /* if we got here then we have a new generator. */ +- break; ++ /* ++ * save will be 1 if we want to save this value-- i.e. we haven't ++ * found PWE yet and this is a quadratic residue-- and 0 otherwise ++ */ ++ save = const_time_select(mask, qr_or_qnr, 0); ++ ++ /* ++ * mask will be true (-1) if we want to save this and false (0) ++ * otherwise ++ */ ++ mask = const_time_eq(save, 1); ++ ++ const_time_select_bin(mask, prfbuf, xbuf, primebytelen, xbuf); ++ save_is_odd = const_time_select(mask, is_odd, save_is_odd); ++ found = const_time_select(mask, -1, found); ++ } ++ ++ /* ++ * now we can savely construct PWE ++ */ ++ BN_bin2bn(xbuf, primebytelen, x_candidate); ++ if (!EC_POINT_set_compressed_coordinates(session->group, session->pwe, ++ x_candidate, save_is_odd, NULL)) { ++ goto fail; + } + + session->group_num = grp_num; +@@ -278,78 +456,81 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num, + } + + /* cleanliness and order.... */ +- BN_clear_free(cofactor); + BN_clear_free(x_candidate); ++ BN_clear_free(y_sqrd); ++ BN_clear_free(qr); ++ BN_clear_free(qnr); + BN_clear_free(rnd); +- talloc_free(prfbuf); ++ ++ if (prfbuf) talloc_free(prfbuf); ++ if (xbuf) talloc_free(xbuf); ++ if (pm1buf) talloc_free(pm1buf); ++ + HMAC_CTX_free(ctx); + + return ret; + } + +-int compute_scalar_element (pwd_session_t *session, BN_CTX *bnctx) { ++int compute_scalar_element(REQUEST *request, pwd_session_t *session, BN_CTX *bn_ctx) ++{ + BIGNUM *mask = NULL; + int ret = -1; + +- if (((session->private_value = BN_new()) == NULL) || +- ((session->my_element = EC_POINT_new(session->group)) == NULL) || +- ((session->my_scalar = BN_new()) == NULL) || +- ((mask = BN_new()) == NULL)) { +- DEBUG2("server scalar allocation failed"); +- goto fail; +- } ++ MEM(session->private_value = BN_new()); ++ MEM(session->my_element = EC_POINT_new(session->group)); ++ MEM(session->my_scalar = BN_new()); ++ ++ MEM(mask = BN_new()); + + if (BN_rand_range(session->private_value, session->order) != 1) { +- DEBUG2("Unable to get randomness for private_value"); +- goto fail; ++ REDEBUG("Unable to get randomness for private_value"); ++ goto error; + } + if (BN_rand_range(mask, session->order) != 1) { +- DEBUG2("Unable to get randomness for mask"); +- goto fail; ++ REDEBUG("Unable to get randomness for mask"); ++ goto error; + } + BN_add(session->my_scalar, session->private_value, mask); +- BN_mod(session->my_scalar, session->my_scalar, session->order, bnctx); ++ BN_mod(session->my_scalar, session->my_scalar, session->order, bn_ctx); + +- if (!EC_POINT_mul(session->group, session->my_element, NULL, session->pwe, mask, bnctx)) { +- DEBUG2("server element allocation failed"); +- goto fail; ++ if (!EC_POINT_mul(session->group, session->my_element, NULL, session->pwe, mask, bn_ctx)) { ++ REDEBUG("Server element allocation failed"); ++ goto error; + } + +- if (!EC_POINT_invert(session->group, session->my_element, bnctx)) { +- DEBUG2("server element inversion failed"); +- goto fail; ++ if (!EC_POINT_invert(session->group, session->my_element, bn_ctx)) { ++ REDEBUG("Server element inversion failed"); ++ goto error; + } + + ret = 0; + +-fail: ++error: + BN_clear_free(mask); + + return ret; + } + +-int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_CTX *bnctx) ++int process_peer_commit(REQUEST *request, pwd_session_t *session, uint8_t *in, size_t in_len, BN_CTX *bn_ctx) + { +- uint8_t *ptr; +- size_t data_len; +- BIGNUM *x = NULL, *y = NULL, *cofactor = NULL; +- EC_POINT *K = NULL, *point = NULL; +- int res = 1; +- +- if (((session->peer_scalar = BN_new()) == NULL) || +- ((session->k = BN_new()) == NULL) || +- ((cofactor = BN_new()) == NULL) || +- ((x = BN_new()) == NULL) || +- ((y = BN_new()) == NULL) || +- ((point = EC_POINT_new(session->group)) == NULL) || +- ((K = EC_POINT_new(session->group)) == NULL) || +- ((session->peer_element = EC_POINT_new(session->group)) == NULL)) { +- DEBUG2("pwd: failed to allocate room to process peer's commit"); +- goto finish; +- } ++ uint8_t *ptr; ++ size_t data_len; ++ BIGNUM *x = NULL, *y = NULL, *cofactor = NULL; ++ EC_POINT *K = NULL, *point = NULL; ++ int ret = 1; ++ ++ MEM(session->peer_scalar = BN_new()); ++ MEM(session->k = BN_new()); ++ MEM(session->peer_element = EC_POINT_new(session->group)); ++ MEM(point = EC_POINT_new(session->group)); ++ MEM(K = EC_POINT_new(session->group)); ++ ++ MEM(cofactor = BN_new()); ++ MEM(x = BN_new()); ++ MEM(y = BN_new()); + + if (!EC_GROUP_get_cofactor(session->group, cofactor, NULL)) { +- DEBUG2("pwd: unable to get group co-factor"); ++ REDEBUG("Unable to get group co-factor"); + goto finish; + } + +@@ -361,7 +542,7 @@ int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_ + * Did the peer send enough data? + */ + if (in_len < (2 * data_len + BN_num_bytes(session->order))) { +- DEBUG("pwd: Invalid commit packet"); ++ REDEBUG("Invalid commit packet"); + goto finish; + } + +@@ -377,54 +558,54 @@ int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_ + if (BN_is_zero(session->peer_scalar) || + BN_is_one(session->peer_scalar) || + BN_cmp(session->peer_scalar, session->order) >= 0) { +- ERROR("Peer's scalar is not within the allowed range"); ++ REDEBUG("Peer's scalar is not within the allowed range"); + goto finish; + } + +- if (!EC_POINT_set_affine_coordinates_GFp(session->group, session->peer_element, x, y, bnctx)) { +- DEBUG2("pwd: unable to get coordinates of peer's element"); ++ if (!EC_POINT_set_affine_coordinates(session->group, session->peer_element, x, y, bn_ctx)) { ++ REDEBUG("Unable to get coordinates of peer's element"); + goto finish; + } + + /* validate received element */ +- if (!EC_POINT_is_on_curve(session->group, session->peer_element, bnctx) || ++ if (!EC_POINT_is_on_curve(session->group, session->peer_element, bn_ctx) || + EC_POINT_is_at_infinity(session->group, session->peer_element)) { +- ERROR("Peer's element is not a point on the elliptic curve"); ++ REDEBUG("Peer's element is not a point on the elliptic curve"); + goto finish; + } + + /* check to ensure peer's element is not in a small sub-group */ + if (BN_cmp(cofactor, BN_value_one())) { + if (!EC_POINT_mul(session->group, point, NULL, session->peer_element, cofactor, NULL)) { +- DEBUG2("pwd: unable to multiply element by co-factor"); ++ REDEBUG("Unable to multiply element by co-factor"); + goto finish; + } + + if (EC_POINT_is_at_infinity(session->group, point)) { +- DEBUG2("pwd: peer's element is in small sub-group"); ++ REDEBUG("Peer's element is in small sub-group"); + goto finish; + } + } + + /* detect reflection attacks */ + if (BN_cmp(session->peer_scalar, session->my_scalar) == 0 || +- EC_POINT_cmp(session->group, session->peer_element, session->my_element, bnctx) == 0) { +- ERROR("Reflection attack detected"); ++ EC_POINT_cmp(session->group, session->peer_element, session->my_element, bn_ctx) == 0) { ++ REDEBUG("Reflection attack detected"); + goto finish; + } + + /* compute the shared key, k */ +- if ((!EC_POINT_mul(session->group, K, NULL, session->pwe, session->peer_scalar, bnctx)) || +- (!EC_POINT_add(session->group, K, K, session->peer_element, bnctx)) || +- (!EC_POINT_mul(session->group, K, NULL, K, session->private_value, bnctx))) { +- DEBUG2("pwd: unable to compute shared key, k"); ++ if ((!EC_POINT_mul(session->group, K, NULL, session->pwe, session->peer_scalar, bn_ctx)) || ++ (!EC_POINT_add(session->group, K, K, session->peer_element, bn_ctx)) || ++ (!EC_POINT_mul(session->group, K, NULL, K, session->private_value, bn_ctx))) { ++ REDEBUG("Unable to compute shared key, k"); + goto finish; + } + + /* ensure that the shared key isn't in a small sub-group */ + if (BN_cmp(cofactor, BN_value_one())) { + if (!EC_POINT_mul(session->group, K, NULL, K, cofactor, NULL)) { +- DEBUG2("pwd: unable to multiply k by co-factor"); ++ REDEBUG("Unable to multiply k by co-factor"); + goto finish; + } + } +@@ -436,15 +617,15 @@ int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_ + * sure" so let's be safe. + */ + if (EC_POINT_is_at_infinity(session->group, K)) { +- DEBUG2("pwd: k is point-at-infinity!"); ++ REDEBUG("K is point-at-infinity"); + goto finish; + } + +- if (!EC_POINT_get_affine_coordinates_GFp(session->group, K, session->k, NULL, bnctx)) { +- DEBUG2("pwd: unable to get shared secret from K"); ++ if (!EC_POINT_get_affine_coordinates(session->group, K, session->k, NULL, bn_ctx)) { ++ REDEBUG("Unable to get shared secret from K"); + goto finish; + } +- res = 0; ++ ret = 0; + + finish: + EC_POINT_clear_free(K); +@@ -453,36 +634,29 @@ finish: + BN_clear_free(x); + BN_clear_free(y); + +- return res; ++ return ret; + } + +-int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) ++int compute_server_confirm(REQUEST *request, pwd_session_t *session, uint8_t *out, BN_CTX *bn_ctx) + { +- BIGNUM *x = NULL, *y = NULL; +- HMAC_CTX *ctx = NULL; +- uint8_t *cruft = NULL; +- int offset, req = -1; +- +- ctx = HMAC_CTX_new(); +- if (ctx == NULL) { +- DEBUG2("pwd: unable to allocate HMAC context!"); +- goto finish; +- } ++ BIGNUM *x = NULL, *y = NULL; ++ HMAC_CTX *hmac_ctx = NULL; ++ uint8_t *cruft = NULL; ++ int offset, req = -1; + + /* + * Each component of the cruft will be at most as big as the prime + */ +- if (((cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime))) == NULL) || +- ((x = BN_new()) == NULL) || ((y = BN_new()) == NULL)) { +- DEBUG2("pwd: unable to allocate space to compute confirm!"); +- goto finish; +- } ++ MEM(cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime))); ++ MEM(x = BN_new()); ++ MEM(y = BN_new()); + + /* + * commit is H(k | server_element | server_scalar | peer_element | + * peer_scalar | ciphersuite) + */ +- H_Init(ctx); ++ MEM(hmac_ctx = HMAC_CTX_new()); ++ HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL); + + /* + * Zero the memory each time because this is mod prime math and some +@@ -492,24 +666,24 @@ int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) + */ + offset = BN_num_bytes(session->prime) - BN_num_bytes(session->k); + BN_bn2bin(session->k, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->prime)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); + + /* + * next is server element: x, y + */ +- if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->my_element, x, y, bnctx)) { +- DEBUG2("pwd: unable to get coordinates of server element"); ++ if (!EC_POINT_get_affine_coordinates(session->group, session->my_element, x, y, bn_ctx)) { ++ REDEBUG("Unable to get coordinates of server element"); + goto finish; + } + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->prime) - BN_num_bytes(x); + BN_bn2bin(x, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->prime)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); + + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->prime) - BN_num_bytes(y); + BN_bn2bin(y, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->prime)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); + + /* + * and server scalar +@@ -517,25 +691,25 @@ int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar); + BN_bn2bin(session->my_scalar, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->order)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); + + /* + * next is peer element: x, y + */ +- if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->peer_element, x, y, bnctx)) { +- DEBUG2("pwd: unable to get coordinates of peer's element"); ++ if (!EC_POINT_get_affine_coordinates(session->group, session->peer_element, x, y, bn_ctx)) { ++ REDEBUG("Unable to get coordinates of peer's element"); + goto finish; + } + + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->prime) - BN_num_bytes(x); + BN_bn2bin(x, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->prime)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); + + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->prime) - BN_num_bytes(y); + BN_bn2bin(y, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->prime)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); + + /* + * and peer scalar +@@ -543,52 +717,46 @@ int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->order) - BN_num_bytes(session->peer_scalar); + BN_bn2bin(session->peer_scalar, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->order)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); + + /* + * finally, ciphersuite + */ +- H_Update(ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); ++ HMAC_Update(hmac_ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); + +- H_Final(ctx, out); ++ pwd_hmac_final(hmac_ctx, out); + + req = 0; ++ + finish: ++ HMAC_CTX_free(hmac_ctx); + talloc_free(cruft); + BN_free(x); + BN_free(y); +- HMAC_CTX_free(ctx); + + return req; + } + +-int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) ++int compute_peer_confirm(REQUEST *request, pwd_session_t *session, uint8_t *out, BN_CTX *bn_ctx) + { +- BIGNUM *x = NULL, *y = NULL; +- HMAC_CTX *ctx = NULL; +- uint8_t *cruft = NULL; +- int offset, req = -1; +- +- ctx = HMAC_CTX_new(); +- if (ctx == NULL) { +- DEBUG2("pwd: unable to allocate HMAC context!"); +- goto finish; +- } ++ BIGNUM *x = NULL, *y = NULL; ++ HMAC_CTX *hmac_ctx = NULL; ++ uint8_t *cruft = NULL; ++ int offset, req = -1; + + /* + * Each component of the cruft will be at most as big as the prime + */ +- if (((cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime))) == NULL) || +- ((x = BN_new()) == NULL) || ((y = BN_new()) == NULL)) { +- DEBUG2("pwd: unable to allocate space to compute confirm!"); +- goto finish; +- } ++ MEM(cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime))); ++ MEM(x = BN_new()); ++ MEM(y = BN_new()); + + /* + * commit is H(k | server_element | server_scalar | peer_element | + * peer_scalar | ciphersuite) + */ +- H_Init(ctx); ++ MEM(hmac_ctx = HMAC_CTX_new()); ++ HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL); + + /* + * Zero the memory each time because this is mod prime math and some +@@ -598,25 +766,25 @@ int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) + */ + offset = BN_num_bytes(session->prime) - BN_num_bytes(session->k); + BN_bn2bin(session->k, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->prime)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); + + /* + * then peer element: x, y + */ +- if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->peer_element, x, y, bnctx)) { +- DEBUG2("pwd: unable to get coordinates of peer's element"); ++ if (!EC_POINT_get_affine_coordinates(session->group, session->peer_element, x, y, bn_ctx)) { ++ REDEBUG("Unable to get coordinates of peer's element"); + goto finish; + } + + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->prime) - BN_num_bytes(x); + BN_bn2bin(x, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->prime)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); + + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->prime) - BN_num_bytes(y); + BN_bn2bin(y, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->prime)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); + + /* + * and peer scalar +@@ -624,24 +792,24 @@ int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->order) - BN_num_bytes(session->peer_scalar); + BN_bn2bin(session->peer_scalar, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->order)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); + + /* + * then server element: x, y + */ +- if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->my_element, x, y, bnctx)) { +- DEBUG2("pwd: unable to get coordinates of server element"); ++ if (!EC_POINT_get_affine_coordinates(session->group, session->my_element, x, y, bn_ctx)) { ++ REDEBUG("Unable to get coordinates of server element"); + goto finish; + } + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->prime) - BN_num_bytes(x); + BN_bn2bin(x, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->prime)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); + + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->prime) - BN_num_bytes(y); + BN_bn2bin(y, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->prime)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); + + /* + * and server scalar +@@ -649,94 +817,75 @@ int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar); + BN_bn2bin(session->my_scalar, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->order)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); + + /* + * finally, ciphersuite + */ +- H_Update(ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); ++ HMAC_Update(hmac_ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); + +- H_Final(ctx, out); ++ pwd_hmac_final(hmac_ctx, out); + + req = 0; + finish: ++ HMAC_CTX_free(hmac_ctx); + talloc_free(cruft); + BN_free(x); + BN_free(y); +- HMAC_CTX_free(ctx); + + return req; + } + +-int compute_keys (pwd_session_t *session, uint8_t *peer_confirm, uint8_t *msk, uint8_t *emsk) ++int compute_keys(UNUSED REQUEST *request, pwd_session_t *session, uint8_t *peer_confirm, uint8_t *msk, uint8_t *emsk) + { +- HMAC_CTX *ctx = NULL; +- uint8_t mk[SHA256_DIGEST_LENGTH], *cruft = NULL; +- uint8_t session_id[SHA256_DIGEST_LENGTH + 1]; +- uint8_t msk_emsk[128]; /* 64 each */ +- int offset, ret = -1; +- +- ctx = HMAC_CTX_new(); +- if (ctx == NULL) { +- DEBUG2("pwd: unable to allocate HMAC context!"); +- goto finish; +- } ++ HMAC_CTX *hmac_ctx; ++ uint8_t mk[SHA256_DIGEST_LENGTH], *cruft; ++ uint8_t session_id[SHA256_DIGEST_LENGTH + 1]; ++ uint8_t msk_emsk[128]; /* 64 each */ ++ int offset; + +- if ((cruft = talloc_array(session, uint8_t, BN_num_bytes(session->prime))) == NULL) { +- DEBUG2("pwd: unable to allocate space to compute keys"); +- goto finish; +- } ++ MEM(cruft = talloc_array(session, uint8_t, BN_num_bytes(session->prime))); ++ MEM(hmac_ctx = HMAC_CTX_new()); + + /* + * first compute the session-id = TypeCode | H(ciphersuite | scal_p | + * scal_s) + */ + session_id[0] = PW_EAP_PWD; +- H_Init(ctx); +- H_Update(ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); ++ HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL); ++ HMAC_Update(hmac_ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); + offset = BN_num_bytes(session->order) - BN_num_bytes(session->peer_scalar); + memset(cruft, 0, BN_num_bytes(session->prime)); + BN_bn2bin(session->peer_scalar, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->order)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); + offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar); + memset(cruft, 0, BN_num_bytes(session->prime)); + BN_bn2bin(session->my_scalar, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->order)); +- H_Final(ctx, (uint8_t *)&session_id[1]); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); ++ pwd_hmac_final(hmac_ctx, (uint8_t *)&session_id[1]); + + /* then compute MK = H(k | commit-peer | commit-server) */ +- H_Init(ctx); ++ HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL); + + memset(cruft, 0, BN_num_bytes(session->prime)); + offset = BN_num_bytes(session->prime) - BN_num_bytes(session->k); + BN_bn2bin(session->k, cruft + offset); +- H_Update(ctx, cruft, BN_num_bytes(session->prime)); ++ HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); + +- H_Update(ctx, peer_confirm, SHA256_DIGEST_LENGTH); ++ HMAC_Update(hmac_ctx, peer_confirm, SHA256_DIGEST_LENGTH); + +- H_Update(ctx, session->my_confirm, SHA256_DIGEST_LENGTH); ++ HMAC_Update(hmac_ctx, session->my_confirm, SHA256_DIGEST_LENGTH); + +- H_Final(ctx, mk); ++ pwd_hmac_final(hmac_ctx, mk); + + /* stretch the mk with the session-id to get MSK | EMSK */ +- if (eap_pwd_kdf(mk, SHA256_DIGEST_LENGTH, (char const *)session_id, +- SHA256_DIGEST_LENGTH + 1, msk_emsk, +- /* it's bits, ((64 + 64) * 8) */ +- 1024) != 0) { +- DEBUG("key derivation function failed"); +- goto finish; +- } ++ eap_pwd_kdf(mk, SHA256_DIGEST_LENGTH, (char const *)session_id, ++ SHA256_DIGEST_LENGTH + 1, msk_emsk, 1024); /* it's bits, ((64 + 64) * 8) */ + + memcpy(msk, msk_emsk, 64); + memcpy(emsk, msk_emsk + 64, 64); + +- ret = 0; +-finish: ++ HMAC_CTX_free(hmac_ctx); + talloc_free(cruft); +- HMAC_CTX_free(ctx); +- return ret; ++ return 0; + } +- +- +- +- +diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h +index ca12778f61..a40a346069 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h ++++ b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h +@@ -102,18 +102,22 @@ typedef struct _pwd_session_t { + EC_POINT *my_element; + EC_POINT *peer_element; + uint8_t my_confirm[SHA256_DIGEST_LENGTH]; ++ uint8_t prep; ++ uint8_t salt_present; ++ uint8_t salt_len; ++ uint8_t salt[255]; + } pwd_session_t; + +-int compute_password_element(pwd_session_t *sess, uint16_t grp_num, ++int compute_password_element(REQUEST *request, pwd_session_t *sess, uint16_t grp_num, + char const *password, int password_len, + char const *id_server, int id_server_len, + char const *id_peer, int id_peer_len, + uint32_t *token); +-int compute_scalar_element(pwd_session_t *sess, BN_CTX *bnctx); +-int process_peer_commit (pwd_session_t *sess, uint8_t *in, size_t in_len, BN_CTX *bnctx); +-int compute_server_confirm(pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx); +-int compute_peer_confirm(pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx); +-int compute_keys(pwd_session_t *sess, uint8_t *peer_confirm, ++int compute_scalar_element(REQUEST *request, pwd_session_t *sess, BN_CTX *bnctx); ++int process_peer_commit(REQUEST *request, pwd_session_t *sess, uint8_t *in, size_t in_len, BN_CTX *bnctx); ++int compute_server_confirm(REQUEST *request, pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx); ++int compute_peer_confirm(REQUEST *request, pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx); ++int compute_keys(REQUEST *request, pwd_session_t *sess, uint8_t *peer_confirm, + uint8_t *msk, uint8_t *emsk); + #ifdef PRINTBUF + void print_buf(char *str, uint8_t *buf, int len); +diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c +index 18ab97f148..4992a2aeef 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c ++++ b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c +@@ -41,11 +41,93 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + #define MPPE_KEY_LEN 32 + #define MSK_EMSK_LEN (2*MPPE_KEY_LEN) + ++/* EAP-PWD can use different preprocessing (prep) modes to mangle the password ++ * before proving to both parties that they both know the same (mangled) password. ++ * ++ * The server advertises a preprocessing mode to the client. Only "none" is ++ * mandatory to implement. ++ * ++ * What is a good selection on the preprocessing mode? ++ * ++ * a) the server uses a hashed password ++ * b) the client uses a hashed password ++ * ++ * a | b | result ++ * --+---+--------------------------------------- ++ * n | n | none ++ * n | y | hint needed (cannot know automatically) ++ * y | n | select by hash given ++ * y | y | only works if both have the same hash; select by hash given ++ * ++ * Which hash functions does the server or client need to implement? ++ * ++ * a | b | server | client ++ * --+---+------------------------+---------------------- ++ * n | n | none | none ++ * n | y | as configured | none ++ * y | n | none | as selected by server ++ * y | y | none | none ++ * ++ * RFC 5931 defines 3 and RFC 8146 another 8 hash functions to implement. ++ * Can we avoid implementing them all? Only if they are provided as hash by some ++ * other module, e.g. in SQL or statically in password database. ++ * ++ * Therefore we select the preprocessing mode by the type of password given if ++ * in automatic mode: ++ * a) Cleartext-Password or User-Password: None. ++ * If the client only supports a hash (e.g. on Windows it might only have an ++ * NT-Password), do not provide a Cleartext-Password attribute but instead ++ * preprocess the password externally (e.g. hash the Cleartext-Password ++ * into an NT-Password and drop the Cleartext-Password). ++ * b) NT-Password: rfc2759 (prep=MS). ++ * The NT-Password Hash is hashed into a HashNTPasswordHash hash. ++ * c) EAP-Pwd-Password-Hash - provides hash as binary ++ * EAP-Pwd-Password-Salt - (optional) salt to be transmitted to client ++ * (RFC 8146) ++ * EAP-Pwd-Password-Prep - constant to transmit to client in prep field ++ * ++ * Though, there is one issue left. The method needs to be selected in ++ * EAP-PWD-ID/Request, that is the first message from server and thus before ++ * the client sent its peer-id. This is feasable using the EAP-Identity frame ++ * (outer identity); EAP-PWD does transmit its peer-id in plaintext anyway. ++ * So we need a toggle for this, in case anybody needs rlm_eap_pwd to use ++ * only the peer_id (inner identity). This toogle is an integer to also support ++ * setting currently unknown nor not implemented preprocessing methods. ++ * ++ * The toogle is named "prep", is a module configuration item, and accepts the ++ * following values: ++ * prep | meaning ++ * -------+-------------------------------------------------------------------- ++ * -1 | [automatic] discover using method described above from EAP-Identity ++ * | as User-Name before EAP-PWD-Id/Request ++ * 0..255 | [static] Fixed password preprocessing method. Expects virtual ++ * | server to provide matching password given EAP-PWD ++ * | peer-id as User-Name. The virtual server is provided ++ * | with EAP-Pwd-Password-Prep containing the configured ++ * | prep value. ++ * else | reserved/invalid ++ * ++ * Attributes to provide Password/Password-Hash and possibly salt. ++ * prep | accepted attributes ++ * -------+-------------------------------------------------------------------- ++ * -1 | see above for automatic discovery ++ * 0 | Use Cleartext-Password or give cleartext in EAP-Pwd-Password-Hash ++ * 1 | Use NT-Password, Cleartext-Password, User-Password or ++ * | give hashed NT-Password hash in EAP-Pwd-Password-Hash ++ * 2..255 | Use EAP-Pwd-Password-Hash and possibly EAP-Pwd-Pasword-Salt. ++ * ++ * To be able to pass EAP-Pwd-Password-Hash and EAP-Pwd-Password-Salt als hex ++ * string, they are decoded as hex if module config option unhex=1 (default). ++ * Set it to zero if you provide binary input. ++ */ ++ + static CONF_PARSER pwd_module_config[] = { + { "group", FR_CONF_OFFSET(PW_TYPE_INTEGER, eap_pwd_t, group), "19" }, + { "fragment_size", FR_CONF_OFFSET(PW_TYPE_INTEGER, eap_pwd_t, fragment_size), "1020" }, + { "server_id", FR_CONF_OFFSET(PW_TYPE_STRING, eap_pwd_t, server_id), NULL }, + { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING, eap_pwd_t, virtual_server), NULL }, ++ { "prep", FR_CONF_OFFSET(PW_TYPE_SIGNED, eap_pwd_t, prep), "0" }, ++ { "unhex", FR_CONF_OFFSET(PW_TYPE_SIGNED, eap_pwd_t, unhex), "1" }, + CONF_PARSER_TERMINATOR + }; + +@@ -65,6 +147,11 @@ static int mod_instantiate (CONF_SECTION *cs, void **instance) + return -1; + } + ++ if (inst->prep < -1 || inst->prep > 255) { ++ cf_log_err_cs(cs, "Invalid value for password preparation method: %d", inst->prep); ++ return -1; ++ } ++ + return 0; + } + +@@ -153,18 +240,282 @@ static int send_pwd_request (pwd_session_t *session, EAP_DS *eap_ds) + return 1; + } + ++static void normify(REQUEST *request, VALUE_PAIR *vp) ++{ ++ size_t decoded; ++ size_t expected_len; ++ uint8_t *buffer; ++ ++ rad_assert((vp->da->type == PW_TYPE_OCTETS) || (vp->da->type == PW_TYPE_STRING)); ++ ++ if (vp->vp_length % 2 != 0 || vp->vp_length == 0) return; ++ ++ expected_len = vp->vp_length / 2; ++ buffer = talloc_zero_array(request, uint8_t, expected_len); ++ rad_assert(buffer); ++ ++ decoded = fr_hex2bin(buffer, expected_len, vp->vp_strvalue, vp->vp_length); ++ if (decoded == expected_len) { ++ RDEBUG2("Normalizing %s from hex encoding, %zu bytes -> %zu bytes", ++ vp->da->name, vp->vp_length, decoded); ++ fr_pair_value_memcpy(vp, buffer, decoded); ++ } else { ++ RDEBUG2("Normalizing %s from hex encoding, %zu bytes -> %zu bytes failed, got %zu bytes", ++ vp->da->name, vp->vp_length, expected_len, decoded); ++ } ++ ++ talloc_free(buffer); ++} ++ ++static int fetch_and_process_password(pwd_session_t *session, REQUEST *request, eap_pwd_t *inst) { ++ REQUEST *fake; ++ VALUE_PAIR *vp, *pw; ++ const char *pwbuf; ++ int pw_len; ++ uint8_t nthash[MD4_DIGEST_LENGTH]; ++ uint8_t nthashash[MD4_DIGEST_LENGTH]; ++ int ret = -1; ++ eap_type_t old_eap_type = 0; ++ ++ if ((fake = request_alloc_fake(request)) == NULL) { ++ RDEBUG("pwd unable to create fake request!"); ++ return ret; ++ } ++ fake->username = fr_pair_afrom_num(fake->packet, PW_USER_NAME, 0); ++ if (!fake->username) { ++ RDEBUG("Failed creating pair for peer id"); ++ goto out; ++ } ++ fr_pair_value_bstrncpy(fake->username, session->peer_id, session->peer_id_len); ++ fr_pair_add(&fake->packet->vps, fake->username); ++ ++ if (inst->prep >= 0) { ++ vp = fr_pair_afrom_num(fake->packet, PW_EAP_PWD_PASSWORD_PREP, 0); ++ rad_assert(vp != NULL); ++ vp->vp_byte = inst->prep; ++ fr_pair_add(&fake->packet->vps, vp); ++ } ++ ++ if ((vp = fr_pair_find_by_num(request->config, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) { ++ fake->server = vp->vp_strvalue; ++ } else if (inst->virtual_server) { ++ fake->server = inst->virtual_server; ++ } /* else fake->server == request->server */ ++ ++ if ((vp = fr_pair_find_by_num(request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY)) != NULL) { ++ /* EAP-Type = NAK here if inst->prep == -1. ++ * But this does not help the virtual server to differentiate ++ * based on which EAP method was selected, that is to properly ++ * prepare session-state: for PWD. ++ * So fake EAP-Type = PWD here for the time of the inner request. ++ */ ++ old_eap_type = vp->vp_integer; ++ vp->vp_integer = PW_EAP_PWD; ++ } ++ RDEBUG("Sending tunneled request"); ++ rdebug_pair_list(L_DBG_LVL_1, request, fake->packet->vps, NULL); ++ ++ if (fake->server) { ++ RDEBUG("server %s {", fake->server); ++ } else { ++ RDEBUG("server {"); ++ } ++ ++ /* ++ * Call authorization recursively, which will ++ * get the password. ++ */ ++ RINDENT(); ++ process_authorize(0, fake); ++ REXDENT(); ++ ++ /* ++ * Note that we don't do *anything* with the reply ++ * attributes. ++ */ ++ if (fake->server) { ++ RDEBUG("} # server %s", fake->server); ++ } else { ++ RDEBUG("}"); ++ } ++ ++ RDEBUG("Got tunneled reply code %d", fake->reply->code); ++ rdebug_pair_list(L_DBG_LVL_1, request, fake->reply->vps, NULL); ++ ++ if (old_eap_type && (vp = fr_pair_find_by_num(request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY)) != NULL) { ++ vp->vp_integer = old_eap_type; ++ } ++ ++ pw = fr_pair_find_by_num(fake->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY); ++ if (!pw) { ++ pw = fr_pair_find_by_num(fake->config, PW_USER_PASSWORD, 0, TAG_ANY); ++ } ++ ++ if (pw && (inst->prep < 0 || inst->prep == EAP_PWD_PREP_NONE)) { ++ VERIFY_VP(pw); ++ session->prep = EAP_PWD_PREP_NONE; ++ ++ RDEBUG("Use Cleartext-Password or User-Password for %s to do pwd authentication", ++ session->peer_id); ++ ++ pwbuf = pw->vp_strvalue; ++ pw_len = pw->vp_length; ++ ++ goto success; ++ } ++ ++ pw = fr_pair_find_by_num(fake->config, PW_NT_PASSWORD, 0, TAG_ANY); ++ ++ if (pw && (inst->prep < 0 || inst->prep == EAP_PWD_PREP_MS)) { ++ VERIFY_VP(pw); ++ session->prep = EAP_PWD_PREP_MS; ++ ++ RDEBUG("Use NT-Password for %s to do pwd authentication", ++ session->peer_id); ++ ++ if (pw->vp_length != MD4_DIGEST_LENGTH) { ++ RDEBUG("NT-Password invalid length"); ++ goto out; ++ } ++ ++ fr_md4_calc(nthashash, pw->vp_octets, pw->vp_length); ++ pwbuf = (const char*) nthashash; ++ pw_len = MD4_DIGEST_LENGTH; ++ ++ goto success; ++ } ++ ++ pw = fr_pair_find_by_num(fake->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY); ++ if (!pw) { ++ pw = fr_pair_find_by_num(fake->config, PW_USER_PASSWORD, 0, TAG_ANY); ++ } ++ ++ if (pw && inst->prep == EAP_PWD_PREP_MS) { ++ VERIFY_VP(pw); ++ session->prep = EAP_PWD_PREP_NONE; ++ ++ RDEBUG("Use Cleartext-Password or User-Password as NT-Password for %s to do pwd authentication", ++ session->peer_id); ++ ++ // compute NT-Hash from Cleartext-Password ++ ssize_t len; ++ uint8_t ucs2_password[512]; ++ len = fr_utf8_to_ucs2(ucs2_password, sizeof(ucs2_password), pw->vp_strvalue, pw->vp_length); ++ if (len < 0) { ++ ERROR("rlm_eap_pwd: Error converting password to UCS2"); ++ goto out; ++ } ++ fr_md4_calc(nthash, ucs2_password, len); ++ ++ fr_md4_calc(nthashash, nthash, MD4_DIGEST_LENGTH); ++ pwbuf = (const char*) nthashash; ++ pw_len = MD4_DIGEST_LENGTH; ++ ++ goto success; ++ } ++ ++ vp = fr_pair_find_by_num(fake->config, PW_EAP_PWD_PASSWORD_PREP, 0, TAG_ANY); ++ if (vp) { ++ VERIFY_VP(vp); ++ } ++ if (vp && inst->prep < 0) { ++ RDEBUG("Use EAP-Pwd-Password-Prep %u for %s to do pwd authentication", ++ vp->vp_byte, session->peer_id); ++ session->prep = vp->vp_byte; ++ } else if (vp && inst->prep != vp->vp_byte) { ++ RDEBUG2("Mismatch of configured password preparation method and provided EAP-Pwd-Password-Prep attribute type for %s", ++ session->peer_id); ++ goto out; ++ } else if (inst->prep < 0) { ++ RDEBUG2("Missing EAP-Pwd-Password-Prep for %s", ++ session->peer_id); ++ goto out; ++ } ++ ++ pw = fr_pair_find_by_num(fake->config, PW_EAP_PWD_PASSWORD_SALT, 0, TAG_ANY); ++ if (pw) { ++ VERIFY_VP(pw); ++ ++ RDEBUG("Use EAP-Pwd-Password-Salt for %s to do pwd authentication", ++ session->peer_id); ++ ++ if (inst->unhex) normify(request, pw); ++ ++ if (pw->vp_length > 255) { ++ /* salt len is 1 byte */ ++ RDEBUG("EAP-Pwd-Password-Salt too long (more than 255 octets)"); ++ goto out; ++ } ++ rad_assert(pw->vp_length <= sizeof(session->salt)); ++ ++ session->salt_present = 1; ++ session->salt_len = pw->vp_length; ++ memcpy(session->salt, pw->vp_octets, pw->vp_length); ++ } ++ ++ pw = fr_pair_find_by_num(fake->config, PW_EAP_PWD_PASSWORD_HASH, 0, TAG_ANY); ++ if (pw) { ++ VERIFY_VP(pw); ++ ++ RDEBUG("Use EAP-Pwd-Password-Hash for %s to do pwd authentication", ++ session->peer_id); ++ ++ if (inst->unhex) normify(request, pw); ++ ++ pwbuf = (const char*) pw->vp_octets; ++ pw_len = pw->vp_length; ++ ++ goto success; ++ } ++ ++ RDEBUG2("Mismatch of password preparation method and provided password attribute type for %s", ++ session->peer_id); ++ goto out; ++ ++success: ++ if (RDEBUG_ENABLED4) { ++ char outbuf[1024]; ++ char *p = outbuf; ++ for (int i = 0; i < pw_len && p < outbuf + sizeof(outbuf) - 3; i++) { ++ p += sprintf(p, "%02hhX", pwbuf[i]); ++ } ++ RDEBUG4("hex pw data: %s (%d)", outbuf, pw_len); ++ } ++ ++ if (compute_password_element(request, session, session->group_num, ++ pwbuf, pw_len, ++ inst->server_id, strlen(inst->server_id), ++ session->peer_id, strlen(session->peer_id), ++ &session->token)) { ++ RDEBUG("failed to obtain password element"); ++ goto out; ++ } ++ ++ ret = 0; ++out: ++ talloc_free(fake); ++ return ret; ++} ++ + static int mod_session_init (void *instance, eap_handler_t *handler) + { + pwd_session_t *session; + eap_pwd_t *inst = (eap_pwd_t *)instance; + VALUE_PAIR *vp; + pwd_id_packet_t *packet; ++ REQUEST *request; + + if (!inst || !handler) { + ERROR("rlm_eap_pwd: Initiate, NULL data provided"); + return 0; + } + ++ request = handler->request; ++ if (!request) { ++ ERROR("rlm_eap_pwd: NULL request provided"); ++ return 0; ++ } ++ + /* + * make sure the server's been configured properly + */ +@@ -232,6 +583,30 @@ static int mod_session_init (void *instance, eap_handler_t *handler) + session->out_pos = 0; + handler->opaque = session; + ++ session->token = fr_rand(); ++ if (inst->prep < 0) { ++ RDEBUG2("using outer identity %s to configure EAP-PWD", handler->identity); ++ session->peer_id_len = strlen(handler->identity); ++ if (session->peer_id_len >= sizeof(session->peer_id)) { ++ RDEBUG("identity is malformed"); ++ return 0; ++ } ++ memcpy(session->peer_id, handler->identity, session->peer_id_len); ++ session->peer_id[session->peer_id_len] = '\0'; ++ ++ /* ++ * make fake request to get the password for the usable ID ++ * in order to identity prep ++ */ ++ if (fetch_and_process_password(session, handler->request, inst) < 0) { ++ RDEBUG("failed to find password for %s to do pwd authentication (init)", ++ session->peer_id); ++ return 0; ++ } ++ } else { ++ session->prep = inst->prep; ++ } ++ + /* + * construct an EAP-pwd-ID/Request + */ +@@ -244,9 +619,8 @@ static int mod_session_init (void *instance, eap_handler_t *handler) + packet->group_num = htons(session->group_num); + packet->random_function = EAP_PWD_DEF_RAND_FUN; + packet->prf = EAP_PWD_DEF_PRF; +- session->token = fr_rand(); + memcpy(packet->token, (char *)&session->token, 4); +- packet->prep = EAP_PWD_PREP_NONE; ++ packet->prep = session->prep; + memcpy(packet->identity, inst->server_id, session->out_len - sizeof(pwd_id_packet_t) ); + + handler->stage = PROCESS; +@@ -259,16 +633,16 @@ static int mod_process(void *arg, eap_handler_t *handler) + pwd_session_t *session; + pwd_hdr *hdr; + pwd_id_packet_t *packet; ++ REQUEST *request; + eap_packet_t *response; +- REQUEST *request, *fake; +- VALUE_PAIR *pw, *vp; + EAP_DS *eap_ds; +- size_t in_len; ++ size_t in_len, peer_id_len; + int ret = 0; + eap_pwd_t *inst = (eap_pwd_t *)arg; + uint16_t offset; + uint8_t exch, *in, *ptr, msk[MSK_EMSK_LEN], emsk[MSK_EMSK_LEN]; + uint8_t peer_confirm[SHA256_DIGEST_LENGTH]; ++ char *peer_id; + + if (((eap_ds = handler->eap_ds) == NULL) || !inst) return 0; + +@@ -389,7 +763,7 @@ static int mod_process(void *arg, eap_handler_t *handler) + + if ((packet->prf != EAP_PWD_DEF_PRF) || + (packet->random_function != EAP_PWD_DEF_RAND_FUN) || +- (packet->prep != EAP_PWD_PREP_NONE) || ++ (packet->prep != session->prep) || + (CRYPTO_memcmp(packet->token, &session->token, 4)) || + (packet->group_num != ntohs(session->group_num))) { + RDEBUG2("pwd id response is invalid"); +@@ -405,89 +779,46 @@ static int mod_process(void *arg, eap_handler_t *handler) + ptr += sizeof(uint8_t); + *ptr = EAP_PWD_DEF_PRF; + +- session->peer_id_len = in_len - sizeof(pwd_id_packet_t); +- if (session->peer_id_len >= sizeof(session->peer_id)) { ++ peer_id_len = in_len - sizeof(pwd_id_packet_t); ++ if (peer_id_len >= sizeof(session->peer_id)) { + RDEBUG2("pwd id response is malformed"); + return 0; + } ++ peer_id = packet->identity; + +- memcpy(session->peer_id, packet->identity, session->peer_id_len); +- session->peer_id[session->peer_id_len] = '\0'; +- +- /* +- * make fake request to get the password for the usable ID +- */ +- if ((fake = request_alloc_fake(handler->request)) == NULL) { +- RDEBUG("pwd unable to create fake request!"); +- return 0; +- } +- fake->username = fr_pair_afrom_num(fake->packet, PW_USER_NAME, 0); +- if (!fake->username) { +- RDEBUG("Failed creating pair for peer id"); +- talloc_free(fake); +- return 0; +- } +- fr_pair_value_bstrncpy(fake->username, session->peer_id, session->peer_id_len); +- fr_pair_add(&fake->packet->vps, fake->username); +- +- if ((vp = fr_pair_find_by_num(request->config, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) { +- fake->server = vp->vp_strvalue; +- } else if (inst->virtual_server) { +- fake->server = inst->virtual_server; +- } /* else fake->server == request->server */ +- +- RDEBUG("Sending tunneled request"); +- rdebug_pair_list(L_DBG_LVL_1, request, fake->packet->vps, NULL); +- +- if (fake->server) { +- RDEBUG("server %s {", fake->server); +- } else { +- RDEBUG("server {"); +- } ++ if (inst->prep >= 0) { ++ /* ++ * make fake request to get the password for the usable ID ++ */ + +- /* +- * Call authorization recursively, which will +- * get the password. +- */ +- RINDENT(); +- process_authorize(0, fake); +- REXDENT(); ++ session->peer_id_len = peer_id_len; ++ memcpy(session->peer_id, peer_id, peer_id_len); ++ session->peer_id[peer_id_len] = '\0'; + +- /* +- * Note that we don't do *anything* with the reply +- * attributes. +- */ +- if (fake->server) { +- RDEBUG("} # server %s", fake->server); ++ if (fetch_and_process_password(session, request, inst) < 0) { ++ RDEBUG2("failed to find password for %s to do pwd authentication", ++ session->peer_id); ++ return 0; ++ } + } else { +- RDEBUG("}"); ++ /* verify inner identity == outer identity */ ++ if (session->peer_id_len != peer_id_len || ++ memcmp(session->peer_id, peer_id, peer_id_len) != 0) { ++ char buf[sizeof(session->peer_id)]; ++ memcpy(buf, peer_id, peer_id_len); ++ buf[peer_id_len] = '\0'; ++ ++ RDEBUG2("inner identity(peer_id) %s does not match outer identity %s", ++ buf, session->peer_id); ++ return 0; ++ } ++ RDEBUG2("inner identity matched for %s", session->peer_id); + } + +- RDEBUG("Got tunneled reply code %d", fake->reply->code); +- rdebug_pair_list(L_DBG_LVL_1, request, fake->reply->vps, NULL); +- +- if ((pw = fr_pair_find_by_num(fake->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) == NULL) { +- DEBUG2("failed to find password for %s to do pwd authentication", +- session->peer_id); +- talloc_free(fake); +- return 0; +- } +- +- if (compute_password_element(session, session->group_num, +- pw->data.strvalue, strlen(pw->data.strvalue), +- inst->server_id, strlen(inst->server_id), +- session->peer_id, strlen(session->peer_id), +- &session->token)) { +- DEBUG2("failed to obtain password element"); +- talloc_free(fake); +- return 0; +- } +- TALLOC_FREE(fake); +- + /* + * compute our scalar and element + */ +- if (compute_scalar_element(session, session->bnctx)) { ++ if (compute_scalar_element(request, session, session->bnctx)) { + DEBUG2("failed to compute server's scalar and element"); + return 0; + } +@@ -498,7 +829,7 @@ static int mod_process(void *arg, eap_handler_t *handler) + /* + * element is a point, get both coordinates: x and y + */ +- if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->my_element, x, y, ++ if (!EC_POINT_get_affine_coordinates(session->group, session->my_element, x, y, + session->bnctx)) { + DEBUG2("server point assignment failed"); + BN_clear_free(x); +@@ -510,12 +841,23 @@ static int mod_process(void *arg, eap_handler_t *handler) + * construct request + */ + session->out_len = BN_num_bytes(session->order) + (2 * BN_num_bytes(session->prime)); ++ if (session->salt_present) ++ session->out_len += 1 + session->salt_len; ++ + if ((session->out = talloc_array(session, uint8_t, session->out_len)) == NULL) { + return 0; + } + memset(session->out, 0, session->out_len); + + ptr = session->out; ++ if (session->salt_present) { ++ *ptr = session->salt_len; ++ ptr++; ++ ++ memcpy(ptr, session->salt, session->salt_len); ++ ptr += session->salt_len; ++ } ++ + offset = BN_num_bytes(session->prime) - BN_num_bytes(x); + BN_bn2bin(x, ptr + offset); + BN_clear_free(x); +@@ -534,7 +876,7 @@ static int mod_process(void *arg, eap_handler_t *handler) + } + break; + +- case PWD_STATE_COMMIT: ++ case PWD_STATE_COMMIT: + if (EAP_PWD_GET_EXCHANGE(hdr) != EAP_PWD_EXCH_COMMIT) { + RDEBUG2("pwd exchange is incorrect: not commit!"); + return 0; +@@ -543,7 +885,7 @@ static int mod_process(void *arg, eap_handler_t *handler) + /* + * process the peer's commit and generate the shared key, k + */ +- if (process_peer_commit(session, in, in_len, session->bnctx)) { ++ if (process_peer_commit(request, session, in, in_len, session->bnctx)) { + RDEBUG2("failed to process peer's commit"); + return 0; + } +@@ -551,7 +893,7 @@ static int mod_process(void *arg, eap_handler_t *handler) + /* + * compute our confirm blob + */ +- if (compute_server_confirm(session, session->my_confirm, session->bnctx)) { ++ if (compute_server_confirm(request, session, session->my_confirm, session->bnctx)) { + ERROR("rlm_eap_pwd: failed to compute confirm!"); + return 0; + } +@@ -582,7 +924,7 @@ static int mod_process(void *arg, eap_handler_t *handler) + RDEBUG2("pwd exchange is incorrect: not commit!"); + return 0; + } +- if (compute_peer_confirm(session, peer_confirm, session->bnctx)) { ++ if (compute_peer_confirm(request, session, peer_confirm, session->bnctx)) { + RDEBUG2("pwd exchange cannot compute peer's confirm"); + return 0; + } +@@ -590,7 +932,7 @@ static int mod_process(void *arg, eap_handler_t *handler) + RDEBUG2("pwd exchange fails: peer confirm is incorrect!"); + return 0; + } +- if (compute_keys(session, peer_confirm, msk, emsk)) { ++ if (compute_keys(request, session, peer_confirm, msk, emsk)) { + RDEBUG2("pwd exchange cannot generate (E)MSK!"); + return 0; + } +diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h +index 2264566bb6..966646c360 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h ++++ b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h +@@ -44,6 +44,8 @@ typedef struct _eap_pwd_t { + uint32_t fragment_size; + char const *server_id; + char const *virtual_server; ++ int32_t prep; ++ int32_t unhex; + } eap_pwd_t; + + #endif /* _RLM_EAP_PWD_H */ +diff --git a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c +index 4d41cd42e6..d327c575fc 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c ++++ b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c +@@ -43,6 +43,7 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + static CONF_PARSER module_config[] = { + { "tls", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_eap_tls_t, tls_conf_name), NULL }, + { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_eap_tls_t, virtual_server), NULL }, ++ { "configurable_client_cert", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_eap_tls_t, configurable_client_cert), NULL }, + CONF_PARSER_TERMINATOR + }; + +@@ -71,6 +72,19 @@ static int mod_instantiate(CONF_SECTION *cs, void **instance) + return -1; + } + ++#ifdef TLS1_3_VERSION ++ if ((inst->tls_conf->max_version == TLS1_3_VERSION) || ++ (inst->tls_conf->min_version == TLS1_3_VERSION)) { ++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ WARN("!! Most supplicants do not support EAP-TLS with TLS 1.3"); ++ WARN("!! Please set tls_max_version = \"1.2\""); ++ WARN("!! FreeRADIUS only supports TLS 1.3 for special builds of wpa_supplicant and Windows"); ++ WARN("!! This limitation is likely to change in late 2021."); ++ WARN("!! If you are using this version of FreeRADIUS after 2021, you will probably need to upgrade"); ++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ } ++#endif ++ + return 0; + } + +@@ -84,25 +98,38 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) + tls_session_t *ssn; + rlm_eap_tls_t *inst; + REQUEST *request = handler->request; ++ bool require_client_cert = true; + + inst = type_arg; + + handler->tls = true; + + /* +- * EAP-TLS always requires a client certificate. ++ * Respect EAP-TLS-Require-Client-Cert, but only if ++ * enabled in the module configuration. ++ * ++ * We can't change behavior of existing systems, so this ++ * change has to be enabled via a new configuration ++ * option. + */ +- ssn = eaptls_session(handler, inst->tls_conf, true); ++ if (inst->configurable_client_cert) { ++ VALUE_PAIR *vp; ++ ++ vp = fr_pair_find_by_num(handler->request->config, PW_EAP_TLS_REQUIRE_CLIENT_CERT, 0, TAG_ANY); ++ if (vp && !vp->vp_integer) require_client_cert = false; ++ } ++ ++ /* ++ * EAP-TLS always requires a client certificate, and ++ * allows for TLS 1.3 if permitted. ++ */ ++ ssn = eaptls_session(handler, inst->tls_conf, require_client_cert, true); + if (!ssn) { + return 0; + } + + handler->opaque = ((void *)ssn); +- +- /* +- * Set up type-specific information. +- */ +- ssn->prf_label = "client EAP encryption"; ++ ssn->quick_session_tickets = true; /* send as soon as we've seen the client cert */ + + /* + * TLS session initialization is over. Now handle TLS +@@ -112,7 +139,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) + if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { + REDEBUG("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); + } else { +- RDEBUG2("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); ++ RDEBUG3("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); + } + if (status == 0) return 0; + +@@ -141,7 +168,7 @@ static int CC_HINT(nonnull) mod_process(void *type_arg, eap_handler_t *handler) + if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { + REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); + } else { +- RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); ++ RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); + } + + +@@ -195,6 +222,13 @@ static int CC_HINT(nonnull) mod_process(void *type_arg, eap_handler_t *handler) + /* success */ + } + ++ /* ++ * Set the label to a fixed string. For TLS 1.3, ++ * the label is the same for all TLS-based EAP ++ * methods. ++ */ ++ tls_session->label = "client EAP encryption"; ++ + /* + * Success: Automatically return MPPE keys. + */ +diff --git a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h +index cebcb92f57..550cbbdce3 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h ++++ b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h +@@ -42,6 +42,11 @@ typedef struct rlm_eap_tls_t { + * Virtual server for checking certificates + */ + char const *virtual_server; ++ ++ /* ++ * Configurable EAP-TLS-Require-Client-Cert ++ */ ++ bool configurable_client_cert; + } rlm_eap_tls_t; + + #endif /* _RLM_EAP_TLS_H */ +diff --git a/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c b/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c +index a3c575bceb..3c77aa4877 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c ++++ b/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c +@@ -130,6 +130,25 @@ static int mod_instantiate(CONF_SECTION *cs, void **instance) + return -1; + } + ++#ifdef TLS1_3_VERSION ++ if ((inst->tls_conf->min_version == TLS1_3_VERSION) && !inst->tls_conf->tls13_enable_magic) { ++ ERROR("There are no standards for using TLS 1.3 with TTLS."); ++ ERROR("You MUST enable TLS 1.2 for TTLS to work."); ++ return -1; ++ } ++ ++ if ((inst->tls_conf->max_version == TLS1_3_VERSION) || ++ (inst->tls_conf->min_version == TLS1_3_VERSION)) { ++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ WARN("!! There is no standard for using TTLS with TLS 1.3"); ++ WARN("!! Please set tls_max_version = \"1.2\""); ++ WARN("!! FreeRADIUS only supports TLS 1.3 for special builds of wpa_supplicant and Windows"); ++ WARN("!! This limitation is likely to change in late 2021."); ++ WARN("!! If you are using this version of FreeRADIUS after 2021, you will probably need to upgrade"); ++ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); ++ } ++#endif ++ + return 0; + } + +@@ -181,7 +200,11 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) + client_cert = inst->req_client_cert; + } + +- ssn = eaptls_session(handler, inst->tls_conf, client_cert); ++ /* ++ * Don't allow TLS 1.3 for us, even if it's allowed ++ * elsewhere. ++ */ ++ ssn = eaptls_session(handler, inst->tls_conf, client_cert, inst->tls_conf->tls13_enable_magic); + if (!ssn) { + return 0; + } +@@ -189,9 +212,13 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) + handler->opaque = ((void *)ssn); + + /* +- * Set up type-specific information. ++ * Set the label to a fixed string. For TLS 1.3, the ++ * label is the same for all TLS-based EAP methods. If ++ * the client is using TLS 1.3, then eaptls_success() ++ * will over-ride this label with the correct label for ++ * TLS 1.3. + */ +- ssn->prf_label = "ttls keying material"; ++ ssn->label = "ttls keying material"; + + /* + * TLS session initialization is over. Now handle TLS +@@ -201,7 +228,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) + if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { + REDEBUG("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); + } else { +- RDEBUG2("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); ++ RDEBUG3("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); + } + if (status == 0) return 0; + +@@ -238,7 +265,7 @@ static int mod_process(void *arg, eap_handler_t *handler) + if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { + REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); + } else { +- RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); ++ RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); + } + + /* +@@ -342,8 +369,7 @@ static int mod_process(void *arg, eap_handler_t *handler) + * Success: Automatically return MPPE keys. + */ + case PW_CODE_ACCESS_ACCEPT: +- ret = eaptls_success(handler, 0); +- goto done; ++ goto do_keys; + + /* + * No response packet, MUST be proxying it. +diff --git a/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c b/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c +index 627d722ed7..cbe423951a 100644 +--- a/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c ++++ b/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c +@@ -145,9 +145,10 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, + size_t size; + size_t data_left = data_len; + VALUE_PAIR *first = NULL; +- VALUE_PAIR *vp; ++ VALUE_PAIR *vp = NULL; + RADIUS_PACKET *packet = fake->packet; /* FIXME: api issues */ + vp_cursor_t out; ++ DICT_ATTR const *da; + + fr_cursor_init(&out, &first); + +@@ -258,13 +259,13 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, + if (decoded < 0) { + REDEBUG2("diameter2vp failed decoding attr: %s", + fr_strerror()); +- goto do_octets; ++ goto raw; + } + + if ((size_t) decoded != size + 2) { + REDEBUG2("diameter2vp failed to entirely decode VSA"); + fr_pair_list_free(&vp); +- goto do_octets; ++ goto raw; + } + + fr_cursor_merge(&out, vp); +@@ -275,8 +276,10 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, + /* + * Create it. If this fails, it's because we're OOM. + */ +- do_octets: +- vp = fr_pair_afrom_num(packet, attr, vendor); ++ da = dict_attrbyvalue(attr, vendor); ++ if (!da) goto raw; ++ ++ vp = fr_pair_afrom_da(packet, da); + if (!vp) { + RDEBUG2("Failure in creating VP"); + fr_pair_list_free(&first); +@@ -293,8 +296,6 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, + case PW_TYPE_INTEGER: + case PW_TYPE_DATE: + if (size != vp->vp_length) { +- DICT_ATTR const *da; +- + /* + * Bad format. Create a "raw" + * attribute. +@@ -405,7 +406,7 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, + */ + if (((vp->da->vendor == 0) && (vp->da->attr == PW_CHAP_CHALLENGE)) || + ((vp->da->vendor == VENDORPEC_MICROSOFT) && (vp->da->attr == PW_MSCHAP_CHALLENGE))) { +- uint8_t challenge[16]; ++ uint8_t challenge[17]; + + if ((vp->vp_length < 8) || + (vp->vp_length > 16)) { +@@ -415,8 +416,11 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, + return NULL; + } + +- eapttls_gen_challenge(ssl, challenge, +- sizeof(challenge)); ++ /* ++ * TLSv1.3 exports a different key depending on the length ++ * requested so ask for *exactly* what the spec requires ++ */ ++ eapttls_gen_challenge(ssl, challenge, vp->vp_length + 1); + + if (memcmp(challenge, vp->vp_octets, + vp->vp_length) != 0) { +@@ -644,6 +648,7 @@ static rlm_rcode_t CC_HINT(nonnull) process_reply(eap_handler_t *handler, tls_se + */ + switch (reply->code) { + case PW_CODE_ACCESS_ACCEPT: ++ tls_session->authentication_success = true; + RDEBUG("Got tunneled Access-Accept"); + + rcode = RLM_MODULE_OK; +diff --git a/src/modules/rlm_exec/rlm_exec.c b/src/modules/rlm_exec/rlm_exec.c +index e7dbfa685e..f7e23628e6 100644 +--- a/src/modules/rlm_exec/rlm_exec.c ++++ b/src/modules/rlm_exec/rlm_exec.c +@@ -353,7 +353,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_exec_dispatch(void *instance, REQUEST *r + * If we're not waiting, then there are no output pairs. + */ + if (inst->output) { +- fr_pair_list_move(ctx, output_pairs, &answer); ++ fr_pair_list_move(ctx, output_pairs, &answer, T_OP_ADD); + } + fr_pair_list_free(&answer); + +@@ -399,7 +399,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + /* + * Always add the value-pairs to the reply. + */ +- fr_pair_list_move(request->reply, &request->reply->vps, &tmp); ++ fr_pair_list_move(request->reply, &request->reply->vps, &tmp, T_OP_ADD); + fr_pair_list_free(&tmp); + + finish: +diff --git a/src/modules/rlm_expr/rlm_expr.c b/src/modules/rlm_expr/rlm_expr.c +index c449d7776b..f835800376 100644 +--- a/src/modules/rlm_expr/rlm_expr.c ++++ b/src/modules/rlm_expr/rlm_expr.c +@@ -970,6 +970,49 @@ static ssize_t md5_xlat(UNUSED void *instance, REQUEST *request, + return strlen(out); + } + ++/** Calculate the MD4 hash of a string or attribute. ++ * ++ * Example: "%{md4:foo}" == "0ac6700c491d70fb8650940b1ca1e4b2" ++ */ ++static ssize_t md4_xlat(UNUSED void *instance, REQUEST *request, ++ char const *fmt, char *out, size_t outlen) ++{ ++ uint8_t digest[16]; ++ ssize_t i, len, inlen; ++ uint8_t const *p; ++ FR_MD4_CTX ctx; ++ ++ /* ++ * We need room for at least one octet of output. ++ */ ++ if (outlen < 3) { ++ *out = '\0'; ++ return 0; ++ } ++ ++ inlen = xlat_fmt_to_ref(&p, request, fmt); ++ if (inlen < 0) { ++ return -1; ++ } ++ ++ fr_md4_init(&ctx); ++ fr_md4_update(&ctx, p, inlen); ++ fr_md4_final(digest, &ctx); ++ ++ /* ++ * Each digest octet takes two hex digits, plus one for ++ * the terminating NUL. ++ */ ++ len = (outlen / 2) - 1; ++ if (len > 16) len = 16; ++ ++ for (i = 0; i < len; i++) { ++ snprintf(out + i * 2, 3, "%02x", digest[i]); ++ } ++ ++ return strlen(out); ++} ++ + /** Calculate the SHA1 hash of a string or attribute. + * + * Example: "%{sha1:foo}" == "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33" +@@ -1570,6 +1613,76 @@ static ssize_t next_time_xlat(UNUSED void *instance, REQUEST *request, + return snprintf(out, outlen, "%" PRIu64, (uint64_t)(mktime(local) - now)); + } + ++/** Calculate number of seconds until the previous n hour(s), day(s), week(s), year(s). ++ * ++ * For example, if it were 16:18 %{lasttime:1h} would expand to -2520. ++ */ ++static ssize_t last_time_xlat(UNUSED void *instance, REQUEST *request, ++ char const *fmt, char *out, size_t outlen) ++{ ++ long num; ++ ++ char const *p; ++ char *q; ++ time_t now; ++ struct tm *local, local_buff; ++ ++ now = time(NULL); ++ local = localtime_r(&now, &local_buff); ++ ++ p = fmt; ++ ++ num = strtoul(p, &q, 10); ++ if (!q || *q == '\0') { ++ REDEBUG("nexttime: must be followed by period specifier (h|d|w|m|y)"); ++ return -1; ++ } ++ ++ if (p == q) { ++ num = 1; ++ } else { ++ p += q - p; ++ } ++ ++ local->tm_sec = 0; ++ local->tm_min = 0; ++ ++ switch (*p) { ++ case 'h': ++ local->tm_hour -= num; ++ break; ++ ++ case 'd': ++ local->tm_hour = 0; ++ local->tm_mday -= num; ++ break; ++ ++ case 'w': ++ local->tm_hour = 0; ++ local->tm_mday -= (7 - local->tm_wday) + (7 * (num-1)); ++ break; ++ ++ case 'm': ++ local->tm_hour = 0; ++ local->tm_mday = 1; ++ local->tm_mon -= num; ++ break; ++ ++ case 'y': ++ local->tm_hour = 0; ++ local->tm_mday = 1; ++ local->tm_mon = 0; ++ local->tm_year -= num; ++ break; ++ ++ default: ++ REDEBUG("lasttime: Invalid period specifier '%c', must be h|d|w|m|y", *p); ++ return -1; ++ } ++ ++ return snprintf(out, outlen, "%" PRIu64, (uint64_t)(now - mktime(local))); ++} ++ + + /* + * Parse the 3 arguments to lpad / rpad. +@@ -1761,6 +1874,7 @@ static int mod_bootstrap(CONF_SECTION *conf, void *instance) + xlat_register("unescape", unescape_xlat, NULL, inst); + xlat_register("tolower", tolower_xlat, NULL, inst); + xlat_register("toupper", toupper_xlat, NULL, inst); ++ xlat_register("md4", md4_xlat, NULL, inst); + xlat_register("md5", md5_xlat, NULL, inst); + xlat_register("sha1", sha1_xlat, NULL, inst); + #ifdef HAVE_OPENSSL_EVP_H +@@ -1778,6 +1892,7 @@ static int mod_bootstrap(CONF_SECTION *conf, void *instance) + xlat_register("explode", explode_xlat, NULL, inst); + + xlat_register("nexttime", next_time_xlat, NULL, inst); ++ xlat_register("lasttime", last_time_xlat, NULL, inst); + xlat_register("lpad", lpad_xlat, NULL, inst); + xlat_register("rpad", rpad_xlat, NULL, inst); + +diff --git a/src/modules/rlm_files/rlm_files.c b/src/modules/rlm_files/rlm_files.c +index c825a9230b..9e77cd7ff1 100644 +--- a/src/modules/rlm_files/rlm_files.c ++++ b/src/modules/rlm_files/rlm_files.c +@@ -422,7 +422,7 @@ static rlm_rcode_t file_common(rlm_files_t *inst, REQUEST *request, char const * + /* ctx may be reply or proxy */ + reply_tmp = fr_pair_list_copy(reply_packet, pl->reply); + radius_pairmove(request, &reply_packet->vps, reply_tmp, true); +- fr_pair_list_move(request, &request->config, &check_tmp); ++ fr_pair_list_move(request, &request->config, &check_tmp, T_OP_ADD); + fr_pair_list_free(&check_tmp); + + /* +diff --git a/src/modules/rlm_ldap/ldap.h b/src/modules/rlm_ldap/ldap.h +index 7dc874d35e..bcec9af9dc 100644 +--- a/src/modules/rlm_ldap/ldap.h ++++ b/src/modules/rlm_ldap/ldap.h +@@ -20,6 +20,7 @@ + * always need to support that. + */ + #define LDAP_DEPRECATED 1 ++USES_APPLE_DEPRECATED_API /* Apple wants us to use OpenDirectory Framework, we don't want that */ + #include + #include + #include "config.h" +diff --git a/src/modules/rlm_mschap/rlm_mschap.c b/src/modules/rlm_mschap/rlm_mschap.c +index 83c84a63e2..4e4447a213 100644 +--- a/src/modules/rlm_mschap/rlm_mschap.c ++++ b/src/modules/rlm_mschap/rlm_mschap.c +@@ -322,6 +322,56 @@ static ssize_t mschap_xlat(void *instance, REQUEST *request, + data = response->vp_octets + 2; + data_len = 24; + ++ /* ++ * Pull the domain name out of the User-Name, if it exists. ++ * ++ * This is the full domain name, not just the name after host/ ++ */ ++ } else if (strncasecmp(fmt, "Domain-Name", 11) == 0) { ++ char *p; ++ ++ user_name = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY); ++ if (!user_name) { ++ REDEBUG("No User-Name was found in the request"); ++ return -1; ++ } ++ ++ /* ++ * First check to see if this is a host/ style User-Name ++ * (a la Kerberos host principal) ++ */ ++ if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) { ++ /* ++ * If we're getting a User-Name formatted in this way, ++ * it's likely due to PEAP. The Windows Domain will be ++ * the first domain component following the hostname, ++ * or the machine name itself if only a hostname is supplied ++ */ ++ p = strchr(user_name->vp_strvalue, '.'); ++ if (!p) { ++ RDEBUG2("setting NT-Domain to same as machine name"); ++ strlcpy(out, user_name->vp_strvalue + 5, outlen); ++ } else { ++ p++; /* skip the period */ ++ strlcpy(out, p, outlen); ++ } ++ } else { ++ p = strchr(user_name->vp_strvalue, '\\'); ++ if (!p) { ++ REDEBUG("No NT-Domain was found in the User-Name"); ++ return -1; ++ } ++ ++ /* ++ * Hack. This is simpler than the alternatives. ++ */ ++ *p = '\0'; ++ strlcpy(out, user_name->vp_strvalue, outlen); ++ *p = '\\'; ++ } ++ ++ return strlen(out); ++ + /* + * Pull the NT-Domain out of the User-Name, if it exists. + */ +@@ -616,7 +666,7 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance) + return -1; + } + #else +- cf_log_err_cs(conf, "'winbind' auth not enabled at compiled time"); ++ cf_log_err_cs(conf, "'winbind' is not enabled in this build."); + return -1; + #endif + } +@@ -942,7 +992,6 @@ ntlm_auth_err: + ssize_t result_len; + char result[253]; + uint8_t nt_pass_decrypted[516], old_nt_hash_expected[NT_DIGEST_LENGTH]; +- RC4_KEY key; + + if (!nt_password) { + RDEBUG("Local MS-CHAPv2 password change requires NT-Password attribute"); +@@ -951,11 +1000,45 @@ ntlm_auth_err: + RDEBUG("Doing MS-CHAPv2 password change locally"); + } + +- /* +- * Decrypt the blob +- */ +- RC4_set_key(&key, nt_password->vp_length, nt_password->vp_octets); +- RC4(&key, 516, new_nt_password, nt_pass_decrypted); ++#if OPENSSL_VERSION_NUMBER >= 0x30000000L ++ { ++ EVP_CIPHER_CTX *ctx; ++ int ntlen = sizeof(nt_pass_decrypted); ++ ++ ctx = EVP_CIPHER_CTX_new(); ++ if (!ctx) { ++ REDEBUG("Failed getting RC4 from OpenSSL"); ++ return -1; ++ } ++ ++ if (!EVP_CIPHER_CTX_set_key_length(ctx, nt_password->vp_length)) { ++ REDEBUG("Failed setting key length"); ++ return -1; ++ } ++ ++ if (!EVP_EncryptInit_ex(ctx, EVP_rc4(), NULL, nt_password->vp_octets, NULL)) { ++ REDEBUG("Failed setting key value"); ++ return -1; ++ } ++ ++ if (!EVP_EncryptUpdate(ctx, nt_pass_decrypted, &ntlen, new_nt_password, ntlen)) { ++ REDEBUG("Failed getting output"); ++ return -1; ++ } ++ ++ EVP_CIPHER_CTX_free(ctx); ++ } ++#else ++ { ++ RC4_KEY key; ++ ++ /* ++ * Decrypt the blob ++ */ ++ RC4_set_key(&key, nt_password->vp_length, nt_password->vp_octets); ++ RC4(&key, 516, new_nt_password, nt_pass_decrypted); ++ } ++#endif + + /* + * pwblock is +diff --git a/src/modules/rlm_otp/otp_mppe.c b/src/modules/rlm_otp/otp_mppe.c +index 399689abf3..932e44abe9 100644 +--- a/src/modules/rlm_otp/otp_mppe.c ++++ b/src/modules/rlm_otp/otp_mppe.c +@@ -39,10 +39,16 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + + #include + ++#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER < 0x30000000L) ++#define UNUSED3 ++#else ++#define UNUSED3 UNUSED ++#endif ++ + /* + * Add MPPE attributes to a request, if required. + */ +-void otp_mppe(REQUEST *request, otp_pwe_t pwe, rlm_otp_t const *opt, char const *passcode) ++void otp_mppe(REQUEST *request, otp_pwe_t pwe, UNUSED3 rlm_otp_t const *opt, UNUSED3 char const *passcode) + { + VALUE_PAIR *cvp, *rvp; + +@@ -58,6 +64,7 @@ void otp_mppe(REQUEST *request, otp_pwe_t pwe, rlm_otp_t const *opt, char const + case PWE_CHAP: + return; + ++#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER < 0x30000000L) + case PWE_MSCHAP: + /* First, set some related attributes. */ + pair_make_reply("MS-MPPE-Encryption-Policy", otp_mppe_policy[opt->mschap_mppe_policy], T_OP_EQ); +@@ -368,7 +375,12 @@ void otp_mppe(REQUEST *request, otp_pwe_t pwe, rlm_otp_t const *opt, char const + + break; /* PWE_MSCHAP2 */ + } /* PWE_MSCHAP2 */ +- ++#else ++ case PWE_MSCHAP: ++ case PWE_MSCHAP2: ++ REDEBUG("MS-CHAP is unsupported for OpenSSL 3."); ++ break; ++#endif + } /* switch (pwe) */ + + return; +diff --git a/src/modules/rlm_otp/otp_pwe.c b/src/modules/rlm_otp/otp_pwe.c +index 56a4dbc71b..99a6d07769 100644 +--- a/src/modules/rlm_otp/otp_pwe.c ++++ b/src/modules/rlm_otp/otp_pwe.c +@@ -28,19 +28,11 @@ + RCSID("$Id: 56a4dbc71b0117cb5eb788367e1fad7be9c8419a $") + + /* avoid inclusion of these FR headers which conflict w/ OpenSSL */ +-#define _FR_MD4_H +-#define _FR_SHA1_H + #include + #include + + #include "extern.h" + +-USES_APPLE_DEPRECATED_API +-#include +-#include +-#include +-#include +- + #include + + /* Attribute IDs for supported password encodings. */ +diff --git a/src/modules/rlm_otp/otp_radstate.c b/src/modules/rlm_otp/otp_radstate.c +index 66fd8b4987..6e53430c2f 100644 +--- a/src/modules/rlm_otp/otp_radstate.c ++++ b/src/modules/rlm_otp/otp_radstate.c +@@ -22,17 +22,13 @@ + RCSID("$Id: 66fd8b4987c0353e5679da23efc31595aa7017db $") + USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + +-/* avoid inclusion of these FR headers which conflict w/ OpenSSL */ +-#define _FR_MD4_H +-#define _FR_SHA1_H +- + #include "extern.h" + + #include + +-#include /* des_cblock */ + #include + #include ++#include + + /* + * Generate the State attribute, suitable for passing to fr_pair_make(). +@@ -113,6 +109,7 @@ size_t otp_gen_state(char state[OTP_MAX_RADSTATE_LEN], + HMAC_CTX *hmac_ctx; + uint8_t hmac[MD5_DIGEST_LENGTH]; + char *p; ++ unsigned int len = sizeof(hmac); + + /* + * Generate the hmac. We already have a dependency on openssl for +@@ -125,7 +122,7 @@ size_t otp_gen_state(char state[OTP_MAX_RADSTATE_LEN], + HMAC_Update(hmac_ctx, (uint8_t const *) challenge, clen); + HMAC_Update(hmac_ctx, (uint8_t *) &flags, 4); + HMAC_Update(hmac_ctx, (uint8_t *) &when, 4); +- HMAC_Final(hmac_ctx, hmac, NULL); ++ HMAC_Final(hmac_ctx, hmac, &len); + HMAC_CTX_free(hmac_ctx); + + /* +diff --git a/src/modules/rlm_rest/rest.c b/src/modules/rlm_rest/rest.c +index 8b85df0662..fcb3fd11fc 100644 +--- a/src/modules/rlm_rest/rest.c ++++ b/src/modules/rlm_rest/rest.c +@@ -115,6 +115,15 @@ do {\ + }\ + } while (0) + ++/* ++ * that macro is originally declared in include/curl/curlver.h ++ * We have to use this as curl uses lots of enums ++ */ ++#ifndef CURL_AT_LEAST_VERSION ++# define CURL_VERSION_BITS(x, y, z) ((x) << 16 | (y) << 8 | (z)) ++# define CURL_AT_LEAST_VERSION(x, y, z) (LIBCURL_VERSION_NUM >= CURL_VERSION_BITS(x, y, z)) ++#endif ++ + const unsigned long http_curl_auth[HTTP_AUTH_NUM_ENTRIES] = { + 0, // HTTP_AUTH_UNKNOWN + 0, // HTTP_AUTH_NONE +@@ -221,6 +230,40 @@ const FR_NAME_NUMBER http_content_type_table[] = { + { NULL , -1 } + }; + ++/** Conversion table for "HTTP" protocol version to use. ++ * ++ * Used by rlm_rest_t for specify the http client version. ++ * ++ * Values we expect to use in curl_easy_setopt() ++ * ++ * @see fr_str2int ++ * @see fr_int2str ++ */ ++const FR_NAME_NUMBER http_negotiation_table[] = { ++ ++ { "1.0", CURL_HTTP_VERSION_1_0 }, //!< Enforce HTTP 1.0 requests. ++ { "1.1", CURL_HTTP_VERSION_1_1 }, //!< Enforce HTTP 1.1 requests. ++/* ++ * These are all enum values ++ */ ++#if CURL_AT_LEAST_VERSION(7,49,0) ++ { "2.0", CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE }, //!< Enforce HTTP 2.0 requests. ++#endif ++#if CURL_AT_LEAST_VERSION(7,33,0) ++ { "2.0+auto", CURL_HTTP_VERSION_2_0 }, //!< Attempt HTTP 2 requests. libcurl will fall back ++ ///< to HTTP 1.1 if HTTP 2 can't be negotiated with the ++ ///< server. (Added in 7.33.0) ++#endif ++#if CURL_AT_LEAST_VERSION(7,47,0) ++ { "2.0+tls", CURL_HTTP_VERSION_2TLS }, //!< Attempt HTTP 2 over TLS (HTTPS) only. ++ ///< libcurl will fall back to HTTP 1.1 if HTTP 2 ++ ///< can't be negotiated with the HTTPS server. ++ ///< For clear text HTTP servers, libcurl will use 1.1. ++#endif ++ { "default", CURL_HTTP_VERSION_NONE } //!< We don't care about what version the library uses. ++ ///< libcurl will use whatever it thinks fit. ++}; ++ + /* + * Encoder specific structures. + * @todo split encoders/decoders into submodules. +@@ -603,19 +646,30 @@ static size_t rest_encode_post(void *out, size_t size, size_t nmemb, void *userd + } + + /* +- * there are more attributes, insert a separator ++ * there are no more attributes, stop + */ +- if (fr_cursor_next(&ctx->cursor)) { +- if (freespace < 1) goto no_space; +- *p++ = '&'; +- freespace--; ++ if (!fr_cursor_next_peek(&ctx->cursor)) { ++ ctx->state = READ_STATE_END; ++ break; + } + ++ if (freespace < 1) goto no_space; ++ *p++ = '&'; ++ freespace--; ++ /* ++ * Only advance once we have a separator ++ * really we should have an additional ++ * state for encoding the separator, ++ * but, we don't, and v3.0.x is stable ++ * so let's do the easiest fix with the ++ * lowest risk. ++ */ ++ fr_cursor_next(&ctx->cursor); ++ + /* + * We wrote one full attribute value pair, record progress. + */ + encoded = p; +- + ctx->state = READ_STATE_ATTR_BEGIN; + } + +@@ -747,7 +801,13 @@ static size_t rest_encode_json(void *out, size_t size, size_t nmemb, void *userd + + type = fr_int2str(dict_attr_types, vp->da->type, ""); + +- len = snprintf(p, freespace + 1, "\"%s\":{\"type\":\"%s\",\"value\":[", vp->da->name, type); ++ if (ctx->section->attr_num) { ++ len = snprintf(p, freespace + 1, "\"%s\":{\"attr_num\":%d,\"type\":\"%s\",\"value\":[", ++ vp->da->name, vp->da->attr, type); ++ } else { ++ len = snprintf(p, freespace + 1, "\"%s\":{\"type\":\"%s\",\"value\":[", vp->da->name, type); ++ } ++ + if (len >= freespace) goto no_space; + p += len; + freespace -= len; +@@ -783,7 +843,7 @@ static size_t rest_encode_json(void *out, size_t size, size_t nmemb, void *userd + * write that out. + */ + attr_space = fr_cursor_next_peek(&ctx->cursor) ? freespace - 1 : freespace; +- len = vp_prints_value_json(p, attr_space + 1, vp); ++ len = vp_prints_value_json(p, attr_space + 1, vp, ctx->section->raw_value); + if (is_truncated(len, attr_space + 1)) goto no_space; + + /* +@@ -1575,8 +1635,9 @@ static size_t rest_response_header(void *in, size_t size, size_t nmemb, void *us + * HTTP/ [ ]\r\n + * + * "HTTP/1.1 " (8) + "100 " (4) + "\r\n" (2) = 14 ++ * "HTTP/2 " (8) + "100 " (4) + "\r\n" (2) = 12 + */ +- if (s < 14) { ++ if (s < 12) { + REDEBUG("Malformed HTTP header: Status line too short"); + goto malformed; + } +@@ -1614,8 +1675,10 @@ static size_t rest_response_header(void *in, size_t size, size_t nmemb, void *us + p++; + s--; + +- /* Char after reason code must be a space, or \r */ +- if (!((p[3] == ' ') || (p[3] == '\r'))) goto malformed; ++ /* ++ * "xxx( |\r)" status code and terminator. ++ */ ++ if (!isdigit(p[0]) || !isdigit(p[1]) || !isdigit(p[2]) || !((p[3] == ' ') || (p[3] == '\r'))) goto malformed; + + ctx->code = atoi(p); + +@@ -1996,11 +2059,24 @@ int rest_request_config(rlm_rest_t *instance, rlm_rest_section_t *section, + SET_OPTION(CURLOPT_NOSIGNAL, 1); + SET_OPTION(CURLOPT_USERAGENT, "FreeRADIUS " RADIUSD_VERSION_STRING); + ++ /* ++ * As described in https://curl.se/libcurl/c/CURLOPT_HTTP_VERSION.html, ++ * The libcurl decides which http version should be ++ * used by default accoring by library version. ++ */ ++ if (instance->http_negotiation != CURL_HTTP_VERSION_NONE) { ++ RDEBUG3("Set HTTP negotiation for %s", instance->http_negotiation_str); ++ SET_OPTION(CURLOPT_HTTP_VERSION, instance->http_negotiation); ++ } ++ + content_type = fr_int2str(http_content_type_table, type, section->body_str); + snprintf(buffer, sizeof(buffer), "Content-Type: %s", content_type); + ctx->headers = curl_slist_append(ctx->headers, buffer); + if (!ctx->headers) goto error_header; + ++ // Pass configuration to the request ++ ctx->request.section = section; ++ + SET_OPTION(CURLOPT_CONNECTTIMEOUT_MS, instance->connect_timeout); + SET_OPTION(CURLOPT_TIMEOUT_MS, section->timeout); + +@@ -2322,6 +2398,7 @@ int rest_request_perform(UNUSED rlm_rest_t *instance, UNUSED rlm_rest_section_t + rlm_rest_handle_t *randle = handle; + CURL *candle = randle->handle; + CURLcode ret; ++ VALUE_PAIR *vp; + + ret = curl_easy_perform(candle); + if (ret != CURLE_OK) { +@@ -2330,6 +2407,14 @@ int rest_request_perform(UNUSED rlm_rest_t *instance, UNUSED rlm_rest_section_t + return -1; + } + ++ /* ++ * Save the HTTP return status code. ++ */ ++ vp = pair_make_reply("REST-HTTP-Status-Code", NULL, T_OP_SET); ++ vp->vp_integer = rest_get_handle_code(handle); ++ ++ RDEBUG2("Adding reply:REST-HTTP-Status-Code = \"%d\"", vp->vp_integer); ++ + return 0; + } + +diff --git a/src/modules/rlm_rest/rest.h b/src/modules/rlm_rest/rest.h +index 9e278d16c4..4c41cf46c2 100644 +--- a/src/modules/rlm_rest/rest.h ++++ b/src/modules/rlm_rest/rest.h +@@ -31,6 +31,10 @@ RCSIDH(other_h, "$Id$") + #define CURL_NO_OLDIES 1 + #include + ++#ifdef HAVE_WDOCUMENTATION ++DIAG_OFF(documentation) ++#endif ++ + #ifdef HAVE_JSON + # if defined(HAVE_JSONMC_JSON_H) + # include +@@ -39,6 +43,10 @@ RCSIDH(other_h, "$Id$") + # endif + #endif + ++#ifdef HAVE_WDOCUMENTATION ++DIAG_ON(documentation) ++#endif ++ + #define REST_URI_MAX_LEN 2048 + #define REST_BODY_MAX_LEN 8192 + #define REST_BODY_INIT 1024 +@@ -102,6 +110,8 @@ extern const FR_NAME_NUMBER http_body_type_table[]; + + extern const FR_NAME_NUMBER http_content_type_table[]; + ++extern const FR_NAME_NUMBER http_negotiation_table[]; ++ + /* + * Structure for section configuration + */ +@@ -115,6 +125,9 @@ typedef struct rlm_rest_section_t { + char const *body_str; //!< The string version of the encoding/content type. + http_body_type_t body; //!< What encoding type should be used. + ++ bool attr_num; //!< If true, the the attribute number is supplied for each attribute. ++ bool raw_value; //!< If true, enumerated attributes are provided as a numeric value ++ + char const *force_to_str; //!< Force decoding with this decoder. + http_body_type_t force_to; //!< Override the Content-Type header in the response + //!< to force decoding as a particular type. +@@ -154,6 +167,9 @@ typedef struct rlm_rest_t { + struct timeval connect_timeout_tv; //!< Connection timeout timeval. + long connect_timeout; //!< Connection timeout ms. + ++ char const *http_negotiation_str; //!< The string version of the http_negotiation ++ long http_negotiation; //!< The HTTP protocol version to use ++ + fr_connection_pool_t *pool; //!< Pointer to the connection pool. + + rlm_rest_section_t authorize; //!< Configuration specific to authorisation. +@@ -205,6 +221,8 @@ typedef struct rlm_rest_request_t { + + size_t chunk; //!< Chunk size + ++ rlm_rest_section_t *section; //!< Configuration data ++ + void *encoder; //!< Encoder specific data. + } rlm_rest_request_t; + +diff --git a/src/modules/rlm_rest/rlm_rest.c b/src/modules/rlm_rest/rlm_rest.c +index 6fa1345614..1337ae5425 100644 +--- a/src/modules/rlm_rest/rlm_rest.c ++++ b/src/modules/rlm_rest/rlm_rest.c +@@ -60,6 +60,8 @@ static const CONF_PARSER section_config[] = { + { "uri", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_rest_section_t, uri), "" }, + { "method", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, method_str), "GET" }, + { "body", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, body_str), "none" }, ++ { "attr_num", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_rest_section_t, attr_num), "no" }, ++ { "raw_value", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_rest_section_t, raw_value), "no" }, + { "data", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_rest_section_t, data), NULL }, + { "force_to", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, force_to_str), NULL }, + +@@ -81,6 +83,8 @@ static const CONF_PARSER section_config[] = { + static const CONF_PARSER module_config[] = { + { "connect_uri", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_t, connect_uri), NULL }, + { "connect_timeout", FR_CONF_OFFSET(PW_TYPE_TIMEVAL, rlm_rest_t, connect_timeout_tv), "4.0" }, ++ { "http_negotiation", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_t, http_negotiation_str), "default" }, ++ + CONF_PARSER_TERMINATOR + }; + +@@ -223,6 +227,8 @@ static ssize_t rest_xlat(void *instance, REQUEST *request, + .name = "xlat", + .method = HTTP_METHOD_GET, + .body = HTTP_BODY_NONE, ++ .attr_num = false, ++ .raw_value = false, + .body_str = "application/x-www-form-urlencoded", + .require_auth = false, + .force_to = HTTP_BODY_PLAIN +@@ -926,6 +932,12 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance) + return -1; + } + ++ inst->http_negotiation = fr_str2int(http_negotiation_table, inst->http_negotiation_str, -1); ++ if (inst->http_negotiation == -1) { ++ cf_log_err_cs(conf, "Unsupported HTTP version \"%s\".", inst->http_negotiation_str); ++ return -1; ++ } ++ + /* + * Initialise REST libraries. + */ +diff --git a/src/modules/rlm_wimax/milenage.c b/src/modules/rlm_wimax/milenage.c +new file mode 100644 +index 0000000000..e14086e78c +--- /dev/null ++++ b/src/modules/rlm_wimax/milenage.c +@@ -0,0 +1,642 @@ ++/** ++ * @file src/modules/rlm_wimax/milenage.c ++ * @brief 3GPP AKA - Milenage algorithm (3GPP TS 35.205, .206, .207, .208) ++ * ++ * This file implements an example authentication algorithm defined for 3GPP ++ * AKA. This can be used to implement a simple HLR/AuC into hlr_auc_gw to allow ++ * EAP-AKA to be tested properly with real USIM cards. ++ * ++ * This implementations assumes that the r1..r5 and c1..c5 constants defined in ++ * TS 35.206 are used, i.e., r1=64, r2=0, r3=32, r4=64, r5=96, c1=00..00, ++ * c2=00..01, c3=00..02, c4=00..04, c5=00..08. The block cipher is assumed to ++ * be AES (Rijndael). ++ * ++ * This software may be distributed under the terms of the BSD license. ++ * See README for more details. ++ * ++ * @copyright 2017 The FreeRADIUS server project ++ * @copyright 2006-2007 (j@w1.fi) ++ */ ++#include ++#include ++ ++#include ++#include ++#include ++#include "milenage.h" ++ ++#define MILENAGE_MAC_A_SIZE 8 ++#define MILENAGE_MAC_S_SIZE 8 ++ ++static inline int aes_128_encrypt_block(EVP_CIPHER_CTX *evp_ctx, ++ uint8_t const key[16], uint8_t const in[16], uint8_t out[16]) ++{ ++ size_t len; ++ ++ if (unlikely(EVP_EncryptInit_ex(evp_ctx, EVP_aes_128_ecb(), NULL, key, NULL) != 1)) { ++ fr_strerror_printf("Failed initialising AES-128-ECB context"); ++ return -1; ++ } ++ ++ /* ++ * By default OpenSSL will try and pad out a 16 byte ++ * plaintext to 32 bytes so that it's detectable that ++ * there was padding. ++ * ++ * In this case we know the length of the plaintext ++ * we're trying to recover, so we explicitly tell ++ * OpenSSL not to pad here, and not to expected padding ++ * when decrypting. ++ */ ++ EVP_CIPHER_CTX_set_padding(evp_ctx, 0); ++ if (unlikely(EVP_EncryptUpdate(evp_ctx, out, (int *)&len, in, 16) != 1) || ++ unlikely(EVP_EncryptFinal_ex(evp_ctx, out + len, (int *)&len) != 1)) { ++ fr_strerror_printf("Failed encrypting data"); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++/** milenage_f1 - Milenage f1 and f1* algorithms ++ * ++ * @param[in] opc 128-bit value derived from OP and K. ++ * @param[in] k 128-bit subscriber key. ++ * @param[in] rand 128-bit random challenge. ++ * @param[in] sqn 48-bit sequence number. ++ * @param[in] amf 16-bit authentication management field. ++ * @param[out] mac_a Buffer for MAC-A = 64-bit network authentication code, or NULL ++ * @param[out] mac_s Buffer for MAC-S = 64-bit resync authentication code, or NULL ++ * @return ++ * - 0 on success. ++ * - -1 on failure. ++ */ ++static int milenage_f1(uint8_t mac_a[MILENAGE_MAC_A_SIZE], ++ uint8_t mac_s[MILENAGE_MAC_S_SIZE], ++ uint8_t const opc[MILENAGE_OPC_SIZE], ++ uint8_t const k[MILENAGE_KI_SIZE], ++ uint8_t const rand[MILENAGE_RAND_SIZE], ++ uint8_t const sqn[MILENAGE_SQN_SIZE], ++ uint8_t const amf[MILENAGE_AMF_SIZE]) ++{ ++ uint8_t tmp1[16], tmp2[16], tmp3[16]; ++ int i; ++ EVP_CIPHER_CTX *evp_ctx; ++ ++ /* tmp1 = TEMP = E_K(RAND XOR OP_C) */ ++ for (i = 0; i < 16; i++) tmp1[i] = rand[i] ^ opc[i]; ++ ++ evp_ctx = EVP_CIPHER_CTX_new(); ++ if (!evp_ctx) { ++ //tls_strerror_printf("Failed allocating EVP context"); ++ return -1; ++ } ++ ++ if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp1) < 0) { ++ error: ++ EVP_CIPHER_CTX_free(evp_ctx); ++ return -1; ++ } ++ ++ /* tmp2 = IN1 = SQN || AMF || SQN || AMF */ ++ memcpy(tmp2, sqn, 6); ++ memcpy(tmp2 + 6, amf, 2); ++ memcpy(tmp2 + 8, tmp2, 8); ++ ++ /* OUT1 = E_K(TEMP XOR rot(IN1 XOR OP_C, r1) XOR c1) XOR OP_C */ ++ ++ /* ++ * rotate (tmp2 XOR OP_C) by r1 (= 0x40 = 8 bytes) ++ */ ++ for (i = 0; i < 16; i++) tmp3[(i + 8) % 16] = tmp2[i] ^ opc[i]; ++ ++ /* ++ * XOR with TEMP = E_K(RAND XOR OP_C) ++ */ ++ for (i = 0; i < 16; i++) tmp3[i] ^= tmp1[i]; ++ /* XOR with c1 (= ..00, i.e., NOP) */ ++ ++ /* ++ * f1 || f1* = E_K(tmp3) XOR OP_c ++ */ ++ if (aes_128_encrypt_block(evp_ctx, k, tmp3, tmp1) < 0) goto error; /* Reuses existing key */ ++ ++ for (i = 0; i < 16; i++) tmp1[i] ^= opc[i]; ++ ++ if (mac_a) memcpy(mac_a, tmp1, 8); /* f1 */ ++ if (mac_s) memcpy(mac_s, tmp1 + 8, 8); /* f1* */ ++ ++ EVP_CIPHER_CTX_free(evp_ctx); ++ ++ return 0; ++} ++ ++/** milenage_f2345 - Milenage f2, f3, f4, f5, f5* algorithms ++ * ++ * @param[out] res Buffer for RES = 64-bit signed response (f2), or NULL ++ * @param[out] ck Buffer for CK = 128-bit confidentiality key (f3), or NULL ++ * @param[out] ik Buffer for IK = 128-bit integrity key (f4), or NULL ++ * @param[out] ak Buffer for AK = 48-bit anonymity key (f5), or NULL ++ * @param[out] ak_resync Buffer for AK = 48-bit anonymity key (f5*), or NULL ++ * @param[in] opc 128-bit value derived from OP and K. ++ * @param[in] k 128-bit subscriber key ++ * @param[in] rand 128-bit random challenge ++ * @return ++ * - 0 on success. ++ * - -1 on failure. ++ */ ++static int milenage_f2345(uint8_t res[MILENAGE_RES_SIZE], ++ uint8_t ik[MILENAGE_IK_SIZE], ++ uint8_t ck[MILENAGE_CK_SIZE], ++ uint8_t ak[MILENAGE_AK_SIZE], ++ uint8_t ak_resync[MILENAGE_AK_SIZE], ++ uint8_t const opc[MILENAGE_OPC_SIZE], ++ uint8_t const k[MILENAGE_KI_SIZE], ++ uint8_t const rand[MILENAGE_RAND_SIZE]) ++{ ++ uint8_t tmp1[16], tmp2[16], tmp3[16]; ++ int i; ++ EVP_CIPHER_CTX *evp_ctx; ++ ++ /* tmp2 = TEMP = E_K(RAND XOR OP_C) */ ++ for (i = 0; i < 16; i++) tmp1[i] = rand[i] ^ opc[i]; ++ ++ evp_ctx = EVP_CIPHER_CTX_new(); ++ if (!evp_ctx) { ++ fr_strerror_printf("Failed allocating EVP context"); ++ return -1; ++ } ++ ++ if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp2) < 0) { ++ error: ++ EVP_CIPHER_CTX_free(evp_ctx); ++ return -1; ++ } ++ ++ /* OUT2 = E_K(rot(TEMP XOR OP_C, r2) XOR c2) XOR OP_C */ ++ /* OUT3 = E_K(rot(TEMP XOR OP_C, r3) XOR c3) XOR OP_C */ ++ /* OUT4 = E_K(rot(TEMP XOR OP_C, r4) XOR c4) XOR OP_C */ ++ /* OUT5 = E_K(rot(TEMP XOR OP_C, r5) XOR c5) XOR OP_C */ ++ ++ /* f2 and f5 */ ++ /* rotate by r2 (= 0, i.e., NOP) */ ++ for (i = 0; i < 16; i++) tmp1[i] = tmp2[i] ^ opc[i]; ++ tmp1[15] ^= 1; /* XOR c2 (= ..01) */ ++ /* f5 || f2 = E_K(tmp1) XOR OP_c */ ++ ++ if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp3) < 0) goto error; ++ ++ for (i = 0; i < 16; i++) tmp3[i] ^= opc[i]; ++ if (res) memcpy(res, tmp3 + 8, 8); /* f2 */ ++ if (ak) memcpy(ak, tmp3, 6); /* f5 */ ++ ++ /* f3 */ ++ if (ck) { ++ /* rotate by r3 = 0x20 = 4 bytes */ ++ for (i = 0; i < 16; i++) tmp1[(i + 12) % 16] = tmp2[i] ^ opc[i]; ++ tmp1[15] ^= 2; /* XOR c3 (= ..02) */ ++ ++ if (aes_128_encrypt_block(evp_ctx, k, tmp1, ck) < 0) goto error; ++ ++ for (i = 0; i < 16; i++) ck[i] ^= opc[i]; ++ } ++ ++ /* f4 */ ++ if (ik) { ++ /* rotate by r4 = 0x40 = 8 bytes */ ++ for (i = 0; i < 16; i++) tmp1[(i + 8) % 16] = tmp2[i] ^ opc[i]; ++ tmp1[15] ^= 4; /* XOR c4 (= ..04) */ ++ ++ if (aes_128_encrypt_block(evp_ctx, k, tmp1, ik) < 0) goto error; ++ ++ for (i = 0; i < 16; i++) ik[i] ^= opc[i]; ++ } ++ ++ /* f5* */ ++ if (ak_resync) { ++ /* rotate by r5 = 0x60 = 12 bytes */ ++ for (i = 0; i < 16; i++) tmp1[(i + 4) % 16] = tmp2[i] ^ opc[i]; ++ tmp1[15] ^= 8; /* XOR c5 (= ..08) */ ++ ++ if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp1) < 0) goto error; ++ ++ for (i = 0; i < 6; i++) ak_resync[i] = tmp1[i] ^ opc[i]; ++ } ++ EVP_CIPHER_CTX_free(evp_ctx); ++ ++ return 0; ++} ++ ++/** Derive OPc from OP and Ki ++ * ++ * @param[out] opc The derived Operator Code used as an input to other Milenage ++ * functions. ++ * @param[in] op Operator Code. ++ * @param[in] ki Subscriber key. ++ * @return ++ * - 0 on success. ++ * - -1 on failure. ++ */ ++int milenage_opc_generate(uint8_t opc[MILENAGE_OPC_SIZE], ++ uint8_t const op[MILENAGE_OP_SIZE], ++ uint8_t const ki[MILENAGE_KI_SIZE]) ++{ ++ int ret; ++ uint8_t tmp[MILENAGE_OPC_SIZE]; ++ EVP_CIPHER_CTX *evp_ctx; ++ size_t i; ++ ++ evp_ctx = EVP_CIPHER_CTX_new(); ++ if (!evp_ctx) { ++ fr_strerror_printf("Failed allocating EVP context"); ++ return -1; ++ } ++ ret = aes_128_encrypt_block(evp_ctx, ki, op, tmp); ++ EVP_CIPHER_CTX_free(evp_ctx); ++ if (ret < 0) return ret; ++ ++ for (i = 0; i < sizeof(tmp); i++) opc[i] = op[i] ^ tmp[i]; ++ ++ return 0; ++} ++ ++/** Generate AKA AUTN, IK, CK, RES ++ * ++ * @param[out] autn Buffer for AUTN = 128-bit authentication token. ++ * @param[out] ik Buffer for IK = 128-bit integrity key (f4), or NULL. ++ * @param[out] ck Buffer for CK = 128-bit confidentiality key (f3), or NULL. ++ * @param[out] ak Buffer for AK = 48-bit anonymity key (f5), or NULL ++ * @param[out] res Buffer for RES = 64-bit signed response (f2), or NULL. ++ * @param[in] opc 128-bit operator variant algorithm configuration field (encr.). ++ * @param[in] amf 16-bit authentication management field. ++ * @param[in] ki 128-bit subscriber key. ++ * @param[in] sqn 48-bit sequence number (host byte order). ++ * @param[in] rand 128-bit random challenge. ++ * @return ++ * - 0 on success. ++ * - -1 on failure. ++ */ ++int milenage_umts_generate(uint8_t autn[MILENAGE_AUTN_SIZE], ++ uint8_t ik[MILENAGE_IK_SIZE], ++ uint8_t ck[MILENAGE_CK_SIZE], ++ uint8_t ak[MILENAGE_AK_SIZE], ++ uint8_t res[MILENAGE_RES_SIZE], ++ uint8_t const opc[MILENAGE_OPC_SIZE], ++ uint8_t const amf[MILENAGE_AMF_SIZE], ++ uint8_t const ki[MILENAGE_KI_SIZE], ++ uint64_t sqn, ++ uint8_t const rand[MILENAGE_RAND_SIZE]) ++{ ++ uint8_t mac_a[8], ak_buff[MILENAGE_AK_SIZE]; ++ uint8_t sqn_buff[MILENAGE_SQN_SIZE]; ++ uint8_t *p = autn; ++ size_t i; ++ ++ if ((milenage_f1(mac_a, NULL, opc, ki, rand, ++ uint48_to_buff(sqn_buff, sqn), amf) < 0) || ++ (milenage_f2345(res, ik, ck, ak_buff, NULL, opc, ki, rand) < 0)) return -1; ++ ++ /* ++ * AUTN = (SQN ^ AK) || AMF || MAC_A ++ */ ++ for (i = 0; i < sizeof(sqn_buff); i++) *p++ = sqn_buff[i] ^ ak_buff[i]; ++ memcpy(p, amf, MILENAGE_AMF_SIZE); ++ p += MILENAGE_AMF_SIZE; ++ memcpy(p, mac_a, sizeof(mac_a)); ++ ++ /* ++ * Output the anonymity key if required ++ */ ++ if (ak) memcpy(ak, ak_buff, sizeof(ak_buff)); ++ ++ return 0; ++} ++ ++/** Milenage AUTS validation ++ * ++ * @param[out] sqn SQN = 48-bit sequence number (host byte order). ++ * @param[in] opc 128-bit operator variant algorithm configuration field (encr.). ++ * @param[in] ki 128-bit subscriber key. ++ * @param[in] rand 128-bit random challenge. ++ * @param[in] auts 112-bit authentication token from client. ++ * @return ++ * - 0 on success with sqn filled. ++ * - -1 on failure. ++ */ ++int milenage_auts(uint64_t *sqn, ++ uint8_t const opc[MILENAGE_OPC_SIZE], ++ uint8_t const ki[MILENAGE_KI_SIZE], ++ uint8_t const rand[MILENAGE_RAND_SIZE], ++ uint8_t const auts[MILENAGE_AUTS_SIZE]) ++{ ++ uint8_t amf[MILENAGE_AMF_SIZE] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */ ++ uint8_t ak[MILENAGE_AK_SIZE], mac_s[MILENAGE_MAC_S_SIZE]; ++ uint8_t sqn_buff[MILENAGE_SQN_SIZE]; ++ size_t i; ++ ++ if (milenage_f2345(NULL, NULL, NULL, NULL, ak, opc, ki, rand)) return -1; ++ for (i = 0; i < sizeof(sqn_buff); i++) sqn_buff[i] = auts[i] ^ ak[i]; ++ ++ if (milenage_f1(NULL, mac_s, opc, ki, rand, sqn_buff, amf) || CRYPTO_memcmp(mac_s, auts + 6, 8) != 0) return -1; ++ ++ *sqn = uint48_from_buff(sqn_buff); ++ ++ return 0; ++} ++ ++/** Generate GSM-Milenage (3GPP TS 55.205) authentication triplet from a quintuplet ++ * ++ * @param[out] sres Buffer for SRES = 32-bit SRES. ++ * @param[out] kc 64-bit Kc. ++ * @param[in] ik 128-bit integrity. ++ * @param[in] ck Confidentiality key. ++ * @param[in] res 64-bit signed response. ++ */ ++void milenage_gsm_from_umts(uint8_t sres[MILENAGE_SRES_SIZE], ++ uint8_t kc[MILENAGE_KC_SIZE], ++ uint8_t const ik[MILENAGE_IK_SIZE], ++ uint8_t const ck[MILENAGE_CK_SIZE], ++ uint8_t const res[MILENAGE_RES_SIZE]) ++{ ++ int i; ++ ++ for (i = 0; i < 8; i++) kc[i] = ck[i] ^ ck[i + 8] ^ ik[i] ^ ik[i + 8]; ++ ++#ifdef GSM_MILENAGE_ALT_SRES ++ memcpy(sres, res, 4); ++#else /* GSM_MILENAGE_ALT_SRES */ ++ for (i = 0; i < 4; i++) sres[i] = res[i] ^ res[i + 4]; ++#endif /* GSM_MILENAGE_ALT_SRES */ ++} ++ ++/** Generate GSM-Milenage (3GPP TS 55.205) authentication triplet ++ * ++ * @param[out] sres Buffer for SRES = 32-bit SRES. ++ * @param[out] kc 64-bit Kc. ++ * @param[in] opc 128-bit operator variant algorithm configuration field (encr.). ++ * @param[in] ki 128-bit subscriber key. ++ * @param[in] rand 128-bit random challenge. ++ * @return ++ * - 0 on success. ++ * - -1 on failure. ++ */ ++int milenage_gsm_generate(uint8_t sres[MILENAGE_SRES_SIZE], ++ uint8_t kc[MILENAGE_KC_SIZE], ++ uint8_t const opc[MILENAGE_OPC_SIZE], ++ uint8_t const ki[MILENAGE_KI_SIZE], ++ uint8_t const rand[MILENAGE_RAND_SIZE]) ++{ ++ uint8_t res[MILENAGE_RES_SIZE], ck[MILENAGE_CK_SIZE], ik[MILENAGE_IK_SIZE]; ++ ++ if (milenage_f2345(res, ik, ck, NULL, NULL, opc, ki, rand)) return -1; ++ ++ milenage_gsm_from_umts(sres, kc, ik, ck, res); ++ ++ return 0; ++} ++ ++/** Milenage check ++ * ++ * @param[out] ik Buffer for IK = 128-bit integrity key (f4), or NULL. ++ * @param[out] ck Buffer for CK = 128-bit confidentiality key (f3), or NULL. ++ * @param[out] res Buffer for RES = 64-bit signed response (f2), or NULL. ++ * @param[in] auts 112-bit buffer for AUTS. ++ * @param[in] opc 128-bit operator variant algorithm configuration field (encr.). ++ * @param[in] ki 128-bit subscriber key. ++ * @param[in] sqn 48-bit sequence number. ++ * @param[in] rand 128-bit random challenge. ++ * @param[in] autn 128-bit authentication token. ++ * @return ++ * - 0 on success. ++ * - -1 on failure. ++ * - -2 on synchronization failure ++ */ ++int milenage_check(uint8_t ik[MILENAGE_IK_SIZE], ++ uint8_t ck[MILENAGE_CK_SIZE], ++ uint8_t res[MILENAGE_RES_SIZE], ++ uint8_t auts[MILENAGE_AUTS_SIZE], ++ uint8_t const opc[MILENAGE_OPC_SIZE], ++ uint8_t const ki[MILENAGE_KI_SIZE], ++ uint64_t sqn, ++ uint8_t const rand[MILENAGE_RAND_SIZE], ++ uint8_t const autn[MILENAGE_AUTN_SIZE]) ++{ ++ ++ uint8_t mac_a[MILENAGE_MAC_A_SIZE], ak[MILENAGE_AK_SIZE], rx_sqn[MILENAGE_SQN_SIZE]; ++ uint8_t sqn_buff[MILENAGE_SQN_SIZE]; ++ const uint8_t *amf; ++ size_t i; ++ ++ uint48_to_buff(sqn_buff, sqn); ++ ++ //FR_PROTO_HEX_DUMP(autn, MILENAGE_AUTN_SIZE, "AUTN"); ++ //FR_PROTO_HEX_DUMP(rand, MILENAGE_RAND_SIZE, "RAND"); ++ ++ if (milenage_f2345(res, ck, ik, ak, NULL, opc, ki, rand)) return -1; ++ ++ //FR_PROTO_HEX_DUMP(res, MILENAGE_RES_SIZE, "RES"); ++ //FR_PROTO_HEX_DUMP(ck, MILENAGE_CK_SIZE, "CK"); ++ //FR_PROTO_HEX_DUMP(ik, MILENAGE_IK_SIZE, "IK"); ++ //FR_PROTO_HEX_DUMP(ak, MILENAGE_AK_SIZE, "AK"); ++ ++ /* AUTN = (SQN ^ AK) || AMF || MAC */ ++ for (i = 0; i < 6; i++) rx_sqn[i] = autn[i] ^ ak[i]; ++ //FR_PROTO_HEX_DUMP(rx_sqn, MILENAGE_SQN_SIZE, "SQN"); ++ ++ if (CRYPTO_memcmp(rx_sqn, sqn_buff, sizeof(rx_sqn)) <= 0) { ++ uint8_t auts_amf[MILENAGE_AMF_SIZE] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */ ++ ++ if (milenage_f2345(NULL, NULL, NULL, NULL, ak, opc, ki, rand)) return -1; ++ ++ //FR_PROTO_HEX_DUMP(ak, sizeof(ak), "AK*"); ++ for (i = 0; i < 6; i++) auts[i] = sqn_buff[i] ^ ak[i]; ++ ++ if (milenage_f1(NULL, auts + 6, opc, ki, rand, sqn_buff, auts_amf) < 0) return -1; ++ //FR_PROTO_HEX_DUMP(auts, 14, "AUTS"); ++ return -2; ++ } ++ ++ amf = autn + 6; ++ //FR_PROTO_HEX_DUMP(amf, MILENAGE_AMF_SIZE, "AMF"); ++ if (milenage_f1(mac_a, NULL, opc, ki, rand, rx_sqn, amf) < 0) return -1; ++ ++ //FR_PROTO_HEX_DUMP(mac_a, MILENAGE_MAC_A_SIZE, "MAC_A"); ++ ++ if (CRYPTO_memcmp(mac_a, autn + 8, 8) != 0) { ++ //FR_PROTO_HEX_DUMP(autn + 8, 8, "Received MAC_A"); ++ fr_strerror_printf("MAC mismatch"); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++#ifdef TESTING_MILENAGE ++/* ++ * cc milenage.c -g3 -Wall -DHAVE_DLFCN_H -DTESTING_MILENAGE -DWITH_TLS -I../../../../ -I../../../ -I ../base/ -I /usr/local/opt/openssl/include/ -include ../include/build.h -L /usr/local/opt/openssl/lib/ -l ssl -l crypto -l talloc -L ../../../../../build/lib/local/.libs/ -lfreeradius-server -lfreeradius-tls -lfreeradius-util -o test_milenage && ./test_milenage ++ */ ++#include ++ ++void test_set_1(void) ++{ ++ /* ++ * Inputs ++ */ ++ uint8_t ki[] = { 0x46, 0x5b, 0x5c, 0xe8, 0xb1, 0x99, 0xb4, 0x9f, ++ 0xaa, 0x5f, 0x0a, 0x2e, 0xe2, 0x38, 0xa6, 0xbc }; ++ uint8_t rand[] = { 0x23, 0x55, 0x3c, 0xbe, 0x96, 0x37, 0xa8, 0x9d, ++ 0x21, 0x8a, 0xe6, 0x4d, 0xae, 0x47, 0xbf, 0x35 }; ++ uint8_t sqn[] = { 0xff, 0x9b, 0xb4, 0xd0, 0xb6, 0x07 }; ++ uint8_t amf[] = { 0xb9, 0xb9 }; ++ uint8_t op[] = { 0xcd, 0xc2, 0x02, 0xd5, 0x12, 0x3e, 0x20, 0xf6, ++ 0x2b, 0x6d, 0x67, 0x6a, 0xc7, 0x2c, 0xb3, 0x18 }; ++ uint8_t opc[] = { 0xcd, 0x63, 0xcb, 0x71, 0x95, 0x4a, 0x9f, 0x4e, ++ 0x48, 0xa5, 0x99, 0x4e, 0x37, 0xa0, 0x2b, 0xaf }; ++ ++ /* ++ * Outputs ++ */ ++ uint8_t opc_out[MILENAGE_OPC_SIZE]; ++ uint8_t mac_a_out[MILENAGE_MAC_A_SIZE]; ++ uint8_t mac_s_out[MILENAGE_MAC_S_SIZE]; ++ uint8_t res_out[MILENAGE_RES_SIZE]; ++ uint8_t ck_out[MILENAGE_CK_SIZE]; ++ uint8_t ik_out[MILENAGE_IK_SIZE]; ++ uint8_t ak_out[MILENAGE_AK_SIZE]; ++ uint8_t ak_resync_out[MILENAGE_AK_SIZE]; ++ ++ /* function 1 */ ++ uint8_t mac_a[] = { 0x4a, 0x9f, 0xfa, 0xc3, 0x54, 0xdf, 0xaf, 0xb3 }; ++ /* function 1* */ ++ uint8_t mac_s[] = { 0x01, 0xcf, 0xaf, 0x9e, 0xc4, 0xe8, 0x71, 0xe9 }; ++ /* function 2 */ ++ uint8_t res[] = { 0xa5, 0x42, 0x11, 0xd5, 0xe3, 0xba, 0x50, 0xbf }; ++ /* function 3 */ ++ uint8_t ck[] = { 0xb4, 0x0b, 0xa9, 0xa3, 0xc5, 0x8b, 0x2a, 0x05, ++ 0xbb, 0xf0, 0xd9, 0x87, 0xb2, 0x1b, 0xf8, 0xcb }; ++ /* function 4 */ ++ uint8_t ik[] = { 0xf7, 0x69, 0xbc, 0xd7, 0x51, 0x04, 0x46, 0x04, ++ 0x12, 0x76, 0x72, 0x71, 0x1c, 0x6d, 0x34, 0x41 }; ++ /* function 5 */ ++ uint8_t ak[] = { 0xaa, 0x68, 0x9c, 0x64, 0x83, 0x70 }; ++ /* function 5* */ ++ uint8_t ak_resync[] = { 0x45, 0x1e, 0x8b, 0xec, 0xa4, 0x3b }; ++ ++ int ret = 0; ++ ++/* ++ fr_debug_lvl = 4; ++*/ ++ ret = milenage_opc_generate(opc_out, op, ki); ++ TEST_CHECK(ret == 0); ++ ++ //FR_PROTO_HEX_DUMP(opc_out, sizeof(opc_out), "opc"); ++ ++ TEST_CHECK(memcmp(opc_out, opc, sizeof(opc_out)) == 0); ++ ++ if ((milenage_f1(mac_a_out, mac_s_out, opc, ki, rand, sqn, amf) < 0) || ++ (milenage_f2345(res_out, ik_out, ck_out, ak_out, ak_resync_out, opc, ki, rand) < 0)) ret = -1; ++ ++ //FR_PROTO_HEX_DUMP(mac_a, sizeof(mac_a_out), "mac_a"); ++ //FR_PROTO_HEX_DUMP(mac_s, sizeof(mac_s_out), "mac_s"); ++ //FR_PROTO_HEX_DUMP(ik_out, sizeof(ik_out), "ik"); ++ //FR_PROTO_HEX_DUMP(ck_out, sizeof(ck_out), "ck"); ++ //FR_PROTO_HEX_DUMP(res_out, sizeof(res_out), "res"); ++ //FR_PROTO_HEX_DUMP(ak_out, sizeof(ak_out), "ak"); ++ //FR_PROTO_HEX_DUMP(ak_resync_out, sizeof(ak_resync_out), "ak_resync"); ++ ++ TEST_CHECK(ret == 0); ++ TEST_CHECK(memcmp(mac_a_out, mac_a, sizeof(mac_a_out)) == 0); ++ TEST_CHECK(memcmp(mac_s_out, mac_s, sizeof(mac_s_out)) == 0); ++ TEST_CHECK(memcmp(res_out, res, sizeof(res_out)) == 0); ++ TEST_CHECK(memcmp(ck_out, ck, sizeof(ck_out)) == 0); ++ TEST_CHECK(memcmp(ik_out, ik, sizeof(ik_out)) == 0); ++ TEST_CHECK(memcmp(ak_out, ak, sizeof(ak_out)) == 0); ++ TEST_CHECK(memcmp(ak_resync, ak_resync, sizeof(ak_resync_out)) == 0); ++} ++ ++void test_set_19(void) ++{ ++ /* ++ * Inputs ++ */ ++ uint8_t ki[] = { 0x51, 0x22, 0x25, 0x02, 0x14, 0xc3, 0x3e, 0x72, ++ 0x3a, 0x5d, 0xd5, 0x23, 0xfc, 0x14, 0x5f, 0xc0 }; ++ uint8_t rand[] = { 0x81, 0xe9, 0x2b, 0x6c, 0x0e, 0xe0, 0xe1, 0x2e, ++ 0xbc, 0xeb, 0xa8, 0xd9, 0x2a, 0x99, 0xdf, 0xa5 }; ++ uint8_t sqn[] = { 0x16, 0xf3, 0xb3, 0xf7, 0x0f, 0xc2 }; ++ uint8_t amf[] = { 0xc3, 0xab }; ++ uint8_t op[] = { 0xc9, 0xe8, 0x76, 0x32, 0x86, 0xb5, 0xb9, 0xff, ++ 0xbd, 0xf5, 0x6e, 0x12, 0x97, 0xd0, 0x88, 0x7b }; ++ uint8_t opc[] = { 0x98, 0x1d, 0x46, 0x4c, 0x7c, 0x52, 0xeb, 0x6e, ++ 0x50, 0x36, 0x23, 0x49, 0x84, 0xad, 0x0b, 0xcf }; ++ ++ /* ++ * Outputs ++ */ ++ uint8_t opc_out[MILENAGE_OPC_SIZE]; ++ uint8_t mac_a_out[MILENAGE_MAC_A_SIZE]; ++ uint8_t mac_s_out[MILENAGE_MAC_S_SIZE]; ++ uint8_t res_out[MILENAGE_RES_SIZE]; ++ uint8_t ck_out[MILENAGE_CK_SIZE]; ++ uint8_t ik_out[MILENAGE_IK_SIZE]; ++ uint8_t ak_out[MILENAGE_AK_SIZE]; ++ uint8_t ak_resync_out[MILENAGE_AK_SIZE]; ++ ++ /* function 1 */ ++ uint8_t mac_a[] = { 0x2a, 0x5c, 0x23, 0xd1, 0x5e, 0xe3, 0x51, 0xd5 }; ++ /* function 1* */ ++ uint8_t mac_s[] = { 0x62, 0xda, 0xe3, 0x85, 0x3f, 0x3a, 0xf9, 0xd2 }; ++ /* function 2 */ ++ uint8_t res[] = { 0x28, 0xd7, 0xb0, 0xf2, 0xa2, 0xec, 0x3d, 0xe5 }; ++ /* function 3 */ ++ uint8_t ck[] = { 0x53, 0x49, 0xfb, 0xe0, 0x98, 0x64, 0x9f, 0x94, ++ 0x8f, 0x5d, 0x2e, 0x97, 0x3a, 0x81, 0xc0, 0x0f }; ++ /* function 4 */ ++ uint8_t ik[] = { 0x97, 0x44, 0x87, 0x1a, 0xd3, 0x2b, 0xf9, 0xbb, ++ 0xd1, 0xdd, 0x5c, 0xe5, 0x4e, 0x3e, 0x2e, 0x5a }; ++ /* function 5 */ ++ uint8_t ak[] = { 0xad, 0xa1, 0x5a, 0xeb, 0x7b, 0xb8 }; ++ /* function 5* */ ++ uint8_t ak_resync[] = { 0xd4, 0x61, 0xbc, 0x15, 0x47, 0x5d }; ++ ++ int ret = 0; ++ ++/* ++ fr_debug_lvl = 4; ++*/ ++ ++ ret = milenage_opc_generate(opc_out, op, ki); ++ TEST_CHECK(ret == 0); ++ ++ //FR_PROTO_HEX_DUMP(opc_out, sizeof(opc_out), "opc"); ++ ++ TEST_CHECK(memcmp(opc_out, opc, sizeof(opc_out)) == 0); ++ ++ if ((milenage_f1(mac_a_out, mac_s_out, opc, ki, rand, sqn, amf) < 0) || ++ (milenage_f2345(res_out, ik_out, ck_out, ak_out, ak_resync_out, opc, ki, rand) < 0)) ret = -1; ++ ++ //FR_PROTO_HEX_DUMP(mac_a, sizeof(mac_a_out), "mac_a"); ++ //FR_PROTO_HEX_DUMP(mac_s, sizeof(mac_s_out), "mac_s"); ++ //FR_PROTO_HEX_DUMP(ik_out, sizeof(ik_out), "ik"); ++ //FR_PROTO_HEX_DUMP(ck_out, sizeof(ck_out), "ck"); ++ //FR_PROTO_HEX_DUMP(res_out, sizeof(res_out), "res"); ++ //FR_PROTO_HEX_DUMP(ak_out, sizeof(ak_out), "ak"); ++ //FR_PROTO_HEX_DUMP(ak_resync_out, sizeof(ak_resync_out), "ak_resync"); ++ ++ TEST_CHECK(ret == 0); ++ TEST_CHECK(memcmp(mac_a_out, mac_a, sizeof(mac_a_out)) == 0); ++ TEST_CHECK(memcmp(mac_s_out, mac_s, sizeof(mac_s_out)) == 0); ++ TEST_CHECK(memcmp(res_out, res, sizeof(res_out)) == 0); ++ TEST_CHECK(memcmp(ck_out, ck, sizeof(ck_out)) == 0); ++ TEST_CHECK(memcmp(ik_out, ik, sizeof(ik_out)) == 0); ++ TEST_CHECK(memcmp(ak_out, ak, sizeof(ak_out)) == 0); ++ TEST_CHECK(memcmp(ak_resync, ak_resync, sizeof(ak_resync_out)) == 0); ++} ++ ++TEST_LIST = { ++ { "test_set_1", test_set_1 }, ++ { "test_set_19", test_set_19 }, ++ { NULL } ++}; ++#endif +diff --git a/src/modules/rlm_wimax/milenage.h b/src/modules/rlm_wimax/milenage.h +new file mode 100644 +index 0000000000..758383aba2 +--- /dev/null ++++ b/src/modules/rlm_wimax/milenage.h +@@ -0,0 +1,128 @@ ++#pragma once ++/** ++ * @file src/modules/rlm_wimax/milenage.h ++ * @brief 3GPP AKA - Milenage algorithm (3GPP TS 35.205, .206, .207, .208) ++ * ++ * This file implements an example authentication algorithm defined for 3GPP ++ * AKA. This can be used to implement a simple HLR/AuC into hlr_auc_gw to allow ++ * EAP-AKA to be tested properly with real USIM cards. ++ * ++ * This implementations assumes that the r1..r5 and c1..c5 constants defined in ++ * TS 35.206 are used, i.e., r1=64, r2=0, r3=32, r4=64, r5=96, c1=00..00, ++ * c2=00..01, c3=00..02, c4=00..04, c5=00..08. The block cipher is assumed to ++ * be AES (Rijndael). ++ * ++ * This software may be distributed under the terms of the BSD license. ++ * See README for more details. ++ * ++ * @copyright 2017 The FreeRADIUS server project ++ * @copyright 2006-2007 (j@w1.fi) ++ */ ++#include ++ ++/* ++ * Inputs ++ */ ++#define MILENAGE_KI_SIZE 16 //!< Subscriber key. ++#define MILENAGE_OP_SIZE 16 //!< Operator code (unique to the operator) ++#define MILENAGE_OPC_SIZE 16 //!< Derived operator code (unique to the operator and subscriber). ++#define MILENAGE_AMF_SIZE 2 //!< Authentication management field. ++#define MILENAGE_SQN_SIZE 6 //!< Sequence number. ++#define MILENAGE_RAND_SIZE 16 //!< Random challenge. ++ ++/* ++ * UMTS Outputs ++ */ ++#define MILENAGE_AK_SIZE 6 //!< Anonymisation key. ++#define MILENAGE_AUTN_SIZE 16 //!< Network authentication key. ++#define MILENAGE_IK_SIZE 16 //!< Integrity key. ++#define MILENAGE_CK_SIZE 16 //!< Ciphering key. ++#define MILENAGE_RES_SIZE 8 ++#define MILENAGE_AUTS_SIZE 14 ++ ++/* ++ * GSM (COMP128-4) outputs ++ */ ++#define MILENAGE_SRES_SIZE 4 ++#define MILENAGE_KC_SIZE 8 ++ ++/** Copy a 48bit value from a 64bit integer into a uint8_t buff in big endian byte order ++ * ++ * There may be fast ways of doing this, but this is the *correct* ++ * way, and does not make assumptions about how integers are laid ++ * out in memory. ++ * ++ * @param[out] out 6 byte butter to store value. ++ * @param[in] i integer value. ++ * @return pointer to out. ++ */ ++static inline uint8_t *uint48_to_buff(uint8_t out[6], uint64_t i) ++{ ++ out[0] = (i & 0xff0000000000) >> 40; ++ out[1] = (i & 0x00ff00000000) >> 32; ++ out[2] = (i & 0x0000ff000000) >> 24; ++ out[3] = (i & 0x000000ff0000) >> 16; ++ out[4] = (i & 0x00000000ff00) >> 8; ++ out[5] = (i & 0x0000000000ff); ++ ++ return out; ++} ++ ++/** Convert a 48bit big endian value into a unsigned 64bit integer ++ * ++ */ ++static inline uint64_t uint48_from_buff(uint8_t const in[6]) ++{ ++ uint64_t i = 0; ++ ++ i |= ((uint64_t)in[0]) << 40; ++ i |= ((uint64_t)in[1]) << 32; ++ i |= ((uint32_t)in[2]) << 24; ++ i |= ((uint32_t)in[3]) << 16; ++ i |= ((uint16_t)in[4]) << 8; ++ i |= in[5]; ++ ++ return i; ++} ++ ++int milenage_opc_generate(uint8_t opc[MILENAGE_OPC_SIZE], ++ uint8_t const op[MILENAGE_OP_SIZE], ++ uint8_t const ki[MILENAGE_KI_SIZE]); ++ ++int milenage_umts_generate(uint8_t autn[MILENAGE_AUTN_SIZE], ++ uint8_t ik[MILENAGE_IK_SIZE], ++ uint8_t ck[MILENAGE_CK_SIZE], ++ uint8_t ak[MILENAGE_AK_SIZE], ++ uint8_t res[MILENAGE_RES_SIZE], ++ uint8_t const opc[MILENAGE_OPC_SIZE], ++ uint8_t const amf[MILENAGE_AMF_SIZE], ++ uint8_t const ki[MILENAGE_KI_SIZE], ++ uint64_t sqn, ++ uint8_t const rand[MILENAGE_RAND_SIZE]); ++ ++int milenage_auts(uint64_t *sqn, ++ uint8_t const opc[MILENAGE_OPC_SIZE], ++ uint8_t const ki[MILENAGE_KI_SIZE], ++ uint8_t const rand[MILENAGE_RAND_SIZE], ++ uint8_t const auts[MILENAGE_AUTS_SIZE]); ++ ++void milenage_gsm_from_umts(uint8_t sres[MILENAGE_SRES_SIZE], ++ uint8_t kc[MILENAGE_KC_SIZE], ++ uint8_t const ik[MILENAGE_IK_SIZE], ++ uint8_t const ck[MILENAGE_CK_SIZE], ++ uint8_t const res[MILENAGE_RES_SIZE]); ++ ++int milenage_gsm_generate(uint8_t sres[MILENAGE_SRES_SIZE], uint8_t kc[MILENAGE_KC_SIZE], ++ uint8_t const opc[MILENAGE_OPC_SIZE], ++ uint8_t const ki[MILENAGE_KI_SIZE], ++ uint8_t const rand[MILENAGE_RAND_SIZE]); ++ ++int milenage_check(uint8_t ik[MILENAGE_IK_SIZE], ++ uint8_t ck[MILENAGE_CK_SIZE], ++ uint8_t res[MILENAGE_RES_SIZE], ++ uint8_t auts[MILENAGE_AUTS_SIZE], ++ uint8_t const opc[MILENAGE_OPC_SIZE], ++ uint8_t const ki[MILENAGE_KI_SIZE], ++ uint64_t sqn, ++ uint8_t const rand[MILENAGE_RAND_SIZE], ++ uint8_t const autn[MILENAGE_AUTN_SIZE]); +diff --git a/src/modules/rlm_wimax/rlm_wimax.c b/src/modules/rlm_wimax/rlm_wimax.c +index f0fb394fcd..d2125eb3a5 100644 +--- a/src/modules/rlm_wimax/rlm_wimax.c ++++ b/src/modules/rlm_wimax/rlm_wimax.c +@@ -26,16 +26,43 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ + + #include + #include ++#include "milenage.h" + + #ifdef HAVE_OPENSSL_HMAC_H + #include + #endif + ++#include ++ ++#define WIMAX_EPSAKA_RAND_SIZE 16 ++#define WIMAX_EPSAKA_KI_SIZE 16 ++#define WIMAX_EPSAKA_OPC_SIZE 16 ++#define WIMAX_EPSAKA_AMF_SIZE 2 ++#define WIMAX_EPSAKA_SQN_SIZE 6 ++#define WIMAX_EPSAKA_MAC_A_SIZE 8 ++#define WIMAX_EPSAKA_MAC_S_SIZE 8 ++#define WIMAX_EPSAKA_XRES_SIZE 8 ++#define WIMAX_EPSAKA_CK_SIZE 16 ++#define WIMAX_EPSAKA_IK_SIZE 16 ++#define WIMAX_EPSAKA_AK_SIZE 6 ++#define WIMAX_EPSAKA_AK_RESYNC_SIZE 6 ++#define WIMAX_EPSAKA_KK_SIZE 32 ++#define WIMAX_EPSAKA_KS_SIZE 14 ++#define WIMAX_EPSAKA_PLMN_SIZE 3 ++#define WIMAX_EPSAKA_KASME_SIZE 32 ++#define WIMAX_EPSAKA_AUTN_SIZE 16 ++#define WIMAX_EPSAKA_AUTS_SIZE 14 ++ + /* + * FIXME: Fix the build system to create definitions from names. + */ + typedef struct rlm_wimax_t { + bool delete_mppe_keys; ++ ++ DICT_ATTR const *resync_info; ++ DICT_ATTR const *xres; ++ DICT_ATTR const *autn; ++ DICT_ATTR const *kasme; + } rlm_wimax_t; + + /* +@@ -52,15 +79,37 @@ static const CONF_PARSER module_config[] = { + CONF_PARSER_TERMINATOR + }; + ++/* ++ * Print hex values in a readable format for debugging ++ * Example: ++ * FOO: 00 11 AA 22 00 FF ++ */ ++static void rdebug_hex(REQUEST *request, char const *prefix, uint8_t const *data, int len) ++{ ++ int i; ++ char buffer[256]; /* large enough for largest len */ ++ ++ /* ++ * Leave a trailing space, we don't really care about that. ++ */ ++ for (i = 0; i < len; i++) { ++ snprintf(buffer + i * 3, sizeof(buffer) - i * 3, "%02x ", data[i]); ++ } ++ ++ RDEBUG("%s %s", prefix, buffer); ++} ++#define RDEBUG_HEX if (rad_debug_lvl) rdebug_hex ++ + /* + * Find the named user in this modules database. Create the set + * of attribute-value pairs to check and reply with for this user + * from the database. The authentication code only needs to check + * the password, the rest is done here. + */ +-static rlm_rcode_t CC_HINT(nonnull) mod_authorize(UNUSED void *instance, REQUEST *request) ++static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) + { + VALUE_PAIR *vp; ++ rlm_wimax_t *inst = instance; + + /* + * Fix Calling-Station-Id. Damn you, WiMAX! +@@ -93,10 +142,124 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(UNUSED void *instance, REQUEST + return RLM_MODULE_OK; + } + ++ /* ++ * Check for attr WiMAX-Re-synchronization-Info ++ * which contains the concatenation of RAND and AUTS ++ * ++ * If it is present then we proceed to verify the SIM and ++ * extract the new value of SQN ++ */ ++ VALUE_PAIR *resync_info, *ki, *opc, *sqn, *rand; ++ int m_ret; ++ ++ /* Look for the Re-synchronization-Info attribute in the request */ ++ resync_info = fr_pair_find_by_da(request->packet->vps, inst->resync_info, TAG_ANY); ++ if (resync_info && (resync_info->vp_length < (WIMAX_EPSAKA_RAND_SIZE + WIMAX_EPSAKA_AUTS_SIZE))) { ++ RWDEBUG("Found request:WiMAX-Re-synchronization-Info with incorrect length: Ignoring it"); ++ resync_info = NULL; ++ } ++ ++ /* ++ * These are the private keys which should be added to the control ++ * list after looking them up in a database by IMSI ++ * ++ * We grab them from the control list here ++ */ ++ ki = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_KI, 0, TAG_ANY); ++ if (ki && (ki->vp_length < MILENAGE_CK_SIZE)) { ++ RWDEBUG("Found config:WiMAX-SIM-Ki with incorrect length: Ignoring it"); ++ ki = NULL; ++ } ++ ++ opc = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_OPC, 0, TAG_ANY); ++ if (opc && (opc->vp_length < MILENAGE_IK_SIZE)) { ++ RWDEBUG("Found config:WiMAX-SIM-OPC with incorrect length: Ignoring it"); ++ opc = NULL; ++ } ++ ++ /* If we have resync info (RAND and AUTS), Ki and OPc then we can proceed */ ++ if (resync_info && ki && opc) { ++ uint64_t sqn_bin; ++ uint8_t rand_bin[WIMAX_EPSAKA_RAND_SIZE]; ++ uint8_t auts_bin[WIMAX_EPSAKA_AUTS_SIZE]; ++ ++ RDEBUG("Found WiMAX-Re-synchronization-Info. Proceeding with SQN resync"); ++ ++ /* Split Re-synchronization-Info into seperate RAND and AUTS */ ++ ++ memcpy(rand_bin, &resync_info->vp_octets[0], WIMAX_EPSAKA_RAND_SIZE); ++ memcpy(auts_bin, &resync_info->vp_octets[WIMAX_EPSAKA_RAND_SIZE], WIMAX_EPSAKA_AUTS_SIZE); ++ ++ RDEBUG_HEX(request, "RAND ", rand_bin, WIMAX_EPSAKA_RAND_SIZE); ++ RDEBUG_HEX(request, "AUTS ", auts_bin, WIMAX_EPSAKA_AUTS_SIZE); ++ ++ /* ++ * This procedure uses the secret keys Ki and OPc to authenticate ++ * the SIM and extract the SQN ++ */ ++ m_ret = milenage_auts(&sqn_bin, opc->vp_octets, ki->vp_octets, rand_bin, auts_bin); ++ ++ /* ++ * If the SIM verification fails then we can't go any further as ++ * we don't have the keys. And that probably means something bad ++ * is happening so we bail out now ++ */ ++ if (m_ret < 0) { ++ RDEBUG("SIM verification failed"); ++ return RLM_MODULE_REJECT; ++ } ++ ++ /* ++ * If we got this far it means have got a new SQN and RAND ++ * so we store them in: ++ * control:WiMAX-SIM-SQN ++ * control:WiMAX-SIM-RAND ++ * ++ * From there they can be grabbed by unlang and used later ++ */ ++ ++ /* SQN is six bytes so we extract what we need from the 64 bit variable */ ++ uint8_t sqn_bin_arr[WIMAX_EPSAKA_SQN_SIZE] = { ++ (sqn_bin & 0x0000FF0000000000ull) >> 40, ++ (sqn_bin & 0x000000FF00000000ull) >> 32, ++ (sqn_bin & 0x00000000FF000000ull) >> 24, ++ (sqn_bin & 0x0000000000FF0000ull) >> 16, ++ (sqn_bin & 0x000000000000FF00ull) >> 8, ++ (sqn_bin & 0x00000000000000FFull) >> 0 ++ }; ++ ++ /* Add SQN to control:WiMAX-SIM-SQN */ ++ sqn = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_SQN, 0, TAG_ANY); ++ if (sqn && (sqn->vp_length < WIMAX_EPSAKA_SQN_SIZE)) { ++ RWDEBUG("Found config:WiMAX-SIM-SQN with incorrect length: Ignoring it"); ++ sqn = NULL; ++ } ++ ++ if (!sqn) { ++ MEM(sqn = pair_make_config("WiMAX-SIM-SQN", NULL, T_OP_SET)); ++ fr_pair_value_memcpy(sqn, sqn_bin_arr, WIMAX_EPSAKA_SQN_SIZE); ++ } ++ RDEBUG_HEX(request, "SQN ", sqn->vp_octets, WIMAX_EPSAKA_SQN_SIZE); ++ ++ /* Add RAND to control:WiMAX-SIM-RAND */ ++ rand = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_RAND, 0, TAG_ANY); ++ if (rand && (rand->vp_length < WIMAX_EPSAKA_RAND_SIZE)) { ++ RWDEBUG("Found config:WiMAX-SIM-RAND with incorrect length: Ignoring it"); ++ rand = NULL; ++ } ++ ++ if (!rand) { ++ MEM(rand = pair_make_config("WiMAX-SIM-RAND", NULL, T_OP_SET)); ++ fr_pair_value_memcpy(rand, rand_bin, WIMAX_EPSAKA_RAND_SIZE); ++ } ++ RDEBUG_HEX(request, "RAND ", rand->vp_octets, WIMAX_EPSAKA_RAND_SIZE); ++ ++ return RLM_MODULE_UPDATED; ++ } ++ + return RLM_MODULE_NOOP; + } + +- + /* + * Massage the request before recording it or proxying it + */ +@@ -105,21 +268,14 @@ static rlm_rcode_t CC_HINT(nonnull) mod_preacct(void *instance, REQUEST *request + return mod_authorize(instance, request); + } + +-/* +- * Write accounting information to this modules database. +- */ +-static rlm_rcode_t CC_HINT(nonnull) mod_accounting(UNUSED void *instance, UNUSED REQUEST *request) +-{ +- return RLM_MODULE_OK; +-} + + /* +- * Generate the keys after the user has been authenticated. ++ * This function generates the keys for old style WiMAX (v1 to v2.0) + */ +-static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request) ++static int mip_keys_generate(void *instance, REQUEST *request, VALUE_PAIR *msk, VALUE_PAIR *emsk) + { + rlm_wimax_t *inst = instance; +- VALUE_PAIR *msk, *emsk, *vp; ++ VALUE_PAIR *vp; + VALUE_PAIR *mn_nai, *ip, *fa_rk; + HMAC_CTX *hmac; + unsigned int rk1_len, rk2_len, rk_len; +@@ -128,13 +284,6 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + uint8_t mip_rk_1[EVP_MAX_MD_SIZE], mip_rk_2[EVP_MAX_MD_SIZE]; + uint8_t mip_rk[2 * EVP_MAX_MD_SIZE]; + +- msk = fr_pair_find_by_num(request->reply->vps, PW_EAP_MSK, 0, TAG_ANY); +- emsk = fr_pair_find_by_num(request->reply->vps, PW_EAP_EMSK, 0, TAG_ANY); +- if (!msk || !emsk) { +- RDEBUG("No EAP-MSK or EAP-EMSK. Cannot create WiMAX keys"); +- return RLM_MODULE_NOOP; +- } +- + /* + * If we delete the MS-MPPE-*-Key attributes, then add in + * the WiMAX-MSK so that the client has a key available. +@@ -143,10 +292,8 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + fr_pair_delete_by_num(&request->reply->vps, 16, VENDORPEC_MICROSOFT, TAG_ANY); + fr_pair_delete_by_num(&request->reply->vps, 17, VENDORPEC_MICROSOFT, TAG_ANY); + +- vp = pair_make_reply("WiMAX-MSK", NULL, T_OP_EQ); +- if (vp) { +- fr_pair_value_memcpy(vp, msk->vp_octets, msk->vp_length); +- } ++ MEM(vp = pair_make_reply("WiMAX-MSK", NULL, T_OP_EQ)); ++ fr_pair_value_memcpy(vp, msk->vp_octets, msk->vp_length); + } + + /* +@@ -162,6 +309,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + */ + hmac = HMAC_CTX_new(); + HMAC_Init_ex(hmac, emsk->vp_octets, emsk->vp_length, EVP_sha256(), NULL); ++ rk1_len = SHA256_DIGEST_LENGTH; + + HMAC_Update(hmac, &usage_data[0], sizeof(usage_data)); + HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); +@@ -173,6 +321,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + + HMAC_Update(hmac, (uint8_t const *) &mip_rk_1, rk1_len); + HMAC_Update(hmac, &usage_data[0], sizeof(usage_data)); ++ rk2_len = SHA256_DIGEST_LENGTH; + HMAC_Final(hmac, &mip_rk_2[0], &rk2_len); + + memcpy(mip_rk, mip_rk_1, rk1_len); +@@ -185,6 +334,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + HMAC_Init_ex(hmac, mip_rk, rk_len, EVP_sha256(), NULL); + + HMAC_Update(hmac, (uint8_t const *) "SPI CMIP PMIP", 12); ++ rk1_len = SHA256_DIGEST_LENGTH; + HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); + + /* +@@ -195,16 +345,8 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + (mip_rk_1[2] << 8) | mip_rk_1[3]); + if (mip_spi < 256) mip_spi += 256; + +- if (rad_debug_lvl) { +- int len = rk_len; +- char buffer[512]; +- +- if (len > 128) len = 128; /* buffer size */ +- +- fr_bin2hex(buffer, mip_rk, len); +- RDEBUG("MIP-RK = 0x%s", buffer); +- RDEBUG("MIP-SPI = %08x", ntohl(mip_spi)); +- } ++ RDEBUG_HEX(request, "MIP-RK ", mip_rk, rk_len); ++ RDEBUG("MIP-SPI = %08x", ntohl(mip_spi)); + + /* + * FIXME: Perform SPI collision prevention +@@ -250,6 +392,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + HMAC_Update(hmac, (uint8_t const *) "PMIP4 MN HA", 11); + HMAC_Update(hmac, (uint8_t const *) &ip->vp_ipaddr, 4); + HMAC_Update(hmac, (uint8_t const *) &mn_nai->vp_strvalue, mn_nai->vp_length); ++ rk1_len = SHA1_DIGEST_LENGTH; + HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); + + /* +@@ -300,6 +443,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + HMAC_Update(hmac, (uint8_t const *) "CMIP4 MN HA", 11); + HMAC_Update(hmac, (uint8_t const *) &ip->vp_ipaddr, 4); + HMAC_Update(hmac, (uint8_t const *) &mn_nai->vp_strvalue, mn_nai->vp_length); ++ rk1_len = SHA1_DIGEST_LENGTH; + HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); + + /* +@@ -350,6 +494,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + HMAC_Update(hmac, (uint8_t const *) "CMIP6 MN HA", 11); + HMAC_Update(hmac, (uint8_t const *) &ip->vp_ipv6addr, 16); + HMAC_Update(hmac, (uint8_t const *) &mn_nai->vp_strvalue, mn_nai->vp_length); ++ rk1_len = SHA1_DIGEST_LENGTH; + HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); + + /* +@@ -396,6 +541,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + + HMAC_Update(hmac, (uint8_t const *) "FA-RK", 5); + ++ rk1_len = SHA1_DIGEST_LENGTH; + HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); + + fr_pair_value_memcpy(fa_rk, &mip_rk_1[0], rk1_len); +@@ -455,6 +601,221 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque + return RLM_MODULE_UPDATED; + } + ++/* ++ * Generate the EPS-AKA authentication vector ++ * ++ * These are the keys needed for new style WiMAX (LTE / 3gpp authentication), ++ for WiMAX v2.1 ++ */ ++static rlm_rcode_t aka_keys_generate(REQUEST *request, rlm_wimax_t const *inst, VALUE_PAIR *ki, VALUE_PAIR *opc, ++ VALUE_PAIR *amf, VALUE_PAIR *sqn, VALUE_PAIR *plmn) ++{ ++ size_t i; ++ VALUE_PAIR *rand_previous, *rand, *xres, *autn, *kasme; ++ ++ /* ++ * For most authentication requests we need to generate a fresh RAND ++ * ++ * The exception is after SQN re-syncronisation - in this case we ++ * get RAND in the request, and this module if called in authorize should ++ * have put it in control:WiMAX-SIM-RAND so we can grab it from there) ++ */ ++ rand_previous = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_RAND, 0, TAG_ANY); ++ if (rand_previous && (rand_previous->vp_length < WIMAX_EPSAKA_RAND_SIZE)) { ++ RWDEBUG("Found config:WiMAX-SIM-Rand with incorrect size. Ignoring it."); ++ rand_previous = NULL; ++ } ++ ++ MEM(rand = pair_make_reply("WiMAX-E-UTRAN-Vector-RAND", NULL, T_OP_SET)); ++ if (!rand_previous) { ++ uint32_t lvalue; ++ uint8_t buffer[WIMAX_EPSAKA_RAND_SIZE]; ++ ++ for (i = 0; i < (WIMAX_EPSAKA_RAND_SIZE / 4); i++) { ++ lvalue = fr_rand(); ++ memcpy(buffer + i * 4, &lvalue, sizeof(lvalue)); ++ } ++ ++ fr_pair_value_memcpy(rand, buffer, WIMAX_EPSAKA_RAND_SIZE); ++ ++ } else { ++ fr_pair_value_memcpy(rand, rand_previous->vp_octets, WIMAX_EPSAKA_RAND_SIZE); ++ } ++ ++ /* ++ * Feed AMF, Ki, SQN and RAND into the Milenage algorithm (f1, f2, f3, f4, f5) ++ * which returns AUTN, AK, CK, IK, XRES. ++ */ ++ uint8_t xres_bin[WIMAX_EPSAKA_XRES_SIZE]; ++ uint8_t ck_bin[WIMAX_EPSAKA_CK_SIZE]; ++ uint8_t ik_bin[WIMAX_EPSAKA_IK_SIZE]; ++ uint8_t ak_bin[WIMAX_EPSAKA_AK_SIZE]; ++ uint8_t autn_bin[WIMAX_EPSAKA_AUTN_SIZE]; ++ ++ /* But first convert uint8 SQN to uint64 */ ++ uint64_t sqn_bin = 0x0000000000000000; ++ for (i = 0; i < sqn->vp_length; ++i) sqn_bin = (sqn_bin << 8) | sqn->vp_octets[i]; ++ ++ if (!opc || (opc->vp_length < MILENAGE_OPC_SIZE)) { ++ RWDEBUG("Found config:WiMAX-SIM-OPC with incorrect size. Ignoring it"); ++ return RLM_MODULE_NOOP; ++ } ++ if (!amf || (amf->vp_length < MILENAGE_AMF_SIZE)) { ++ RWDEBUG("Found config:WiMAX-SIM-AMF with incorrect size. Ignoring it"); ++ return RLM_MODULE_NOOP; ++ } ++ if (!ki || (ki->vp_length < MILENAGE_KI_SIZE)) { ++ RWDEBUG("Found config:WiMAX-SIM-KI with incorrect size. Ignoring it"); ++ return RLM_MODULE_NOOP; ++ } ++ ++ /* Call milenage */ ++ milenage_umts_generate(autn_bin, ik_bin, ck_bin, ak_bin, xres_bin, opc->vp_octets, ++ amf->vp_octets, ki->vp_octets, sqn_bin, rand->vp_octets); ++ ++ /* ++ * Now we genertate KASME ++ * ++ * Officially described in 33401-g30.doc section A.2 ++ * But an easier to read explanation can be found at: ++ * https://medium.com/uw-ictd/lte-authentication-2d0810a061ec ++ * ++ */ ++ ++ /* k = CK || IK */ ++ uint8_t kk_bin[WIMAX_EPSAKA_KK_SIZE]; ++ memcpy(kk_bin, ck_bin, sizeof(ck_bin)); ++ memcpy(kk_bin + sizeof(ck_bin), ik_bin, sizeof(ik_bin)); ++ ++ /* Initialize a 14 byte buffer s */ ++ uint8_t ks_bin[WIMAX_EPSAKA_KS_SIZE]; ++ ++ /* Assign the first byte of s as 0x10 */ ++ ks_bin[0] = 0x10; ++ ++ /* Copy the 3 bytes of PLMN into s */ ++ memcpy(ks_bin + 1, plmn->vp_octets, 3); ++ ++ /* Assign 5th and 6th byte as 0x00 and 0x03 */ ++ ks_bin[4] = 0x00; ++ ks_bin[5] = 0x03; ++ ++ /* Assign the next 6 bytes as SQN XOR AK */ ++ for (i = 0; i < 6; i++) { ++ ks_bin[i+6] = sqn->vp_octets[i] ^ ak_bin[i]; ++ } ++ ++ /* Assign the last two bytes as 0x00 and 0x06 */ ++ ks_bin[12] = 0x00; ++ ks_bin[13] = 0x06; ++ ++ /* Perform an HMAC-SHA256 using Key k from step 1 and s as the message. */ ++ uint8_t kasme_bin[WIMAX_EPSAKA_KASME_SIZE]; ++ HMAC_CTX *hmac; ++ unsigned int kasme_len = sizeof(kasme_bin); ++ ++ hmac = HMAC_CTX_new(); ++ HMAC_Init_ex(hmac, kk_bin, sizeof(kk_bin), EVP_sha256(), NULL); ++ HMAC_Update(hmac, ks_bin, sizeof(ks_bin)); ++ kasme_len = SHA256_DIGEST_LENGTH; ++ HMAC_Final(hmac, &kasme_bin[0], &kasme_len); ++ HMAC_CTX_free(hmac); ++ ++ /* ++ * Add reply attributes XRES, AUTN and KASME (RAND we added earlier) ++ * ++ * Note that we can't call fr_pair_find_by_num(), as ++ * these attributes are buried deep inside of the WiMAX ++ * hierarchy. ++ */ ++ xres = fr_pair_find_by_da(request->reply->vps, inst->xres, TAG_ANY); ++ if (!xres) { ++ MEM(xres = pair_make_reply("WiMAX-E-UTRAN-Vector-XRES", NULL, T_OP_SET)); ++ fr_pair_value_memcpy(xres, xres_bin, WIMAX_EPSAKA_XRES_SIZE); ++ } ++ ++ autn = fr_pair_find_by_da(request->reply->vps, inst->autn, TAG_ANY); ++ if (!autn) { ++ MEM(autn = pair_make_reply("WiMAX-E-UTRAN-Vector-AUTN", NULL, T_OP_SET)); ++ fr_pair_value_memcpy(autn, autn_bin, WIMAX_EPSAKA_AUTN_SIZE); ++ } ++ ++ kasme = fr_pair_find_by_da(request->reply->vps, inst->kasme, TAG_ANY); ++ if (!kasme) { ++ MEM(kasme = pair_make_reply("WiMAX-E-UTRAN-Vector-KASME", NULL, T_OP_SET)); ++ fr_pair_value_memcpy(kasme, kasme_bin, WIMAX_EPSAKA_KASME_SIZE); ++ } ++ ++ /* Print keys to log for debugging */ ++ if (rad_debug_lvl) { ++ RDEBUG("-------- Milenage in --------"); ++ RDEBUG_HEX(request, "OPc ", opc->vp_octets, opc->vp_length); ++ RDEBUG_HEX(request, "Ki ", ki->vp_octets, ki->vp_length); ++ RDEBUG_HEX(request, "RAND ", rand->vp_octets, rand->vp_length); ++ RDEBUG_HEX(request, "SQN ", sqn->vp_octets, sqn->vp_length); ++ RDEBUG_HEX(request, "AMF ", amf->vp_octets, amf->vp_length); ++ RDEBUG("-------- Milenage out -------"); ++ RDEBUG_HEX(request, "XRES ", xres->vp_octets, xres->vp_length); ++ RDEBUG_HEX(request, "Ck ", ck_bin, sizeof(ck_bin)); ++ RDEBUG_HEX(request, "Ik ", ik_bin, sizeof(ik_bin)); ++ RDEBUG_HEX(request, "Ak ", ak_bin, sizeof(ak_bin)); ++ RDEBUG_HEX(request, "AUTN ", autn->vp_octets, autn->vp_length); ++ RDEBUG("-----------------------------"); ++ RDEBUG_HEX(request, "Kk ", kk_bin, sizeof(kk_bin)); ++ RDEBUG_HEX(request, "Ks ", ks_bin, sizeof(ks_bin)); ++ RDEBUG_HEX(request, "KASME ", kasme->vp_octets, kasme->vp_length); ++ } ++ ++ return RLM_MODULE_UPDATED; ++} ++ ++/* ++ * Generate the keys after the user has been authenticated. ++ */ ++static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request) ++{ ++ VALUE_PAIR *msk, *emsk, *ki, *opc, *amf, *sqn, *plmn; ++ ++ /* ++ * If we have MSK and EMSK then assume we want MIP keys ++ * Else if we have the SIM keys then we want the EPS-AKA vector ++ */ ++ ++ msk = fr_pair_find_by_num(request->reply->vps, PW_EAP_MSK, 0, TAG_ANY); ++ emsk = fr_pair_find_by_num(request->reply->vps, PW_EAP_EMSK, 0, TAG_ANY); ++ ++ if (msk && emsk) { ++ RDEBUG("MSK and EMSK found. Generating MIP keys"); ++ return mip_keys_generate(instance, request, msk, emsk); ++ } ++ ++ ki = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_KI, 0, TAG_ANY); ++ opc = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_OPC, 0, TAG_ANY); ++ amf = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_AMF, 0, TAG_ANY); ++ sqn = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_SQN, 0, TAG_ANY); ++ plmn = fr_pair_find_by_num(request->packet->vps, 146, VENDORPEC_WIMAX, TAG_ANY); ++ ++ if (ki && opc && amf && sqn && plmn) { ++ RDEBUG("AKA attributes found. Generating AKA keys."); ++ return aka_keys_generate(request, instance, ki, opc, amf, sqn, plmn); ++ } ++ ++ RDEBUG("Input keys not found. Cannot create WiMAX keys"); ++ return RLM_MODULE_NOOP; ++} ++ ++ ++static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance) ++{ ++ rlm_wimax_t *inst = instance; ++ ++ inst->resync_info = dict_attrbyname("WiMAX-Re-synchronization-Info"); ++ inst->xres = dict_attrbyname("WiMAX-E-UTRAN-Vector-XRES"); ++ inst->autn = dict_attrbyname("WiMAX-E-UTRAN-Vector-AUTN"); ++ inst->kasme = dict_attrbyname("WiMAX-E-UTRAN-Vector-KASME"); ++ ++ return 0; ++} + + /* + * The module name should be the only globally exported symbol. +@@ -472,10 +833,10 @@ module_t rlm_wimax = { + .type = RLM_TYPE_THREAD_SAFE, + .inst_size = sizeof(rlm_wimax_t), + .config = module_config, ++ .instantiate = mod_instantiate, + .methods = { + [MOD_AUTHORIZE] = mod_authorize, + [MOD_PREACCT] = mod_preacct, +- [MOD_ACCOUNTING] = mod_accounting, + [MOD_POST_AUTH] = mod_post_auth + }, + }; +diff --git a/src/tests/keywords/md4 b/src/tests/keywords/md4 +new file mode 100644 +index 0000000000..7e9b1ffcdf +--- /dev/null ++++ b/src/tests/keywords/md4 +@@ -0,0 +1,58 @@ ++# ++# PRE: update if ++# ++update reply { ++ Filter-Id := "filter" ++} ++ ++update { ++ control:Cleartext-Password := 'hello' ++ request:Tmp-String-0 := "This is a string\n" ++ request:Tmp-Octets-0 := 0x000504030201 ++ request:Tmp-String-1 := "what do ya want for nothing?" ++ request:Tmp-String-2 := "Jefe" ++} ++ ++# ++# Put "This is a string" into a file and call "md5sum" on it. ++# You should get this string. ++# ++if ("%{md4:This is a string\n}" != '1f60d5cd85e17bfbdda7c923822f060c') { ++ update reply { ++ Filter-Id += 'fail 1' ++ } ++} ++ ++if ("%{md4:&Tmp-String-0}" != '1f60d5cd85e17bfbdda7c923822f060c') { ++ update reply { ++ Filter-Id += 'fail 2' ++ } ++} ++ ++if ("%{md4:&request:Tmp-String-0}" != '1f60d5cd85e17bfbdda7c923822f060c') { ++ update reply { ++ Filter-Id += 'fail 3' ++ } ++} ++ ++if ("%{md4:%{request:Tmp-String-0}}" != '1f60d5cd85e17bfbdda7c923822f060c') { ++ update reply { ++ Filter-Id += 'fail 4' ++ } ++} ++ ++# ++# MD4 should also be able to cope with references to octet attributes ++# ++if ("%{md4:&request:Tmp-Octets-0}" != 'ac3ed17b3cf19ec38352ec534a932fc6') { ++ update reply { ++ Filter-Id += 'fail 5' ++ } ++} ++ ++if ("%{md4:&Tmp-String-1}" != 'f7b44afb9cfdc877aa99d44654fe808b') { ++ update reply { ++ Filter-Id += 'fail 6' ++ } ++} ++ diff --git a/SOURCES/freeradius-ldap-infinite-timeout-on-starttls.patch b/SOURCES/freeradius-ldap-infinite-timeout-on-starttls.patch new file mode 100644 index 0000000..40df134 --- /dev/null +++ b/SOURCES/freeradius-ldap-infinite-timeout-on-starttls.patch @@ -0,0 +1,31 @@ +From: Antonio Torres +Date: Fri, 28 Jan 2022 +Subject: Use infinite timeout when using LDAP+start-TLS + +This will ensure that the TLS connection to the LDAP server will complete +before starting FreeRADIUS, as it forces libldap to use a blocking socket during +the process. Infinite timeout is the OpenLDAP default. +Avoids this: https://git.openldap.org/openldap/openldap/-/blob/87ffc60006298069a5a044b8e63dab27a61d3fdf/libraries/libldap/tls2.c#L1134 + +Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1992551 +Signed-off-by: Antonio Torres +--- + src/modules/rlm_ldap/ldap.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/src/modules/rlm_ldap/ldap.c b/src/modules/rlm_ldap/ldap.c +index cf7a84e069..841bf888a1 100644 +--- a/src/modules/rlm_ldap/ldap.c ++++ b/src/modules/rlm_ldap/ldap.c +@@ -1472,7 +1472,10 @@ void *mod_conn_create(TALLOC_CTX *ctx, void *instance) + } + + #ifdef LDAP_OPT_NETWORK_TIMEOUT +- if (inst->net_timeout) { ++ bool using_tls = inst->start_tls || ++ inst->port == 636 || ++ strncmp(inst->server, "ldaps://", strlen("ldaps://")) == 0; ++ if (inst->net_timeout && !using_tls) { + memset(&tv, 0, sizeof(tv)); + tv.tv_sec = inst->net_timeout; + diff --git a/SOURCES/freeradius-tmpfiles.conf b/SOURCES/freeradius-tmpfiles.conf index 8f20796..bccd09d 100644 --- a/SOURCES/freeradius-tmpfiles.conf +++ b/SOURCES/freeradius-tmpfiles.conf @@ -1 +1,2 @@ D /run/radiusd 0710 radiusd radiusd - +D /run/radiusd/tmp 0700 radiusd radiusd - diff --git a/SPECS/freeradius.spec b/SPECS/freeradius.spec index b52eed8..2a42dea 100644 --- a/SPECS/freeradius.spec +++ b/SPECS/freeradius.spec @@ -1,7 +1,7 @@ Summary: High-performance and highly configurable free RADIUS server Name: freeradius Version: 3.0.21 -Release: 21%{?dist} +Release: 26%{?dist} License: GPLv2+ and LGPLv2+ URL: http://www.freeradius.org/ @@ -26,6 +26,8 @@ Patch3: freeradius-bootstrap-create-only.patch Patch4: freeradius-no-buildtime-cert-gen.patch Patch5: freeradius-bootstrap-make-permissions.patch Patch6: freeradius-Fix-resource-hard-limit-error.patch +Patch7: freeradius-ldap-infinite-timeout-on-starttls.patch +Patch8: freeradius-Backport-OpenSSL3-fixes.patch %global docdir %{?_pkgdocdir}%{!?_pkgdocdir:%{_docdir}/%{name}-%{version}} @@ -53,7 +55,7 @@ BuildRequires: ykclient-devel # Require OpenSSL version we built with, or newer, to avoid startup failures # due to runtime OpenSSL version checks. -Requires: openssl >= %(rpm -q --queryformat '%%{EPOCH}:%%{VERSION}' openssl) +Requires: openssl >= %(rpm -q --queryformat '%%{VERSION}' openssl) Requires(pre): shadow-utils glibc-common Requires(post): systemd-sysv Requires(post): systemd-units @@ -209,6 +211,8 @@ This plugin provides the REST support for the FreeRADIUS server project. %patch4 -p1 %patch5 -p1 %patch6 -p1 +%patch7 -p1 +%patch8 -p1 %build # Force compile/link options, extra security for network facing daemon @@ -851,6 +855,28 @@ exit 0 %attr(640,root,radiusd) %config(noreplace) /etc/raddb/mods-available/rest %changelog +* Mon Jan 31 2022 Antonio Torres - 3.0.21-26 +- Move remaining files from /var/run to /run + Related: rhbz#2047972 + +* Fri Jan 28 2022 Antonio Torres - 3.0.21-25 +- Revert "Allow to connect to partially open LDAP handle" +- Use infinite timeout (openldap default) when using LDAP+start-TLS +- Update openssl dependency to not check epoch (was causing detection issues) + Related: rhbz#1992551 + +* Thu Jan 13 2022 Antonio Torres - 3.0.21-24 +- Avoid segfault when trying to use MD4 without legacy provider + Related: rhbz#1978216 + +* Wed Jan 12 2022 Antonio Torres - 3.0.21-23 +- Backport OpenSSL3 fixes + Related: rhbz#1978216 + +* Wed Oct 13 2021 Antonio Torres - 3.0.21-22 +- Allow to connect to partially open LDAP handle + Related: rhbz#1992551 + * Mon Sep 27 2021 Antonio Torres - 3.0.21-21 - Move FR's systemd unit PID file from /var/run to /run Related: rhbz#2006368