Date: Tue, 11 Jan 2022 Subject: [PATCH] Backport OpenSSL3 fixes from 3.0.x Backport TLS and OpenSSL3 fixes from the 3.0.x branch as of May 24th, 2022. Related: rhbz#1978216 Related: rhbz#2083699 Signed-off-by: Antonio Torres [antorres@redhat.com]: commit 947d5d6bd2674a60f7320f0b721e4723243c2285 is backported manually to avoid issues when applying on top of 3.0.21 tag. Because of this, files configure and configure.ac only contain changes (adapted) from this commit, not other changes from upstream state. --- configure | 19 + configure.ac | 19 + share/dictionary.freeradius.internal | 54 +- src/include/build.h | 25 +- src/include/libradius.h | 23 +- src/include/listen.h | 26 +- src/include/md4.h | 50 +- src/include/md5.h | 33 +- src/include/openssl3.h | 109 ++ src/include/tls-h | 32 +- src/include/token.h | 7 +- src/lib/dict.c | 150 +- src/lib/hmacmd5.c | 6 +- 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 | 121 +- src/main/map.c | 54 +- src/main/radclient.c | 77 +- src/main/tls.c | 1912 ++++++++++++++++---- src/main/tls_listen.c | 177 +- 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 | 211 ++- 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 | 64 +- src/modules/rlm_eap/types/rlm_eap_peap/peap.c | 22 +- .../rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c | 21 +- src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h | 190 ++ src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c | 779 +++++--- 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 | 20 +- 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 | 4 + src/modules/rlm_mschap/rlm_mschap.c | 99 +- 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 + 58 files changed, 5951 insertions(+), 1205 deletions(-) diff --git a/configure b/configure index edf08649a0..5b58f76c97 100755 --- a/configure +++ b/configure @@ -736,6 +736,7 @@ ac_subst_files='' ac_user_opts=' enable_option_checking enable_developer +enable_fips_workaround enable_largefile enable_strict_dependencies enable_werror @@ -1406,6 +1407,7 @@ Optional Features: --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --enable-FEATURE[=ARG] include FEATURE [ARG=yes] --enable-developer enables features of interest to developers. + --enable-fips-workaround enables local MD4, MD5, etc. functionality to avoid OpenSSL FIPS issues. --disable-largefile omit support for large files --enable-strict-dependencies fail configure on lack of module dependancy. --enable-werror causes the build to fail if any warnings are generated. @@ -2486,6 +2488,23 @@ if test "x$developer" = "xyes"; then : ${CFLAGS=-g3} fi +# Check whether --enable-fips-workaround was given. +if test ${enable_fips_workaround+y} +then : + enableval=$enable_fips_workaround; case "$enableval" in + no) + fips="" + ;; + *) + fips="yes" + esac +else $as_nop + fips="" +fi + +if test "x$fips" = "xyes"; then +$as_echo "#define WITH_FIPS 1" >>confdefs.h +fi ac_aux_dir= for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do diff --git a/configure.ac b/configure.ac index c72511ab39..10b7cc02c0 100644 --- a/configure.ac +++ b/configure.ac @@ -100,6 +100,25 @@ if test "x$developer" = "xyes"; then : ${CFLAGS=-g3} fi +dnl # +dnl # Hard-code FIPS support/ +dnl # +AC_ARG_ENABLE(fips-workaround, +[ --enable-fips-workaround enables local MD4, MD5, etc. functionality to avoid OpenSSL FIPS issues.], +[ case "$enableval" in + no) + fips="" + ;; + *) + fips="yes" + esac ], +[ fips="" ], +) +if test "x$fips" != "xyes"; then + AC_DEFINE(WITH_FIPS, [1], [define if you want FIPS support]) +fi +AC_SUBST(WITH_FIPS) + dnl ############################################################# dnl # dnl # 0. Checks for compiler, libtool, and command line options. diff --git a/share/dictionary.freeradius.internal b/share/dictionary.freeradius.internal index 724e1f7ff6..347e3e59f3 100644 --- a/share/dictionary.freeradius.internal +++ b/share/dictionary.freeradius.internal @@ -148,7 +148,7 @@ VALUE EAP-IKEv2-IDType DER_ASN1_GN 10 VALUE EAP-IKEv2-IDType KEY_ID 11 ATTRIBUTE EAP-IKEv2-ID 1104 string -ATTRIBUTE EAP-IKEv2-Secret 1105 string +ATTRIBUTE EAP-IKEv2-Secret 1105 string secret ATTRIBUTE EAP-IKEv2-AuthType 1106 integer VALUE EAP-IKEv2-AuthType none 0 @@ -196,7 +196,7 @@ ATTRIBUTE FreeRADIUS-Client-Require-MA 1122 integer VALUE FreeRADIUS-Client-Require-MA no 0 VALUE FreeRADIUS-Client-Require-MA yes 1 -ATTRIBUTE FreeRADIUS-Client-Secret 1123 string +ATTRIBUTE FreeRADIUS-Client-Secret 1123 string secret ATTRIBUTE FreeRADIUS-Client-Shortname 1124 string ATTRIBUTE FreeRADIUS-Client-NAS-Type 1125 string ATTRIBUTE FreeRADIUS-Client-Virtual-Server 1126 string @@ -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..e8222a3f02 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 @@ -68,11 +70,12 @@ struct rad_listen { int fd; char const *server; int status; -#ifdef WITH_TCP int count; - bool dual; +#ifdef WITH_TCP 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..1492bd4a5c 100644 --- a/src/include/md4.h +++ b/src/include/md4.h @@ -26,6 +26,10 @@ RCSIDH(md4_h, "$Id$") #include +#ifdef WITH_FIPS +#undef HAVE_OPENSSL_MD4_H +#endif + #ifdef HAVE_OPENSSL_MD4_H # include #endif @@ -71,14 +75,58 @@ 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); + EVP_DigestInit_ex(ctx->ctx, ctx->md, NULL); +} + +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..b7d571ac7b 100644 --- a/src/include/md5.h +++ b/src/include/md5.h @@ -26,6 +26,10 @@ RCSIDH(md5_h, "$Id$") # include +#ifdef WITH_FIPS +#undef HAVE_OPENSSL_MD5_H +#endif + #ifdef HAVE_OPENSSL_MD5_H # include #endif @@ -68,14 +72,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..206f55db79 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; @@ -103,7 +103,6 @@ typedef struct _tls_info_t { char info_description[256]; size_t record_len; - int version; } tls_info_t; #if OPENSSL_VERSION_NUMBER < 0x10001000L @@ -139,6 +138,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 +162,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 +314,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 +366,10 @@ struct fr_tls_server_conf_t { bool disable_tlsv1; bool disable_tlsv1_1; bool disable_tlsv1_2; + 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 +381,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 +403,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 +430,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/dict.c b/src/lib/dict.c index 96e06b5287..479bf1104e 100644 --- a/src/lib/dict.c +++ b/src/lib/dict.c @@ -725,7 +725,8 @@ int dict_addattr(char const *name, int attr, unsigned int vendor, PW_TYPE type, { size_t namelen; DICT_ATTR const *parent; - DICT_ATTR *n; + DICT_ATTR *n; + DICT_ATTR const *old; static int max_attr = 0; namelen = strlen(name); @@ -882,6 +883,8 @@ int dict_addattr(char const *name, int attr, unsigned int vendor, PW_TYPE type, return -1; } + if (flags.encrypt) flags.secret = 1; + if (flags.length && (type != PW_TYPE_OCTETS)) { fr_strerror_printf("The \"length\" flag can only be set for attributes of type \"octets\""); return -1; @@ -1080,6 +1083,18 @@ int dict_addattr(char const *name, int attr, unsigned int vendor, PW_TYPE type, n->type = type; n->flags = flags; + /* + * Allow old-style names, but they always end up as + * new-style names. + */ + old = dict_attrbyvalue(n->attr, n->vendor); + if (old && (old->type == n->type)) { + DICT_ATTR *mutable; + + memcpy(&mutable, &old, sizeof(old)); /* const issues */ + mutable->flags.is_dup = true; + } + /* * Insert the attribute, only if it's not a duplicate. */ @@ -1258,6 +1273,8 @@ int dict_addvalue(char const *namestr, char const *attrstr, int value) fr_int2str(dict_attr_types, da->type, "?Unknown?")); return -1; } + /* in v4 this is done with the UNCONST #define */ + ((DICT_ATTR *)((uintptr_t)(da)))->flags.has_value = 1; } else { value_fixup_t *fixup; @@ -1742,6 +1759,10 @@ static int process_attribute(char const* fn, int const line, "\"encrypt=3\" flag set", fn, line); return -1; } + flags.secret = 1; + + } else if (strncmp(key, "secret", 6) == 0) { + flags.secret = 1; } else if (strncmp(key, "array", 6) == 0) { flags.array = 1; @@ -2022,7 +2043,7 @@ static int process_value_alias(char const* fn, int const line, char **argv, } -static int parse_format(char const *fn, int line, char const *format, int *pvalue, int *ptype, int *plength, bool *pcontinuation) +static int parse_format(char const *fn, int line, char const *format, int *ptype, int *plength, bool *pcontinuation) { char const *p; int type, length; @@ -2075,9 +2096,8 @@ static int parse_format(char const *fn, int line, char const *format, int *pvalu } continuation = true; - if ((*pvalue != VENDORPEC_WIMAX) || - (type != 1) || (length != 1)) { - fr_strerror_printf("dict_init: %s[%d]: Only WiMAX VSAs can have continuations", + if ((type != 1) || (length != 1)) { + fr_strerror_printf("dict_init: %s[%d]: Only 'format=1,1' VSAs can have continuations", fn, line); return -1; } @@ -2132,7 +2152,7 @@ static int process_vendor(char const* fn, int const line, char **argv, * Look for a format statement. Allow it to over-ride the hard-coded formats below. */ if (argc == 3) { - if (parse_format(fn, line, argv[2], &value, &type, &length, &continuation) < 0) { + if (parse_format(fn, line, argv[2], &type, &length, &continuation) < 0) { return -1; } @@ -2409,7 +2429,8 @@ static int my_dict_init(char const *parent, char const *filename, /* * Optionally include a dictionary */ - if (strcasecmp(argv[0], "$INCLUDE-") == 0) { + if ((strcasecmp(argv[0], "$INCLUDE-") == 0) || + (strcasecmp(argv[0], "$-INCLUDE") == 0)) { int rcode = my_dict_init(dir, argv[1], fn, line); if (rcode == -2) continue; @@ -2783,45 +2804,72 @@ int dict_init(char const *dir, char const *fn) return 0; } -static size_t print_attr_oid(char *buffer, size_t size, unsigned int attr, - int dv_type) +static size_t print_attr_oid(char *buffer, size_t bufsize, unsigned int attr, unsigned int vendor) { - int nest; - size_t outlen; + int nest, dv_type = 1; size_t len; + char *p = buffer; + + if (vendor > FR_MAX_VENDOR) { + len = snprintf(p, bufsize, "%u.", vendor / FR_MAX_VENDOR); + p += len; + bufsize -= len; + vendor &= (FR_MAX_VENDOR) - 1; + } + + if (vendor) { + DICT_VENDOR *dv; + + /* + * dv_type is the length of the vendor's type field + * RFC 2865 never defined a mandatory length, so + * different vendors have different length type fields. + */ + dv = dict_vendorbyvalue(vendor); + if (dv) dv_type = dv->type; + + len = snprintf(p, bufsize, "26.%u.", vendor); + + p += len; + bufsize -= len; + } + switch (dv_type) { default: case 1: - len = snprintf(buffer, size, "%u", attr & 0xff); + len = snprintf(p, bufsize, "%u", attr & 0xff); + p += len; + bufsize -= len; + if ((attr >> 8) == 0) return p - buffer; break; - case 4: - return snprintf(buffer, size, "%u", attr); - case 2: - return snprintf(buffer, size, "%u", attr & 0xffff); - - } + len = snprintf(p, bufsize, "%u", attr & 0xffff); + p += len; + return p - buffer; - if ((attr >> 8) == 0) return len; + case 4: + len = snprintf(p, bufsize, "%u", attr); + p += len; + return p - buffer; - outlen = len; - buffer += len; - size -= len; + } + /* + * "attr" is a sequence of packed numbers. Unpack them. + */ for (nest = 1; nest <= fr_attr_max_tlv; nest++) { if (((attr >> fr_attr_shift[nest]) & fr_attr_mask[nest]) == 0) break; - len = snprintf(buffer, size, ".%u", + len = snprintf(p, bufsize, ".%u", (attr >> fr_attr_shift[nest]) & fr_attr_mask[nest]); - outlen = len; - buffer += len; - size -= len; + p += len; + bufsize -= len; } - return outlen; + return p - buffer; } /** Free dynamically allocated (unknown attributes) @@ -2862,7 +2910,6 @@ void dict_attr_free(DICT_ATTR const **da) int dict_unknown_from_fields(DICT_ATTR *da, unsigned int attr, unsigned int vendor) { char *p; - int dv_type = 1; size_t len = 0; size_t bufsize = DICT_ATTR_MAX_NAME_LEN; @@ -2888,32 +2935,7 @@ int dict_unknown_from_fields(DICT_ATTR *da, unsigned int attr, unsigned int vend p += len; bufsize -= len; - if (vendor > FR_MAX_VENDOR) { - len = snprintf(p, bufsize, "%u.", vendor / FR_MAX_VENDOR); - p += len; - bufsize -= len; - vendor &= (FR_MAX_VENDOR) - 1; - } - - if (vendor) { - DICT_VENDOR *dv; - - /* - * dv_type is the length of the vendor's type field - * RFC 2865 never defined a mandatory length, so - * different vendors have different length type fields. - */ - dv = dict_vendorbyvalue(vendor); - if (dv) { - dv_type = dv->type; - } - len = snprintf(p, bufsize, "26.%u.", vendor); - - p += len; - bufsize -= len; - } - - print_attr_oid(p, bufsize , attr, dv_type); + print_attr_oid(p, bufsize , attr, vendor); return 0; } @@ -3270,7 +3292,15 @@ DICT_ATTR const *dict_attrbyname(char const *name) da = (DICT_ATTR *) buffer; strlcpy(da->name, name, DICT_ATTR_MAX_NAME_LEN + 1); - return fr_hash_table_finddata(attributes_byname, da); + da = fr_hash_table_finddata(attributes_byname, da); + if (!da) return NULL; + + if (!da->flags.is_dup) return da; + + /* + * This MUST exist if the dup flag is set. + */ + return dict_attrbyvalue(da->attr, da->vendor); } /** Look up a dictionary attribute by name embedded in another string @@ -3464,3 +3494,13 @@ DICT_ATTR const *dict_unknown_add(DICT_ATTR const *old) da = dict_attrbyvalue(old->attr, old->vendor); return da; } + +size_t dict_print_oid(char *buffer, size_t buflen, DICT_ATTR const *da) +{ + return print_attr_oid(buffer, buflen, da->attr, da->vendor); +} + +int dict_walk(fr_hash_table_walk_t callback, void *context) +{ + return fr_hash_table_walk(attributes_byname, callback, context); +} diff --git a/src/lib/hmacmd5.c b/src/lib/hmacmd5.c index 1cca00fa2a..2aad490e30 100644 --- a/src/lib/hmacmd5.c +++ b/src/lib/hmacmd5.c @@ -34,8 +34,9 @@ RCSID("$Id$") #include #include +#include -#ifdef HAVE_OPENSSL_EVP_H +#if defined(HAVE_OPENSSL_EVP_H) && !defined(WITH_FIPS) /** Calculate HMAC using OpenSSL's MD5 implementation * * @param digest Caller digest to be filled in. @@ -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..f8b2edbecc 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); } } } @@ -87,14 +136,24 @@ void cbtls_msg(int write_p, int msg_version, int content_type, * content types. Which breaks our tracking of * the SSL Session state. */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L if ((msg_version == 0) && (content_type > UINT8_MAX)) { - DEBUG4("Ignoring cbtls_msg call with pseudo content type %i, version %i", +#else + /* + * "...we do not see the need to resolve application breakage + * just because the documentation now is incorrect." + * + * https://github.com/openssl/openssl/issues/17262 + */ + if ((content_type > UINT8_MAX) && (content_type != SSL3_RT_INNER_CONTENT_TYPE)) { +#endif + 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 +163,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) @@ -111,7 +189,6 @@ void cbtls_msg(int write_p, int msg_version, int content_type, state->info.origin = write_p; state->info.content_type = content_type; state->info.record_len = len; - state->info.version = msg_version; state->info.initialized = true; if (content_type == SSL3_RT_ALERT) { @@ -124,6 +201,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 +224,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/radclient.c b/src/main/radclient.c index 52d2872b13..09d27c8711 100644 --- a/src/main/radclient.c +++ b/src/main/radclient.c @@ -28,6 +28,10 @@ RCSID("$Id$") #include #include #include +#ifdef HAVE_OPENSSL_SSL_H +#include +#include +#endif #include #ifdef HAVE_GETOPT_H @@ -36,6 +40,8 @@ RCSID("$Id$") #include +USES_APPLE_DEPRECATED_API + typedef struct REQUEST REQUEST; /* to shut up warnings about mschap.h */ #include "smbdes.h" @@ -155,9 +161,60 @@ static int _rc_request_free(rc_request_t *request) return 0; } +#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L +# include + +static OSSL_PROVIDER *openssl_default_provider = NULL; +static OSSL_PROVIDER *openssl_legacy_provider = NULL; + +static int openssl3_init(void) +{ + /* + * 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; + } + + return 0; +} + +static void openssl3_free(void) +{ + 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; +} +#else +#define openssl3_init() +#define openssl3_free() +#endif + + + static int mschapv1_encode(RADIUS_PACKET *packet, VALUE_PAIR **request, char const *password) { + int rcode; unsigned int i; uint8_t *p; VALUE_PAIR *challenge, *reply; @@ -190,9 +247,8 @@ static int mschapv1_encode(RADIUS_PACKET *packet, VALUE_PAIR **request, p[1] = 0x01; /* NT hash */ - if (mschap_ntpwdhash(nthash, password) < 0) { - return 0; - } + rcode = mschap_ntpwdhash(nthash, password); + if (rcode < 0) return 0; smbdes_mschap(nthash, challenge->vp_octets, p + 26); return 1; @@ -960,8 +1016,8 @@ static int send_one_packet(rc_request_t *request) */ fr_packet_list_yank(pl, request->packet); - REDEBUG("No reply from server for ID %d socket %d", - request->packet->id, request->packet->sockfd); + RDEBUG("No reply from server for ID %d socket %d", + request->packet->id, request->packet->sockfd); deallocate_id(request); /* @@ -1197,9 +1253,11 @@ int main(int argc, char **argv) break; case 'c': - if (!isdigit((int) *optarg)) - usage(); + if (!isdigit((int) *optarg)) usage(); + resend_count = atoi(optarg); + + if (resend_count < 1) usage(); break; case 'D': @@ -1421,6 +1479,8 @@ int main(int argc, char **argv) exit(1); } + openssl3_init(); + /* * Bind to the first specified IP address and port. * This means we ignore later ones. @@ -1637,5 +1697,8 @@ int main(int argc, char **argv) if ((stats.lost > 0) || (stats.failed > 0)) { exit(1); } + + openssl3_free(); + exit(0); } diff --git a/src/main/tls.c b/src/main/tls.c index 78c7370a63..118978b52a 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,25 @@ 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) + +#define FIPS_mode(_x) EVP_default_properties_is_fips_enabled(NULL) +#define PEM_read_bio_DHparams(_bio, _x, _y, _z) PEM_read_bio_Parameters(_bio, &dh) +#define SSL_CTX_set0_tmp_dh_pkey(_ctx, _dh) SSL_CTX_set_tmp_dh(_ctx, _dh) +#define DH EVP_PKEY +#define DH_free(_dh) +#endif + #ifdef ENABLE_OPENSSL_VERSION_CHECK typedef struct libssl_defect { uint64_t high; @@ -153,6 +175,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 +218,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 +232,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 +244,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 +336,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 +350,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 +403,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 +413,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 +423,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 +446,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 +502,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 +517,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 +577,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); @@ -516,6 +588,9 @@ tls_session_t *tls_new_client_session(TALLOC_CTX *ctx, fr_tls_server_conf_t *con } request = request_alloc(ssn); + request->packet = rad_alloc(request, false); + request->reply = rad_alloc(request, false); + SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_REQUEST, (void *)request); /* @@ -537,15 +612,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 +629,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 +649,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 +711,35 @@ 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 either no standard for using this EAP method with TLS 1.3,"); + WARN("!! or FreeRADIUS does not fully support TLS 1.3 for this EAP method."); + WARN("!!"); + WARN("!! This message can be removed by setting tls_max_version = \"1.2\""); + 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 +747,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 +786,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 +874,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 +898,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 +956,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,24 +978,26 @@ 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) { + switch (SSL_version(ssn->ssl)) { case SSL2_VERSION: str_version = "SSL 2.0"; break; @@ -767,13 +1031,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 +1057,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,25 +1071,25 @@ 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."); - /* Its clean application data, do whatever we want */ + RDEBUG2("(TLS) Application data."); + /* Its clean application data, leave whatever is in the buffer */ +#if 0 record_init(&ssn->clean_out); +#endif } /* We are done with dirty_in, reinitialize it */ @@ -855,13 +1121,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 +1228,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,9 +1240,20 @@ 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) { + switch (SSL_version(tls_session->ssl)) { case SSL2_VERSION: str_version = "SSL 2.0 "; break; @@ -1001,13 +1280,12 @@ void tls_session_information(tls_session_t *tls_session) #endif default: - sprintf(buffer, "UNKNOWN TLS VERSION '%04X'", tls_session->info.version); + sprintf(buffer, "UNKNOWN TLS VERSION '%04X'", SSL_version(tls_session->ssl)); str_version = buffer; 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 +1304,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 +1324,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 +1356,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 +1392,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 +1413,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 ((SSL_version(tls_session->ssl) > 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 +1442,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 +1519,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 +1558,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 +1651,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 +1659,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 +1681,19 @@ 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 + }, - { "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 +1722,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,9 +1742,19 @@ 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), "1.0" }, + { "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 + }, + + { "hostname", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, client_hostname), NULL }, CONF_PARSER_TERMINATOR }; @@ -1347,7 +1768,44 @@ static int load_dh_params(SSL_CTX *ctx, char *file) DH *dh = NULL; BIO *bio; - 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."); + file = NULL; + } + + /* + * No dh file, set auto context. + */ + if (!file) { + if (!SSL_CTX_set_dh_auto(ctx, 1)) { + ERROR(LOG_PREFIX ": Unable to set DH parameters"); + return -1; + } + + return 0; + } + + WARN(LOG_PREFIX ": Setting DH parameters from %s - this is no longer necessary.", file); + WARN(LOG_PREFIX ": You should comment out the 'dh_file' configuration item."); + +#else + if (!file) { + WARN(LOG_PREFIX ": Cannot set DH parameters. DH cipher suites may not work."); + return 0; + } +#endif + if ((bio = BIO_new_file(file, "r")) == NULL) { ERROR(LOG_PREFIX ": Unable to open DH file - %s", file); @@ -1422,7 +1880,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 +1897,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 +1905,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 +1921,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 +1944,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 +1952,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 +2053,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 +2075,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 +2098,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 +2122,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 +2132,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 +2166,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,34 +2206,378 @@ 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; + p = SSL_SESSION_get_id(ssn, &size); + if (size > bufsize) size = bufsize; + + memcpy(buffer, p, size); + return size; +#endif +} + +/* + * 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; @@ -1811,7 +2628,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 +2653,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 +2662,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 +2682,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 +2855,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 +2871,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 +2904,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 +3004,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 +3149,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 +3232,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 +3272,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 +3305,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 +3466,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 +3527,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 (!ecdh_curve || !*ecdh_curve) return 0; - - nid = OBJ_sn2nid(ecdh_curve); - if (!nid) { - ERROR(LOG_PREFIX ": Unknown ecdh_curve \"%s\"", ecdh_curve); - return -1; + if (!disable_single_dh_use) { + SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE); } - ecdh = EC_KEY_new_by_curve_name(nid); - if (!ecdh) { - ERROR(LOG_PREFIX ": Unable to create new curve \"%s\"", ecdh_curve); - return -1; - } + if (!ecdh_curve) return 0; - SSL_CTX_set_tmp_ecdh(ctx, ecdh); +#if OPENSSL_VERSION_NUMBER >= 0x1000200fL + /* + * A colon-separated list of curves. + */ + if (*ecdh_curve) { + char *list; - if (!disable_single_dh_use) { - SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE); + 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; + } } - EC_KEY_free(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); + + EC_KEY_free(ecdh); + } +#endif return 0; } @@ -2708,10 +3610,32 @@ 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 +3701,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 +3734,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 +3750,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 +3774,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 +3967,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 +4038,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 +4057,250 @@ 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); + ctx_available |= SSL_OP_NO_TLSv1_1; #endif -#ifdef SSL_OP_NO_TLSv1_1 - insecure_tls_version |= (conf->disable_tlsv1_1 == false); -#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. + * Set the cipher list if we were told to do so. We do + * this before setting min/max TLS version. In a sane + * world, OpenSSL would error out if we set the max TLS + * version to something which was unsupported by the + * current security level. However, this is OpenSSL. If + * you set conflicting options, it doesn't give an error. + * Instead, it just picks something to do. */ -#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 (conf->cipher_list) { + if (!SSL_CTX_set_cipher_list(ctx, conf->cipher_list)) { + tls_error_log(NULL, "Failed setting cipher list"); + return NULL; + } } - 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"); + /* + * 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. + */ + if (min_version <= TLS1_1_VERSION) { +#if OPENSSL_VERSION_NUMBER >= 0x10101000L + int seclevel = SSL_CTX_get_security_level(ctx); + int required;; + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + required = 0; +#else + required = 1; #endif - } - ctx_tls_versions |= SSL_OP_NO_TLSv1_1; + if (seclevel != required) { + WARN(LOG_PREFIX ": In order to use TLS 1.0 and/or TLS 1.1, you likely need to set: cipher_list = \"DEFAULT@SECLEVEL=%d\"", required); + } + +#else + /* + * No API to get the security level. Just guess based on the string in the cipher_list. + */ + if (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\""); + } #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 + 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'"); + } + if (conf->disable_tlsv1_2) { + 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 +4335,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 +4393,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 +4425,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); @@ -3389,16 +4466,6 @@ post_ca: } #endif - /* - * Set the cipher list if we were told to - */ - if (conf->cipher_list) { - if (!SSL_CTX_set_cipher_list(ctx, conf->cipher_list)) { - tls_error_log(NULL, "Failed setting cipher list"); - return NULL; - } - } - /* * Setup session caching */ @@ -3424,9 +4491,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 +4535,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 +4576,109 @@ 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: + if (dir) closedir(dir); + 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; + } + } + + conf->realms = ht; + closedir(dir); + + return 0; +} + + fr_tls_server_conf_t *tls_server_conf_parse(CONF_SECTION *cs) { fr_tls_server_conf_t *conf; @@ -3535,6 +4706,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 +4744,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 +4819,11 @@ 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*/ + { char *dh_file; @@ -3655,7 +4842,7 @@ skip_list: } if (conf->verify_client_cert_cmd && !conf->verify_tmp_dir) { - ERROR(LOG_PREFIX ": You MUST set the verify directory in order to use verify_client_cmd"); + ERROR(LOG_PREFIX ": You MUST set the 'tmpdir' directory in order to use '%s' cmd", conf->verify_client_cert_cmd); goto error; } @@ -3663,12 +4850,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,7 +4895,7 @@ 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; } @@ -3755,7 +4947,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 +4955,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 +4972,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 +5033,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 +5055,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 +5086,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 +5102,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 (SSL_version(ssn->ssl) == 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 +5134,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. */ @@ -3953,49 +5170,69 @@ fr_tls_status_t tls_application_data(tls_session_t *ssn, REQUEST *request) 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); record_init(&ssn->dirty_in); - RDEBUG("Failed writing %zd bytes to SSL BIO: %d", ssn->dirty_in.used, err); return FR_TLS_FAIL; } + + record_init(&ssn->dirty_in); } /* - * Clear the dirty buffer now that we are done with it - * and init the clean_out buffer to store decrypted data + * tls_handshake_recv() may read application data. So + * don't touch clean_out. But only if the BIO_write() + * above didn't do anything. */ - record_init(&ssn->dirty_in); - record_init(&ssn->clean_out); + else if (ssn->clean_out.used > 0) { + RDEBUG("(TLS) We already have %zd bytes of application data, processing it.", + (ssn->clean_out.used)); + goto add_certs; + } /* * Read (and decrypt) the tunneled data from the * SSL session, and put it into the decrypted * data buffer. */ - err = SSL_read(ssn->ssl, ssn->clean_out.data, sizeof(ssn->clean_out.data)); + err = SSL_read(ssn->ssl, ssn->clean_out.data + ssn->clean_out.used, + sizeof(ssn->clean_out.data) - ssn->clean_out.used); if (err <= 0) { int code; - RDEBUG("SSL_read Error"); + RDEBUG3("(TLS) 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"); + if (ssn->clean_out.used > 0) { /* just process what application data we have */ + err = 0; + break; + } + + 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"); + if (ssn->clean_out.used > 0) { /* just process what application data we have */ + err = 0; + break; + } + + REDEBUG("(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; } } @@ -4003,8 +5240,9 @@ fr_tls_status_t tls_application_data(tls_session_t *ssn, REQUEST *request) /* * Passed all checks, successfully decrypted data */ - ssn->clean_out.used = err; + ssn->clean_out.used += err; +add_certs: /* * Add the certificates to intermediate packets, so that * the inner tunnel policies can use them. @@ -4026,27 +5264,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 +5295,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 +5308,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..c3c40d17ea 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,55 @@ 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. + * If SSL handshake still isn't finished, then there + * is more data to read. Release the mutex and + * return so this function will be called again + */ + if (!SSL_is_init_finished(sock->ssn->ssl)) { + PTHREAD_MUTEX_UNLOCK(&sock->mutex); + return 0; + } + } + + /* + * 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 +318,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 +330,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 +351,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 +366,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 +380,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 +388,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 +424,7 @@ redo: rad_assert(client != NULL); packet = talloc_steal(NULL, sock->packet); + sock->request->packet = NULL; sock->packet = NULL; /* @@ -391,8 +457,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 +489,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 +516,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 +539,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 +839,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 +886,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..2f37663df1 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 (SSL_version(tls_session->ssl)) { +#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 ((SSL_version(tls_session->ssl) == 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..385441c62f 100644 --- a/src/modules/rlm_eap/libeap/mppe_keys.c +++ b/src/modules/rlm_eap/libeap/mppe_keys.c @@ -26,11 +26,16 @@ RCSID("$Id$") USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ #include "eap_tls.h" +#include #include +#include +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include +#endif /* - * 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,23 +43,18 @@ 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(); -#ifdef EVP_MD_CTX_FLAG_NON_FIPS_ALLOW - HMAC_CTX_set_flags(ctx_a, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); - HMAC_CTX_set_flags(ctx_out, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); -#endif 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,82 @@ 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); + + EVP_MD const *md5 = NULL; + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MD *md5_to_free = NULL; + + /* + * If we are using OpenSSL >= 3.0 and FIPS mode is + * enabled, we need to load the default provider in a + * standalone context in order to access MD5. + */ + OSSL_LIB_CTX *libctx = NULL; + OSSL_PROVIDER *default_provider = NULL; + + if (EVP_default_properties_is_fips_enabled(NULL)) { + libctx = OSSL_LIB_CTX_new(); + default_provider = OSSL_PROVIDER_load(libctx, "default"); + + if (!default_provider) { + ERROR("Failed loading OpenSSL default provider."); + return; + } + + md5_to_free = EVP_MD_fetch(libctx, "MD5", NULL); + if (!md5_to_free) { + ERROR("Failed loading OpenSSL MD5 function."); + return; + } + + md5 = md5_to_free; + } else { + md5 = EVP_md5(); + } +#else + md5 = EVP_md5(); +#endif + + P_hash(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]; + } + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + if (libctx) { + OSSL_PROVIDER_unload(default_provider); + OSSL_LIB_CTX_free(libctx); + EVP_MD_free(md5_to_free); + } +#endif +} + +/* + * 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 +207,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 +269,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 +280,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 +301,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 +322,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 (SSL_version(tls_session->ssl)) { + 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 +357,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 +376,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..bbb5a03c95 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, SSL_version(tls_session->ssl), 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..093dc868cd 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,13 @@ 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. We haven't implemented the necessary + * changes, so we don't allow it. + */ + handler->opaque = tls_session = eaptls_session(handler, inst->tls_conf, client_cert, false); if (!tls_session) return 0; @@ -566,16 +591,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 @@ -599,6 +628,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) rcode = eap_fast_tls_start(handler->eap_ds, tls_session); if (rcode < 0) { + error: talloc_free(tls_session); return 0; } @@ -607,7 +637,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) if (!SSL_set_session_ticket_ext_cb(tls_session->ssl, _session_ticket, tls_session)) { RERROR("Failed setting SSL session ticket callback"); - return 0; + goto error; } handler->stage = PROCESS; 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..d9f850cef2 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 @@ -192,7 +192,10 @@ 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); + /* + * Allow TLS 1.3, it works. + */ + ssn = eaptls_session(handler, inst->tls_conf, client_cert, true); if (!ssn) { return 0; } @@ -200,9 +203,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 +237,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 +281,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 +318,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..26260527a5 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,100 +27,237 @@ * 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; + } + + 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); - HMAC_Final(ctx, digest, &mdlen); + 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; - HMAC_CTX *ctx = NULL; - uint8_t pwe_digest[SHA256_DIGEST_LENGTH], *prfbuf = NULL, ctr; - int nid, is_odd, primebitlen, primebytelen, ret = 0; - - ctx = HMAC_CTX_new(); - if (ctx == NULL) { - DEBUG("failed allocating HMAC context"); - goto fail; - } + BIGNUM *x_candidate = NULL, *rnd = NULL, *y_sqrd = NULL, *qr = NULL, *qnr = NULL, *y1 = NULL, *y2 = NULL, *y = NULL, *exp = NULL; + EVP_MD_CTX *hmac_ctx; + EVP_PKEY *hmac_pkey; + uint8_t pwe_digest[SHA256_DIGEST_LENGTH], *prfbuf = NULL, *xbuf = NULL, *pm1buf = NULL, *y1buf = NULL, *y2buf = NULL, *ybuf = 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; + + MEM(hmac_ctx = EVP_MD_CTX_new()); + MEM(hmac_pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, allzero, sizeof(allzero))); switch (grp_num) { /* from IANA registry for IKE D-H groups */ case 19: @@ -159,17 +294,23 @@ 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) || + ((y1 = consttime_BN()) == NULL) || + ((y2 = consttime_BN()) == NULL) || + ((y = consttime_BN()) == NULL) || + ((exp = 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 +320,80 @@ 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; + } + if ((y1buf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) { + DEBUG("unable to alloc space for y1 buffer"); + goto fail; + } + if ((y2buf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) { + DEBUG("unable to alloc space for y2 buffer"); + goto fail; + } + if ((ybuf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) { + DEBUG("unable to alloc space for y 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); + EVP_DigestSignInit(hmac_ctx, NULL, EVP_sha256(), NULL, hmac_pkey); + EVP_DigestSignUpdate(hmac_ctx, (uint8_t *)token, sizeof(*token)); + EVP_DigestSignUpdate(hmac_ctx, (uint8_t const *)id_peer, id_peer_len); + EVP_DigestSignUpdate(hmac_ctx, (uint8_t const *)id_server, id_server_len); + EVP_DigestSignUpdate(hmac_ctx, (uint8_t const *)password, password_len); + EVP_DigestSignUpdate(hmac_ctx, (uint8_t *)&ctr, sizeof(ctr)); + + { + size_t mdlen = SHA256_DIGEST_LENGTH; + + EVP_DigestSignFinal(hmac_ctx, pwe_digest, &mdlen); + EVP_MD_CTX_reset(hmac_ctx); + } 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 +401,86 @@ 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... - */ - is_odd = BN_is_odd(rnd) ? 1 : 0; + * 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); /* - * 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; - } + * need to unambiguously identify the solution, if there is + * one.. + */ + is_odd = BN_is_odd(rnd); /* - * 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; - } + * 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 (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 the candidate >= prime then we want to skip it + */ + qr_or_qnr = const_time_select(skip, 0, qr_or_qnr); - 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; + /* + * 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); + + /* + * 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); + do_equation(session->group, y_sqrd, x_candidate, session->bnctx); + if ( !BN_add(exp, session->prime, BN_value_one()) || + !BN_rshift(exp, exp, 2) || + !BN_mod_exp_mont_consttime(y1, y_sqrd, exp, session->prime, session->bnctx, NULL) || + !BN_sub(y2, session->prime, y1) || + !BN_bn2bin(y1, y1buf) || + !BN_bn2bin(y2, y2buf)) { + DEBUG("unable to compute y"); + goto fail; + } + mask = const_time_eq(save_is_odd, BN_is_odd(y1)); + const_time_select_bin(mask, y1buf, y2buf, primebytelen, ybuf); + if (BN_bin2bn(ybuf, primebytelen, y) == NULL || + !EC_POINT_set_affine_coordinates(session->group, session->pwe, x_candidate, y, session->bnctx)) { + DEBUG("unable to set point coordinate"); + goto fail; } session->group_num = grp_num; @@ -278,78 +490,89 @@ 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); - HMAC_CTX_free(ctx); + BN_clear_free(y1); + BN_clear_free(y2); + BN_clear_free(y); + BN_clear_free(exp); + + if (prfbuf) talloc_free(prfbuf); + if (xbuf) talloc_free(xbuf); + if (pm1buf) talloc_free(pm1buf); + if (y1buf) talloc_free(y1buf); + if (y2buf) talloc_free(y2buf); + if (ybuf) talloc_free(ybuf); + + EVP_MD_CTX_free(hmac_ctx); + EVP_PKEY_free(hmac_pkey); 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 +584,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 +600,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 +659,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 +676,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 +708,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 +733,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 +759,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 +808,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 +834,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 +859,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..4e53c92244 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 @@ -181,7 +181,10 @@ 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); + /* + * Allow TLS 1.3, it works. + */ + ssn = eaptls_session(handler, inst->tls_conf, client_cert, true); if (!ssn) { return 0; } @@ -189,9 +192,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 +208,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 +245,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 +349,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..29e7cfd757 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" @@ -265,6 +266,9 @@ typedef struct ldap_instance { int tls_require_cert; //!< OpenLDAP constant representing the require cert string. + char const *tls_min_version_str; //!< Minimum TLS version + int tls_min_version; + /* * Options */ diff --git a/src/modules/rlm_mschap/rlm_mschap.c b/src/modules/rlm_mschap/rlm_mschap.c index 83c84a63e2..25bcd9c44e 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,47 @@ 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"); + error: + if (ctx) EVP_CIPHER_CTX_free(ctx); + return -1; + } + + if (!EVP_CIPHER_CTX_set_key_length(ctx, nt_password->vp_length)) { + REDEBUG("Failed setting key length"); + goto error; + } + + if (!EVP_EncryptInit_ex(ctx, EVP_rc4(), NULL, nt_password->vp_octets, NULL)) { + REDEBUG("Failed setting key value"); + goto error;; + } + + if (!EVP_EncryptUpdate(ctx, nt_pass_decrypted, &ntlen, new_nt_password, ntlen)) { + REDEBUG("Failed getting output"); + goto error; + } + + 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' + } +} +