5e110dfcd8
These options are lefotvers from before the OpenSSL3 support backport. They do not harm FreeRADIUS functioning but print warnings on server startup. Resolves: RHEL-30830 Signed-off-by: Antonio Torres <antorres@redhat.com>
11717 lines
376 KiB
Diff
11717 lines
376 KiB
Diff
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>
|
|
|
|
[antorres@redhat.com]: these changes include the macro WITH_FIPS, which allows FreeRADIUS
|
|
to work on top of OpenSSL 3.0 when the system is in FIPS mode. We enable this macro on the specfile.
|
|
[antorres@redhat.com]: backported tls.c, tls-h changes from 3.2.x branch.
|
|
[antorres@redhat.com]: the sites-available/tls file has been modified to add the fix_cert_order option.
|
|
[antorres@redhat.com]: mods-available/eap has been modified to comment out 'disable_tlsv1' and 'dh_file' options.
|
|
---
|
|
raddb/mods-available/eap | 6 +-
|
|
raddb/sites-available/tls | 8 +
|
|
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 | 45 +-
|
|
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 | 2012 ++++++++++++++++----
|
|
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_radstate.c | 3 +-
|
|
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 +
|
|
57 files changed, 6032 insertions(+), 1199 deletions(-)
|
|
|
|
diff --git a/raddb/mods-available/eap b/raddb/mods-available/eap
|
|
index a89a783663..bf73485e3c 100644
|
|
--- a/raddb/mods-available/eap
|
|
+++ b/raddb/mods-available/eap
|
|
@@ -281,7 +281,7 @@ eap {
|
|
#
|
|
# openssl dhparam -out certs/dh 2048
|
|
#
|
|
- dh_file = ${certdir}/dh
|
|
+ # dh_file = ${certdir}/dh
|
|
|
|
# If your system doesn't have /dev/urandom,
|
|
# you will need to create this file, and
|
|
@@ -392,8 +392,8 @@ eap {
|
|
# tls_max_version.
|
|
#
|
|
# disable_tlsv1_2 = no
|
|
- disable_tlsv1_1 = yes
|
|
- disable_tlsv1 = yes
|
|
+ # disable_tlsv1_1 = yes
|
|
+ # disable_tlsv1 = yes
|
|
|
|
# Set min / max TLS version. Mainly for Debian
|
|
# "trusty", which disables older versions of TLS, and
|
|
diff --git a/raddb/sites-available/tls b/raddb/sites-available/tls
|
|
index e2a3b080ca..25a10b6364 100644
|
|
--- a/raddb/sites-available/tls
|
|
+++ b/raddb/sites-available/tls
|
|
@@ -468,6 +468,14 @@ home_server tls {
|
|
# this configuration item.
|
|
ca_file = ${cadir}/ca.pem
|
|
|
|
+ # In previous versions, outbound RadSec connections
|
|
+ # would put the home server certificate into the
|
|
+ # TLS-Client-Cert* attributes. Set this configuration
|
|
+ # item to "yes" in order to have the home server
|
|
+ # certificates placed into the "TLS-Cert-*" attributes.
|
|
+ #
|
|
+# fix_cert_order = yes
|
|
+
|
|
#
|
|
# For TLS-PSK, the key should be specified
|
|
# dynamically, instead of using a hard-coded
|
|
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 <talloc.h>
|
|
+#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 <string.h>
|
|
|
|
+#ifdef WITH_FIPS
|
|
+#undef HAVE_OPENSSL_MD4_H
|
|
+#endif
|
|
+
|
|
#ifdef HAVE_OPENSSL_MD4_H
|
|
# include <openssl/md4.h>
|
|
#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 <openssl/evp.h>
|
|
+
|
|
+/*
|
|
+ * 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 <string.h>
|
|
|
|
+#ifdef WITH_FIPS
|
|
+#undef HAVE_OPENSSL_MD5_H
|
|
+#endif
|
|
+
|
|
#ifdef HAVE_OPENSSL_MD5_H
|
|
# include <openssl/md5.h>
|
|
#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 <openssl/evp.h>
|
|
+
|
|
+/*
|
|
+ * 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 <string.h>
|
|
+#include <openssl/evp.h>
|
|
+#include <openssl/core_names.h>
|
|
+
|
|
+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..4bf1665483 100644
|
|
--- a/src/include/tls-h
|
|
+++ b/src/include/tls-h
|
|
@@ -67,7 +67,7 @@ typedef enum {
|
|
} fr_tls_status_t;
|
|
extern FR_NAME_NUMBER const fr_tls_status_table[];
|
|
|
|
-#define MAX_RECORD_SIZE 16384
|
|
+#define MAX_RECORD_SIZE 65536
|
|
|
|
/*
|
|
* A single TLS record may be up to 16384 octets in length, but a
|
|
@@ -89,12 +89,12 @@ extern FR_NAME_NUMBER const fr_tls_status_table[];
|
|
* or configure TLS not to exceed MAX_RECORD_SIZE.
|
|
*/
|
|
typedef struct _record_t {
|
|
- uint8_t data[MAX_RECORD_SIZE];
|
|
size_t used;
|
|
+ uint8_t data[MAX_RECORD_SIZE];
|
|
} 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
|
|
@@ -335,6 +341,7 @@ fr_tls_status_t tls_application_data(tls_session_t *ssn, REQUEST *request);
|
|
#define FR_TLS_EX_INDEX_STORE (14)
|
|
#define FR_TLS_EX_INDEX_SSN (15)
|
|
#define FR_TLS_EX_INDEX_TALLOC (16)
|
|
+#define FR_TLS_EX_INDEX_FIX_CERT_ORDER (17)
|
|
|
|
extern int fr_tls_ex_index_certs;
|
|
extern int fr_tls_ex_index_vps;
|
|
@@ -360,6 +367,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 +382,21 @@ 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;
|
|
+ char const *sigalgs_list;
|
|
|
|
bool session_cache_enable;
|
|
- uint32_t session_timeout;
|
|
+ uint32_t session_lifetime;
|
|
uint32_t session_cache_size;
|
|
char const *session_id_name;
|
|
char const *session_cache_path;
|
|
+ char const *session_cache_server;
|
|
fr_hash_table_t *cache_ht;
|
|
char session_context_id[SSL_MAX_SSL_SESSION_ID_LENGTH];
|
|
|
|
@@ -389,6 +405,10 @@ struct fr_tls_server_conf_t {
|
|
char const *verify_client_cert_cmd;
|
|
bool require_client_cert;
|
|
|
|
+ bool fix_cert_order;
|
|
+
|
|
+ pthread_mutex_t mutex;
|
|
+
|
|
#ifdef HAVE_OPENSSL_OCSP_H
|
|
/*
|
|
* OCSP Configuration
|
|
@@ -414,6 +434,15 @@ 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 WITH_RADIUSV11
|
|
+ char const *radiusv11_name;
|
|
+ fr_radiusv11_t radiusv11;
|
|
+#endif
|
|
};
|
|
|
|
#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 <freeradius-devel/libradius.h>
|
|
#include <freeradius-devel/md5.h>
|
|
+#include <freeradius-devel/openssl3.h>
|
|
|
|
-#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 <openssl/hmac.h>
|
|
#include <openssl/evp.h>
|
|
+#include <freeradius-devel/openssl3.h>
|
|
#endif
|
|
|
|
#include <freeradius-devel/libradius.h>
|
|
@@ -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 <freeradius-devel/libradius.h>
|
|
|
|
#include <freeradius-devel/md5.h>
|
|
+#include <freeradius-devel/rfc4849.h>
|
|
|
|
#include <fcntl.h>
|
|
#include <ctype.h>
|
|
@@ -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 : "<none>";
|
|
|
|
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 <freeradius-devel/radpaths.h>
|
|
#include <freeradius-devel/udpfromto.h>
|
|
#include <freeradius-devel/conf.h>
|
|
+#ifdef HAVE_OPENSSL_SSL_H
|
|
+#include <openssl/ssl.h>
|
|
+#include <freeradius-devel/openssl3.h>
|
|
+#endif
|
|
#include <ctype.h>
|
|
|
|
#ifdef HAVE_GETOPT_H
|
|
@@ -36,6 +40,8 @@ RCSID("$Id$")
|
|
|
|
#include <assert.h>
|
|
|
|
+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 <openssl/provider.h>
|
|
+
|
|
+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..338ccd6446 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 <freeradius-devel/radiusd.h>
|
|
#include <freeradius-devel/process.h>
|
|
+#include <freeradius-devel/modules.h>
|
|
#include <freeradius-devel/rad_assert.h>
|
|
|
|
#ifdef HAVE_SYS_STAT_H
|
|
@@ -37,6 +38,10 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
|
|
#include <fcntl.h>
|
|
#endif
|
|
|
|
+#ifdef HAVE_DIRENT_H
|
|
+#include <dirent.h>
|
|
+#endif
|
|
+
|
|
#ifdef HAVE_UTIME_H
|
|
#include <utime.h>
|
|
#endif
|
|
@@ -56,8 +61,25 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */
|
|
# endif
|
|
# include <openssl/ssl.h>
|
|
|
|
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
|
|
+# include <openssl/provider.h>
|
|
+
|
|
+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;
|
|
}
|
|
|
|
@@ -338,7 +365,7 @@ static bool identity_is_safe(const char *identity)
|
|
if (!identity) return true;
|
|
|
|
while ((c = *(identity++)) != '\0') {
|
|
- if (isalpha((int) c) || isdigit((int) c) || isspace((int) c) ||
|
|
+ if (isalpha((uint8_t) c) || isdigit((uint8_t) c) || isspace((uint8_t) c) ||
|
|
(c == '@') || (c == '-') || (c == '_') || (c == '.')) {
|
|
continue;
|
|
}
|
|
@@ -369,24 +396,32 @@ static unsigned int psk_server_callback(SSL *ssl, const char *identity,
|
|
FR_TLS_EX_INDEX_REQUEST);
|
|
if (request && conf->psk_query) {
|
|
size_t hex_len;
|
|
- VALUE_PAIR *vp;
|
|
+ VALUE_PAIR *vp, **certs;
|
|
+ TALLOC_CTX *talloc_ctx;
|
|
char buffer[2 * PSK_MAX_PSK_LEN + 4]; /* allow for too-long keys */
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
vp = pair_make_request("TLS-PSK-Identity", identity, T_OP_SET);
|
|
if (!vp) return 0;
|
|
|
|
+ certs = (VALUE_PAIR **)SSL_get_ex_data(ssl, fr_tls_ex_index_certs);
|
|
+ talloc_ctx = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_TALLOC);
|
|
+ fr_assert(certs != NULL); /* pointer to sock->certs */
|
|
+ fr_assert(talloc_ctx != NULL); /* sock */
|
|
+
|
|
+ fr_pair_add(certs, fr_pair_copy(talloc_ctx, vp));
|
|
+
|
|
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 +431,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 +454,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 +510,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 +525,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 +585,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,8 +596,15 @@ 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);
|
|
|
|
+ if (conf->fix_cert_order) {
|
|
+ SSL_set_ex_data(ssn->ssl, FR_TLS_EX_INDEX_FIX_CERT_ORDER, (void *) &conf->fix_cert_order);
|
|
+ }
|
|
+
|
|
/*
|
|
* Add the message callback to identify what type of
|
|
* message/handshake is passed
|
|
@@ -537,17 +624,19 @@ 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:
|
|
+ ssn->connected = false;
|
|
+ return ssn;
|
|
+
|
|
case SSL_ERROR_WANT_WRITE:
|
|
ssn->connected = false;
|
|
return ssn;
|
|
@@ -555,7 +644,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 +664,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 +726,33 @@ 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 FAST.
|
|
+ *
|
|
+ * We need another magic configuration option to allow
|
|
+ * it.
|
|
+ */
|
|
+ if (!allow_tls13 && (conf->max_version == TLS1_3_VERSION)) {
|
|
+ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
|
|
+ WARN("!! FORCING MAXIMUM TLS VERSION TO TLS 1.2 !!");
|
|
+ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
|
|
+ WARN("!! There is no standard for using this EAP method with TLS 1.3");
|
|
+ WARN("!! Please set tls_max_version = \"1.2\"");
|
|
+ WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
|
|
+
|
|
+ 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 +760,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 +799,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 +887,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 +911,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 +969,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 +991,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 +1044,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 +1070,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 +1084,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 +1134,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 +1241,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 +1253,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 +1293,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 +1317,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 +1337,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 +1369,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 +1405,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 +1426,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 +1455,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 +1532,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 +1571,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 +1664,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 +1672,14 @@ 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 >= 0x10101000L
|
|
+ { "sigalgs_list", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, sigalgs_list), NULL },
|
|
+#endif
|
|
+
|
|
+#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 +1698,23 @@ static CONF_PARSER tls_server_config[] = {
|
|
{ "disable_tlsv1_2", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, disable_tlsv1_2), NULL },
|
|
#endif
|
|
|
|
- { "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), "" },
|
|
+ { "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), NULL },
|
|
+
|
|
+ { "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version),
|
|
+#if defined(TLS1_2_VERSION)
|
|
+ "1.2"
|
|
+#elif defined(TLS1_1_VERSION)
|
|
+ "1.1"
|
|
+#else
|
|
+ "1.0"
|
|
+#endif
|
|
+ },
|
|
+
|
|
+#ifdef WITH_RADIUSV11
|
|
+ { "radiusv1_1", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, radiusv11_name), NULL },
|
|
+#endif
|
|
|
|
- { "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version), "1.0" },
|
|
+ { "realm_dir", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, realm_dir), NULL },
|
|
|
|
{ "cache", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) cache_config },
|
|
|
|
@@ -1312,6 +1743,9 @@ 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" },
|
|
+
|
|
+ { "fix_cert_order", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, fix_cert_order), NULL },
|
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
|
|
#ifndef OPENSSL_NO_ECDH
|
|
@@ -1331,9 +1765,23 @@ static CONF_PARSER tls_client_config[] = {
|
|
{ "disable_tlsv1_2", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, fr_tls_server_conf_t, disable_tlsv1_2), NULL },
|
|
#endif
|
|
|
|
- { "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), "" },
|
|
+ { "tls_max_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_max_version), NULL },
|
|
+
|
|
+ { "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version),
|
|
+#if defined(TLS1_2_VERSION)
|
|
+ "1.2"
|
|
+#elif defined(TLS1_1_VERSION)
|
|
+ "1.1"
|
|
+#else
|
|
+ "1.0"
|
|
+#endif
|
|
+ },
|
|
+
|
|
+#ifdef WITH_RADIUSV11
|
|
+ { "radiusv1_1", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, radiusv11_name), NULL },
|
|
+#endif
|
|
|
|
- { "tls_min_version", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, tls_min_version), "1.0" },
|
|
+ { "hostname", FR_CONF_OFFSET(PW_TYPE_STRING, fr_tls_server_conf_t, client_hostname), NULL },
|
|
|
|
CONF_PARSER_TERMINATOR
|
|
};
|
|
@@ -1347,7 +1795,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 +1907,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 +1924,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 +1932,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 +1948,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 +1971,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 +1979,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 +2080,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 +2102,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 +2125,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 +2149,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 +2159,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 +2193,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,22 +2233,366 @@ 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)
|
|
+{
|
|
+#if OPENSSL_VERSION_NUMBER < 0x10001000L
|
|
+ size_t size;
|
|
+
|
|
+ size = ssn->session_id_length;
|
|
+ if (size > bufsize) size = bufsize;
|
|
+
|
|
+ memcpy(buffer, ssn->session_id, size);
|
|
+ return size;
|
|
+#else
|
|
+ unsigned int size;
|
|
+ uint8_t const *p;
|
|
+
|
|
+ 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;
|
|
@@ -1811,7 +2655,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 +2680,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 +2689,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 +2709,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 +2882,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 +2898,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 +2931,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
|
|
@@ -2152,12 +3000,6 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx)
|
|
|
|
lookup = depth;
|
|
|
|
- /*
|
|
- * Log client/issuing cert. If there's an error, log
|
|
- * issuing cert.
|
|
- */
|
|
- if ((lookup > 1) && !my_ok) lookup = 1;
|
|
-
|
|
/*
|
|
* Retrieve the pointer to the SSL of the connection currently treated
|
|
* and the application specific data stored into the SSL object.
|
|
@@ -2177,14 +3019,37 @@ int cbtls_verify(int ok, X509_STORE_CTX *ctx)
|
|
|
|
talloc_ctx = SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_TALLOC);
|
|
|
|
+ /*
|
|
+ * Log client/issuing cert. If there's an error, log
|
|
+ * issuing cert.
|
|
+ *
|
|
+ * Inbound: 0 = client, 1 = server (intermediate CA), 2 = issuing CA
|
|
+ * Outbound: 0 = server, 2 = issuing CA.
|
|
+ *
|
|
+ * Our array of certificates uses 0 for client, and 1 for server. We
|
|
+ * also ignore subsequent certs.
|
|
+ */
|
|
+ if (lookup > 1) {
|
|
+ if (!my_ok) lookup = 1;
|
|
+
|
|
+ } else if (lookup == 0) {
|
|
+ /*
|
|
+ * This flag is only set for outbound
|
|
+ * connections. And then allows us to remap SSL
|
|
+ * offset 0 (server) to our offset 1 (also
|
|
+ * server).
|
|
+ */
|
|
+ lookup = (SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_FIX_CERT_ORDER) != NULL);
|
|
+ }
|
|
+
|
|
/*
|
|
* Get the Serial Number
|
|
*/
|
|
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 +3193,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 +3276,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 +3316,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 +3349,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 +3510,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;
|
|
}
|
|
|
|
@@ -2645,38 +3569,65 @@ static X509_STORE *init_revocation_store(fr_tls_server_conf_t *conf)
|
|
if (conf->check_all_crl)
|
|
X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK_ALL);
|
|
#endif
|
|
+
|
|
+#if defined(X509_V_FLAG_PARTIAL_CHAIN)
|
|
+ X509_STORE_set_flags(store, X509_V_FLAG_PARTIAL_CHAIN);
|
|
+#endif
|
|
+
|
|
return store;
|
|
}
|
|
-#endif /* HAVE_OPENSSL_OCSP_H */
|
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
|
|
#ifndef OPENSSL_NO_ECDH
|
|
static int set_ecdh_curve(SSL_CTX *ctx, char const *ecdh_curve, bool disable_single_dh_use)
|
|
{
|
|
- int nid;
|
|
- EC_KEY *ecdh;
|
|
+ if (!disable_single_dh_use) {
|
|
+ SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE);
|
|
+ }
|
|
|
|
- if (!ecdh_curve || !*ecdh_curve) return 0;
|
|
+ if (!ecdh_curve) return 0;
|
|
|
|
- nid = OBJ_sn2nid(ecdh_curve);
|
|
- if (!nid) {
|
|
- ERROR(LOG_PREFIX ": Unknown ecdh_curve \"%s\"", ecdh_curve);
|
|
- return -1;
|
|
- }
|
|
+#if OPENSSL_VERSION_NUMBER >= 0x1000200fL
|
|
+ /*
|
|
+ * A colon-separated list of curves.
|
|
+ */
|
|
+ if (*ecdh_curve) {
|
|
+ char *list;
|
|
|
|
- ecdh = EC_KEY_new_by_curve_name(nid);
|
|
- if (!ecdh) {
|
|
- ERROR(LOG_PREFIX ": Unable to create new curve \"%s\"", ecdh_curve);
|
|
- return -1;
|
|
+ memcpy(&list, &ecdh_curve, sizeof(list)); /* const issues */
|
|
+
|
|
+ if (SSL_CTX_set1_curves_list(ctx, list) == 0) {
|
|
+ ERROR(LOG_PREFIX ": Unknown ecdh_curve \"%s\"", ecdh_curve);
|
|
+ return -1;
|
|
+ }
|
|
}
|
|
|
|
- SSL_CTX_set_tmp_ecdh(ctx, ecdh);
|
|
+ (void) SSL_CTX_set_ecdh_auto(ctx, 1);
|
|
+#else
|
|
+ /*
|
|
+ * Use APIs for older versions of OpenSSL.
|
|
+ */
|
|
+ {
|
|
+ int nid;
|
|
+ EC_KEY *ecdh;
|
|
|
|
- if (!disable_single_dh_use) {
|
|
- SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE);
|
|
- }
|
|
+ nid = OBJ_sn2nid(ecdh_curve);
|
|
+ if (!nid) {
|
|
+ ERROR(LOG_PREFIX ": Unknown ecdh_curve \"%s\"", ecdh_curve);
|
|
+ return -1;
|
|
+ }
|
|
|
|
- EC_KEY_free(ecdh);
|
|
+ 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 +3659,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 +3750,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 +3783,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 +3799,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 +3823,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 +4016,56 @@ 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 (conf->ca_file || conf->ca_path) {
|
|
+ if ((certstore = fr_init_x509_store(conf)) == NULL ) return NULL;
|
|
+ SSL_CTX_set_cert_store(ctx, certstore);
|
|
+ } else {
|
|
#if defined(X509_V_FLAG_PARTIAL_CHAIN)
|
|
- X509_STORE_set_flags(SSL_CTX_get_cert_store(ctx), 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 (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 +4088,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 +4107,291 @@ 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)
|
|
+#ifdef WITH_RADIUSV11
|
|
+ /*
|
|
+ * RADIUS 1.1 requires TLS 1.3 or later.
|
|
+ */
|
|
+ if (conf->radiusv11) {
|
|
+ max_version = TLS1_3_VERSION;
|
|
+ } else
|
|
+#endif
|
|
+
|
|
+
|
|
+ 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 {
|
|
+#ifdef WITH_RADIUSV11
|
|
/*
|
|
- * Set these for the rest of the code.
|
|
+ * RADIUS 1.1 requires TLS 1.3 or later.
|
|
*/
|
|
-#ifdef TLS1_2_VERSION
|
|
- if (max_version < TLS1_2_VERSION) {
|
|
- conf->disable_tlsv1_2 = true;
|
|
- }
|
|
-#endif
|
|
-#ifdef TLS1_1_VERSION
|
|
- if (max_version < TLS1_1_VERSION) {
|
|
- conf->disable_tlsv1_1 = true;
|
|
- }
|
|
+ if (conf->radiusv11) {
|
|
+ min_version = TLS1_3_VERSION;
|
|
+ } else
|
|
#endif
|
|
-
|
|
/*
|
|
- * Get the min version.
|
|
+ * Allow TLS 1.0. It is horribly insecure, but
|
|
+ * some systems still use it.
|
|
*/
|
|
- 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;
|
|
- }
|
|
+ min_version = TLS1_VERSION;
|
|
+ }
|
|
|
|
- /*
|
|
- * 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);
|
|
- return NULL;
|
|
- }
|
|
+ /*
|
|
+ * 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;
|
|
+ }
|
|
|
|
-#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.");
|
|
- }
|
|
+ /*
|
|
+ * 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
|
|
|
|
- if (!SSL_CTX_set_max_proto_version(ctx, max_version)) {
|
|
- ERROR("Failed setting TLS maximum version");
|
|
- return NULL;
|
|
+ /*
|
|
+ * 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)
|
|
+#endif
|
|
+ {
|
|
+ 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'");
|
|
}
|
|
+ }
|
|
|
|
- if (!SSL_CTX_set_min_proto_version(ctx, min_version)) {
|
|
- ERROR("Failed setting TLS minimum 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;
|
|
}
|
|
+ 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;
|
|
+ }
|
|
|
|
- /*
|
|
- * 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_VERSION) ctx_options |= SSL_OP_NO_TLSv1;
|
|
|
|
-#ifdef SSL_OP_NO_TLSv1
|
|
- insecure_tls_version |= (conf->disable_tlsv1 == false);
|
|
+ ctx_available |= SSL_OP_NO_TLSv1;
|
|
#endif
|
|
+
|
|
#ifdef SSL_OP_NO_TLSv1_1
|
|
- insecure_tls_version |= (conf->disable_tlsv1_1 == false);
|
|
+ /*
|
|
+ * 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 (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;
|
|
+ }
|
|
+
|
|
+ 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;
|
|
+
|
|
+ ctx_available |= SSL_OP_NO_TLSv1_1;
|
|
#endif
|
|
-#endif /* OpenSSL version ? 1.1.0 */
|
|
|
|
- if (rad_debug_lvl && insecure_tls_version) {
|
|
- WARN("The configuration allows TLS 1.0 and/or TLS 1.1. We STRONGLY recommned using only TLS 1.2 for security");
|
|
- WARN("Please set: tls_min_version = \"1.2\"");
|
|
+#ifdef SSL_OP_NO_TLSv1_2
|
|
+ /*
|
|
+ * Check min / max against the old-style "disable" flag.
|
|
+ */
|
|
+ if (conf->disable_tlsv1_2) {
|
|
+ if (min_version <= TLS1_2_VERSION) {
|
|
+ ERROR(LOG_PREFIX ": 'disable_tlsv1_2' is set, but 'min_version <= 1.2'. These cannot both be true.");
|
|
+ return NULL;
|
|
+ }
|
|
+ if (max_version == TLS1_2_VERSION) {
|
|
+ ERROR(LOG_PREFIX ": 'disable_tlsv1_1' is set, but 'max_version = 1.2'. These cannot both be true.");
|
|
+ return NULL;
|
|
}
|
|
+ ctx_options |= SSL_OP_NO_TLSv1_2;
|
|
}
|
|
+ ctx_available |= SSL_OP_NO_TLSv1_2;
|
|
+
|
|
+ if (min_version > TLS1_2_VERSION) ctx_options |= SSL_OP_NO_TLSv1_2;
|
|
+ if (max_version < TLS1_2_VERSION) ctx_options |= SSL_OP_NO_TLSv1_2;
|
|
+#endif
|
|
+
|
|
+#ifdef SSL_OP_NO_TLSv1_3
|
|
+ ctx_available |= SSL_OP_NO_TLSv1_3;
|
|
+ if (min_version > TLS1_3_VERSION) ctx_options |= SSL_OP_NO_TLSv1_3;
|
|
+ if (max_version < TLS1_3_VERSION) ctx_options |= SSL_OP_NO_TLSv1_3;
|
|
+#endif
|
|
|
|
+
|
|
+#ifdef WITH_RADIUSV11
|
|
/*
|
|
- * For historical config compatibility, we also allow
|
|
- * these, but complain if the admin uses them.
|
|
+ * RADIUS 1.1 requires TLS 1.3 or later.
|
|
*/
|
|
-#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");
|
|
+ if (conf->radiusv11 && (min_version < TLS1_3_VERSION)) {
|
|
+ ERROR(LOG_PREFIX ": Please set 'tls_min_version = 1.2' or greater to use 'radiusv1_1 = true'");
|
|
+ return NULL;
|
|
+ }
|
|
#endif
|
|
+
|
|
+ /*
|
|
+ * 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.
|
|
+ */
|
|
+ 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;
|
|
+#if OPENSSL_VERSION_NUMBER >= 0x10101000L
|
|
+ if (conf->sigalgs_list) {
|
|
+ char *list;
|
|
+
|
|
+ memcpy(&list, &(conf->sigalgs_list), sizeof(list)); /* const issues */
|
|
+
|
|
+ if (SSL_CTX_set1_sigalgs_list(ctx, list) == 0) {
|
|
+ tls_error_log(NULL, "Failed setting signature list '%s'", conf->sigalgs_list);
|
|
+ return NULL;
|
|
+ }
|
|
+ }
|
|
#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 +4426,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 +4484,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 +4516,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 +4557,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 +4582,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 +4626,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 +4667,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 +4797,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 +4835,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 +4910,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 +4933,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 +4941,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 +4986,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 +5038,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 +5046,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 +5063,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 +5124,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 +5146,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 +5177,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 +5193,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 +5225,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 +5261,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 +5331,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 +5355,33 @@ 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");
|
|
+ if (ssn->dirty_out.used > 0) {
|
|
+ RDEBUG2("(TLS) Peer ACKed our handshake fragment");
|
|
+ /* Fragmentation handler, send next fragment */
|
|
+ return FR_TLS_REQUEST;
|
|
+ }
|
|
+
|
|
+ if (ssn->is_init_finished || SSL_is_init_finished(ssn->ssl)) {
|
|
+ RDEBUG2("(TLS) Peer ACKed our handshake fragment. handshake is finished");
|
|
|
|
/*
|
|
* From now on all the content is
|
|
@@ -4057,12 +5392,11 @@ 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");
|
|
- /* Fragmentation handler, send next fragment */
|
|
- return FR_TLS_REQUEST;
|
|
+ REDEBUG("(TLS) Cannot continue, as the peer is misbehaving.");
|
|
+ return FR_TLS_FAIL;
|
|
|
|
case application_data:
|
|
- RDEBUG2("Peer ACKed our application data fragment");
|
|
+ RDEBUG2("(TLS) Peer ACKed our application data fragment");
|
|
return FR_TLS_REQUEST;
|
|
|
|
/*
|
|
@@ -4070,7 +5404,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, "<INVALID>"));
|
|
+ REDEBUG("(TLS) EAP Verification failed with %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
|
|
} else {
|
|
- RDEBUG2("[eaptls verify] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
|
|
+ RDEBUG3("(TLS) EAP Verification says %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
|
|
}
|
|
|
|
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 <openssl/ssl.h>
|
|
#include <openssl/hmac.h>
|
|
+#include <freeradius-devel/openssl3.h>
|
|
|
|
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
|
|
+#include <openssl/provider.h>
|
|
+#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, "<INVALID>"));
|
|
} else {
|
|
- RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
|
|
+ RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
|
|
}
|
|
|
|
/*
|
|
@@ -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, "<INVALID>"));
|
|
} else {
|
|
- RDEBUG2("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
|
|
+ RDEBUG3("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
|
|
}
|
|
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, "<INVALID>"));
|
|
} else {
|
|
- RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
|
|
+ RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
|
|
}
|
|
|
|
/*
|
|
@@ -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 <freeradius-devel/openssl3.h>
|
|
|
|
-#include <freeradius-devel/radiusd.h>
|
|
-#include <freeradius-devel/modules.h>
|
|
+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, "<INVALID>"));
|
|
} else {
|
|
- RDEBUG2("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
|
|
+ RDEBUG3("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
|
|
}
|
|
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, "<INVALID>"));
|
|
} else {
|
|
- RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
|
|
+ RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
|
|
}
|
|
|
|
|
|
@@ -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, "<INVALID>"));
|
|
} else {
|
|
- RDEBUG2("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
|
|
+ RDEBUG3("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
|
|
}
|
|
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, "<INVALID>"));
|
|
} else {
|
|
- RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
|
|
+ RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>"));
|
|
}
|
|
|
|
/*
|
|
@@ -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: <int> 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 <lber.h>
|
|
#include <ldap.h>
|
|
#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 <string.h>
|
|
|
|
+#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_radstate.c b/src/modules/rlm_otp/otp_radstate.c
|
|
index 66fd8b4987..256437a552 100644
|
|
--- a/src/modules/rlm_otp/otp_radstate.c
|
|
+++ b/src/modules/rlm_otp/otp_radstate.c
|
|
@@ -113,6 +113,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 +126,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, "<INVALID>");
|
|
|
|
- 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/<version> <reason_code>[ <reason_phrase>]\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 <curl/curl.h>
|
|
|
|
+#ifdef HAVE_WDOCUMENTATION
|
|
+DIAG_OFF(documentation)
|
|
+#endif
|
|
+
|
|
#ifdef HAVE_JSON
|
|
# if defined(HAVE_JSONMC_JSON_H)
|
|
# include <json-c/json.h>
|
|
@@ -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 <stddef.h>
|
|
+#include <string.h>
|
|
+
|
|
+#include <freeradius-devel/radiusd.h>
|
|
+#include <freeradius-devel/modules.h>
|
|
+#include <openssl/evp.h>
|
|
+#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 <freeradius-devel/util/acutest.h>
|
|
+
|
|
+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 <stddef.h>
|
|
+
|
|
+/*
|
|
+ * 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 <freeradius-devel/radiusd.h>
|
|
#include <freeradius-devel/modules.h>
|
|
+#include "milenage.h"
|
|
|
|
#ifdef HAVE_OPENSSL_HMAC_H
|
|
#include <openssl/hmac.h>
|
|
#endif
|
|
|
|
+#include <freeradius-devel/openssl3.h>
|
|
+
|
|
+#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'
|
|
+ }
|
|
+}
|
|
+
|