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 Related: rhbz#2263240 Signed-off-by: Antonio Torres [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. [antorres@redhat.com]: add fix for BlastRADIUS CVE, commit range backported: 3a00a6ecc188629b0441fd45ad61ca8986de156e^..da643f1edc267ce95260dc36069e6f1a7a4d66f8, this backport includes changes from other files not included in the commit range, to ensure correct compilation. --- man/man1/radclient.1 | 10 +- man/man1/radtest.1 | 13 +- raddb/clients.conf | 98 +- raddb/mods-available/eap | 6 +- raddb/proxy.conf | 83 +- raddb/radiusd.conf.in | 280 ++- raddb/sites-available/tls | 8 + share/dictionary.freeradius | 5 + share/dictionary.freeradius.internal | 54 +- src/include/build.h | 29 +- src/include/clients.h | 16 +- src/include/conffile.h | 2 + src/include/event.h | 3 + src/include/libradius.h | 43 +- src/include/listen.h | 30 +- src/include/md4.h | 50 +- src/include/md5.h | 33 +- src/include/missing-h | 4 + src/include/openssl3.h | 109 ++ src/include/process.h | 9 + src/include/radius.h | 1 + src/include/radiusd.h | 27 +- src/include/realms.h | 23 +- src/include/socket.h | 53 + src/include/tls-h | 45 +- src/include/token.h | 7 +- src/lib/dict.c | 150 +- src/lib/event.c | 153 +- 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 | 428 ++++- src/lib/token.c | 24 +- src/main/cb.c | 121 +- src/main/client.c | 255 ++- src/main/command.c | 220 ++- src/main/conffile.c | 71 +- src/main/listen.c | 582 +++++- src/main/mainconfig.c | 130 +- src/main/map.c | 54 +- src/main/process.c | 640 +++++-- src/main/radclient.c | 231 ++- src/main/radiusd.c | 1 + src/main/radtest.in | 8 +- src/main/realms.c | 354 +++- src/main/session.c | 33 +- src/main/stats.c | 177 +- src/main/tls.c | 2012 ++++++++++++++++---- src/main/tls_listen.c | 509 ++++- 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 + 84 files changed, 9222 insertions(+), 1902 deletions(-) diff --git a/man/man1/radclient.1 b/man/man1/radclient.1 index 229dcae0c7..b83bee931a 100644 --- a/man/man1/radclient.1 +++ b/man/man1/radclient.1 @@ -1,10 +1,11 @@ -.TH RADCLIENT 1 "22 March 2019" "" "FreeRADIUS Daemon" +.TH RADCLIENT 1 "21 May 2024" "" "FreeRADIUS Daemon" .SH NAME radclient - send packets to a RADIUS server, show reply .SH SYNOPSIS .B radclient .RB [ \-4 ] .RB [ \-6 ] +.RB [ \-b ] .RB [ \-c .IR count ] .RB [ \-d @@ -52,6 +53,13 @@ automatically encrypted before the packet is sent to the server. Use IPv4 (default) .IP \-6 Use IPv6 +.IP \-b +Enforce the Blast RADIUS checks. All replies to an Access-Request packet +must contain a Message-Authenticator as the first attribute. + +For compatibility with old servers, this flag is not set by default. +However, radclient still checks for the Blast RADIUS signature, and +discards packets which match the attack. .IP \-c\ \fIcount\fP Send each packet \fIcount\fP times. .IP \-d\ \fIraddb_directory\fP diff --git a/man/man1/radtest.1 b/man/man1/radtest.1 index b3184779c0..d90651aba8 100644 --- a/man/man1/radtest.1 +++ b/man/man1/radtest.1 @@ -1,4 +1,4 @@ -.TH RADTEST 1 "5 April 2010" "" "FreeRADIUS Daemon" +.TH RADTEST 1 "21 May 2024" "" "FreeRADIUS Daemon" .SH NAME radtest - send packets to a RADIUS server, show reply .SH SYNOPSIS @@ -15,6 +15,8 @@ radtest - send packets to a RADIUS server, show reply .IR ] .RB [ \-6 .IR ] +.RB [ \-b +.IR ] .I user password radius-server nas-port-number secret .RB [ ppphint ] .RB [ nasname ] @@ -26,6 +28,13 @@ way to test a radius server. .SH OPTIONS +.IP \-b +Enforce the Blast RADIUS checks. All replies to an Access-Request packet +must contain a Message-Authenticator as the first attribute. + +For compatibility with old servers, this flag is not set by default. +However, radclient still checks for the Blast RADIUS signature, and +discards packets which match the attack. .IP "\-d \fIraddb_directory\fP" The directory that contains the RADIUS dictionary files. Defaults to \fI/etc/raddb\fP. @@ -35,7 +44,7 @@ Use \fIproto\fP transport protocol ("tcp" or "udp"). Only available if FreeRADIUS is compiled with TCP transport support. .IP "\-t \fIpap/chap/mschap/eap-md5\fP" -Choose the authentiction method to use. e.g. "-t pap", "-t chap", "-t +Choose the authentication method to use. e.g. "-t pap", "-t chap", "-t mschap", or "-t eap-md5",. Defaults to "pap". Using EAP-MD5 requires that the "radeapclient" program is installed. diff --git a/raddb/clients.conf b/raddb/clients.conf index 76b300d3c5..28bd6863b5 100644 --- a/raddb/clients.conf +++ b/raddb/clients.conf @@ -82,17 +82,33 @@ client localhost { # Quotation marks can be entered by escaping them, # e.g. "foo\"bar" # - # A note on security: The security of the RADIUS protocol + # A note on security: The security of the RADIUS protocol # depends COMPLETELY on this secret! We recommend using a - # shared secret that is composed of: + # shared secret that at LEAST 16 characters long. It should + # preferably be 32 characters in length. The secret MUST be + # random, and should not be words, phrase, or anything else + # that is recognisable. # - # upper case letters - # lower case letters - # numbers + # Computing power has increased enormously since RADIUS was + # first defined. A hobbyist with a high-end GPU can try ALL + # of the 8-character shared secrets in about a day. The + # security of shared secrets increases MUCH more with the + # length of the shared secret, than with number of different + # characters used in it. So don't bother trying to use + # "special characters" or anything else in an attempt to get + # un-guessable secrets. Instead, just get data from a secure + # random number generator, and use that. # - # And is at LEAST 8 characters long, preferably 16 characters in - # length. The secret MUST be random, and should not be words, - # phrase, or anything else that is recognisable. + # You should create shared secrets using a method like this: + # + # dd if=/dev/random bs=1 count=24 | base64 + # + # This process will give output which takes 24 random bytes, + # and converts them to 32 characters of ASCII. The output + # should be accepted by all RADIUS clients. + # + # You should NOT create shared secrets by hand. They will + # not be random. They will will be trivial to crack. # # The default secret below is only for testing, and should # not be used in any real environment. @@ -100,15 +116,45 @@ client localhost { secret = testing123 # - # Old-style clients do not send a Message-Authenticator - # in an Access-Request. RFC 5080 suggests that all clients - # SHOULD include it in an Access-Request. The configuration - # item below allows the server to require it. If a client - # is required to include a Message-Authenticator and it does - # not, then the packet will be silently discarded. + # The global configuration "security.require_message_authenticator" + # flag sets the default for all clients. That default can be + # over-ridden here, by setting it to a value. If no value is set, + # then the default from the "radiusd.conf" file is used. + # + # See that file for full documentation on the flag, along + # with allowed values and meanings. + # + # This flag exists solely for legacy clients which do not send + # Message-Authenticator in all Access-Request packets. We do not + # recommend setting it to "no". # - # allowed values: yes, no - require_message_authenticator = no + # The number one way to protect yourself from the BlastRADIUS + # attack is to update all RADIUS servers, and then set this + # flag to "yes". If all RADIUS servers are updated, and if + # all of them have this flag set to "yes" for all clients, + # then your network is safe. You can then upgrade the + # clients when it is convenient, instead of rushing the + # upgrades. + # + # allowed values: yes, no, auto + # +# require_message_authenticator = no + + # + # The global configuration "security.limit_proxy_state" + # flag sets the default for all clients. That default can be + # over-ridden here, by setting it to "no". + # + # See that file for full documentation on the flag, along + # with allowed values,and meanings. + # + # This flag exists solely for legacy clients which do not send + # Message-Authenticator in all Access-Request packets. We do not + # recommend setting it to "no". + # + # allowed values: yes, no, auto + # +# limit_proxy_state = yes # # The short name is used as an alias for the fully qualified @@ -260,6 +306,26 @@ client localhost_ipv6 { # "clients = per_socket_clients". That IP address/port combination # will then accept ONLY the clients listed in this section. # +# There are additional considerations when using clients from SQL. +# +# A client can be link to a virtual server via modules such as SQL. +# This link is done via the following process: +# +# If there is no listener in a virtual server, SQL clients are added +# to the global list for that virtual server. +# +# If there is a listener, and the first listener does not have a +# "clients=..." configuration item, SQL clients are added to the +# global list. +# +# If there is a listener, and the first one does have a "clients=..." +# configuration item, SQL clients are added to that list. The client +# { ...} ` configured in that list are also added for that listener. +# +# The only issue is if you have multiple listeners in a virtual +# server, each with a different client list, then the SQL clients are +# added only to the first listener. +# #clients per_socket_clients { # client socket_client { # ipaddr = 192.0.2.4 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/proxy.conf b/raddb/proxy.conf index 91b4b37930..7295538d5e 100644 --- a/raddb/proxy.conf +++ b/raddb/proxy.conf @@ -66,6 +66,46 @@ proxy server { # default_fallback = no + # + # Whether or not we allow dynamic home servers. + # + # This setting should be "no" by default. If set to "yes", + # it can slow the server down, due to mutex locking across + # multiple threads. + # + # Dynamic servers will work ONLY with the "directory" + # configuration below. + # +# dynamic = yes + + # + # The directory which contains dynamic home servers. Each + # file in the directory should be a normal "home_server" + # definitions. This directory does not exist by default. + # + # e.g: The content of home_servers/example.com should be + # a home server definition. + # + # The name of the home server MUST be the same as the + # filename. + # + # Each home server must be set to only one type. e.g. + # "type = auth", and not "type = auth+acct" + # + # For example: + # + # home_server example.com { + # type = auth + # ipaddr = ... + # ... + # } + # + # For complete documentation, please see + # + # doc/configuration/dynamic_home_servers.md + # +# directory = ${confdir}/home_servers + } ####################################################################### @@ -111,6 +151,12 @@ proxy server { # which was awkward. In 2.0, they have been made independent # from realms, which is better for a number of reasons. # +# You can proxy to a specific home server by doing: +# +# update control { +# Home-Server-Name = "name of home server" +# } +# home_server localhost { # # Home servers can be sent Access-Request packets @@ -204,6 +250,24 @@ home_server localhost { # secret = testing123 + # + # The global configuration "security.require_message_authenticator" + # flag sets the default for all home servers. That default can be + # over-ridden here, by setting it to a value. If no value is set, + # then the default from the "radiusd.conf" file is used. + # + # See that file for full documentation on the flag, along + # with allowed values and meanings. + # + # This flag exists solely for legacy home servers which do + # not send Message-Authenticator in all Access-Accept, + # Access-Reject, or Access-Challenge packets. We do not + # recommend setting it to "no". + # + # allowed values: yes, no, auto + # +# require_message_authenticator = no + ############################################################ # # The rest of the configuration items listed here are optional, @@ -510,6 +574,12 @@ home_server localhost { # 10 'realm" sections, and one "home_server_pool" section to tie the # two together. # +# You can proxy to a specific home server pool by doing: +# +# update control { +# Home-Server-Pool = "name of pool" +# } +# home_server_pool my_auth_failover { # # The type of this pool controls how home servers are chosen. @@ -664,10 +734,9 @@ realm example.com { auth_pool = my_auth_failover # acct_pool = acct - # As of Version 3.0, the server can proxy CoA packets - # based on the Operator-Name attribute. This requires - # that the "suffix" module be listed in the "recv-coa" - # section. + # The server can proxy CoA packets based on the Operator-Name + # attribute. This requires that the "suffix" module be + # listed in the "recv-coa" section. # # See raddb/sites-available/coa # @@ -689,6 +758,9 @@ realm example.com { # # If you do not want this to happen, uncomment "nostrip" below. # + # Note that if the system is doing EAP, you MUST set the "nostrip" + # option for realms used in EAP. Otherwise EAP will fail. + # # nostrip # There are no more configuration entries for a realm. @@ -790,9 +862,6 @@ realm LOCAL { # Yes, this rule is different than the normal "unlang" rules for # regular expressions. That may be fixed in a future release. # -# - for version 3.0.4 and following, with "correct_escapes = true", -# use normal regex backslash rules. Just one. Not two. -# # - If you are matching domain names, put a '$' at the end of the regex # that matches the domain name. This tells the regex matching code # that the realm ENDS with the domain name, so it does not match diff --git a/raddb/radiusd.conf.in b/raddb/radiusd.conf.in index e8aee3c001..4909c1b901 100644 --- a/raddb/radiusd.conf.in +++ b/raddb/radiusd.conf.in @@ -25,7 +25,7 @@ # The "sites-available" directory contains many # worked examples of common configurations. # -# raddb/certs/README +# raddb/certs/README.md # How to create certificates for EAP or RadSec. # # Every configuration item in the server is documented @@ -151,13 +151,21 @@ pidfile = ${run_dir}/${name}.pid # # correct_escapes: use correct backslash escaping # +# This setting is for compatibility with 3.0.4 and earlier. If +# you're running a copy of the configuration from 3.0.4, you can +# change this setting to "no" in order to run a new binary using the +# old configuration files. +# +# If you've created the configuration after 2014, this should be set +# to "true", and you can ignore it. +# # Prior to version 3.0.5, the handling of backslashes was a little # awkward, i.e. "wrong". In some cases, to get one backslash into # a regex, you had to put 4 in the config files. # -# Version 3.0.5 fixes that. However, for backwards compatibility, +# Version 3.0.5 fixed that. However, for backwards compatibility, # the new method of escaping is DISABLED BY DEFAULT. This means -# that upgrading to 3.0.5 won't break your configuration. +# that upgrading from a 3.0.4 or below won't break your configuration. # # If you don't have double backslashes (i.e. \\) in your configuration, # this won't matter to you. If you do have them, fix that to use only @@ -207,8 +215,8 @@ correct_escapes = true # max_request_time: The maximum time (in seconds) to handle a request. # -# Requests which take more time than this to process may be killed, and -# a REJECT message is returned. +# Requests which take more time than this to process are discarded, +# and no reply is returned. # # WARNING: If you notice that requests take a long time to be handled, # then this MAY INDICATE a bug in the server, in one of the modules @@ -219,6 +227,12 @@ correct_escapes = true # then it probably means that you haven't indexed the database. See your # SQL server documentation for more information. # +# In general, values larger than 30 or so are useless. If the server +# replies to the NAS after 60 seconds, the NAS will almost always +# have given up on the request, and gone to another one. You can see +# this happening when the logs have messages like "received conflicting +# packet", and "discarding old request". +# # Useful range of values: 5 to 120 # max_request_time = 30 @@ -279,6 +293,17 @@ max_requests = 16384 # hostname_lookups = no +# +# Run a "Post-Auth-Type Client-Lost" section. This ONLY happens when +# the server sends an Access-Challenge, and then client does not +# respond to it. The goal is to allow administrators to log +# something when the client does not respond. +# +# See sites-available/default, "Post-Auth-Type Client-Lost" for more +# information. +# +#postauth_client_lost = no + # # Logging section. The various "log_*" configuration items # will eventually be moved here. @@ -377,6 +402,25 @@ log { # The message when the user exceeds the Simultaneous-Use limit. # msg_denied = "You are already logged in - access denied" + + # Suppress "secret" attributes when printing them in debug mode. + # + # Secrets are NOT tracked across xlat expansions. If your + # configuration puts secrets into other strings, they will + # still get printed. + # + # Setting this to "yes" means that the server prints + # + # <<< secret >>> + # + # instead of the value, for attriburtes which contain secret + # information. e.g. User-Name, Tunnel-Password, etc. + # + # This configuration is disabled by default. It is extremely + # important for administrators to be able to debug user logins + # by seeing what is actually being sent. + # +# suppress_secrets = no } # The program to execute to do concurrency checks. @@ -436,6 +480,24 @@ ENV { # LD_PRELOAD = /path/to/library2.so } +# +# TEMPLATES +# +# Template files hold common definitions that can be used in other +# server sections. When a template is referenced, the configuration +# items within the referenced template are copied to the referencing +# section. +# +# Using templates reduces repetition of common configuration items, +# which in turn makes the server configuration easier to maintain. +# +# See templates.conf for examples of using templates, and the +# referencing syntax. +# + +# $INCLUDE templates.conf + + # SECURITY CONFIGURATION # # There may be multiple methods of attacking on the server. This @@ -462,7 +524,7 @@ security { # # If you are worried about security issues related to this # use of chdir, then simply ensure that the "raddb" directory - # is inside of the chroot, end be sure to do "cd raddb" + # is inside of the chroot, and be sure to do "cd raddb" # BEFORE starting the server. # # If the server is statically linked, then the only files @@ -538,8 +600,7 @@ security { # rejects will be sent at 'cleanup_delay' time, when the request # is deleted from the internal cache of requests. # - # As of Version 3.0.5, "reject_delay" has sub-second resolution. - # e.g. "reject_delay = 1.4" seconds is possible. + # This number can be a decimal, e.g. 3.4 # # Useful ranges: 1 to 5 reject_delay = 1 @@ -564,6 +625,191 @@ security { # status_server = yes + # + # Global configuration for requiring Message-Authenticator in + # all Access-* packets sent over UDP or TCP. This flag is + # ignored for TLS. + # + # The number one way to protect yourself from the BlastRADIUS + # attack is to update all RADIUS servers, and then set this + # flag to "yes". If all RADIUS servers are updated, and if + # all of them have this flag set to "yes" for all clients, + # then your network is safe. You can then upgrade the + # clients when it is convenient, instead of rushing the + # upgrades. + # + # This flag sets the global default for all clients and home + # servers. It can be over-ridden in an individual client or + # home_server definition by adding the same flag to that + # section with an appropriate value. + # + # All upgraded RADIUS implementations should send + # Message-Authenticator in all Access-Request, Access-Accept, + # Access-Reject, and Access-Challenge packets. Once all + # systems are upgraded, setting this flag to "yes" is the + # best protection from the attack. + # + # The possible values and meanings for + # "require_message_authenticator" are; + # + # * "no" - allow Access-* packet which do not contain + # Message-Authenticator + # + # For a client, if this flag is set to "no", then the + # "limit_proxy_state" flag, below, is also checked. + # + # For a home_server, if this flag is set to "no", then the + # Access-Accept, Access-Reject, and Access-Challenge + # packets do not need to contain Message-Authenticator. + # + # The only reason to set this flag to "no" is when the + # RADIUS client or home server has not been updated. It is + # always safer to set this flag "no" in the individual + # client or home_server definition. The global flag SHOULD + # still be set to a safe value: "yes". + # + # WARNING: Setting this flag and the "limit_proxy_state" + # flag to "no" will allow MITM attackers to create fake + # Access-Accept packets to the NAS! At least one of them + # MUST be set to "yes" for the system to have any + # protection against the attack. + # + # * "yes" - Require that all Access-* packets (client and + # home_server) contain Message-Authenticator. If a packet + # does not contain Message-Authenticator, then it is + # discarded. + # + # * "auto" - Automatically determine the value of the flag, + # based on the first packet received from that client or + # home_server. + # + # If the packet does not contain Message-Authenticator, + # then the value of the flag is automatically switched to + # "no". + # + # If the packet contains Message-Authenticator but not + # EAP-Message, then the value of the flag is automatically + # switched to "yes". The server has to check for + # EAP-Message, because the previous RFCs require that the + # packet contains Message-Authenticator when it also + # contains EAP-Message. So having a Message-Authenticator + # in those packets doesn't give the server enough + # information to determined if the client or home_server + # has been updated. + # + # If the packet contains Message-Authenticator and + # EAP-Message, then the flag is left at the "auto" value. + # + # WARNING: This switch is done for the first packet + # received from that client or home server. The change + # does NOT persist across server restarts. You MUST change + # the to "yes" manually, in order to make a permanent + # change to the configuration. + # + # WARNING: If there are multiple NASes with the same source + # IP and client definitions, BUT the NASes have different + # behavior, then this flag WILL LIKELY BREAK YOUR NETWORK. + # + # That is, when there are multiple different RADIUS clients + # behind one NATed IP address, then these security settings + # have to be set to allow the MOST INSECURE packets to be + # processed. This is a terrible idea, and will leave your + # network vulnerable to the attack. Please upgrade all + # clients immediately. + # + # The only solution to that rare configuration is to set + # this flag to "no", in which case the network will work, + # but will be vulnerable to the attack. + # + require_message_authenticator = auto + + # + # Global configuration for limiting the combination of + # Proxy-State and Message-Authenticator. This flag only + # applies to packets sent over UDP or TCP. This flag is + # ignored for TLS. + # + # This flag sets the global default for all clients. It can + # be over-ridden in an individual client definition by adding + # the same flag to that section with an appropriate value. + # + # If "require_message_authenticator" is set to "yes", this + # configuration item is ignored. + # + # If "require_message_authenticator" is set to "no", this + # configuration item is checked. + # + # The possible values and meanings for "limit_proxy_state" are; + # + # * "no" - allow any packets from the client, even packets + # which contain the BlastRADIUS attack. Please be aware + # that in this configuration the server will complain for + # EVERY packet which it receives. + # + # The only reason to set this flag to "no" is when the + # client is a proxy, AND the proxy does not send + # Message-Authenticator in Access-Request packets. Even + # then, the best approach to fix the issue is to (1) update + # the proxy to send Message-Authenticator, and if that + # can't be done, then (2) set this flag to "no", but ONLY + # for that one client. The global flag SHOULD still be set + # to a safe value: "yes". + # + # WARNING: Setting both this flag and the + # "require_message_authenticator" flag to "no" will allow + # MITM attackers to create fake Access-Accept packets to the + # NAS! At least one of them MUST be set to "yes" for the + # system to have any protection against the attack. + # + # * "yes" - Allow packets without Message-Authenticator, + # but only when they do not contain Proxy-State. + # packets which contain Proxy-State MUST also contain + # Message-Authenticator, otherwise they are discarded. + # + # This setting is safe for most NASes, GGSNs, BRAS, etc. + # Most regular RADIUS clients do not send Proxy-State + # attributes for Access-Request packets that they originate. + # However some aggregators (e.g. Wireless LAN Controllers) + # may act as a RADIUS proxy for requests from their cohort + # of managed devices, and in such cases will provide a + # Proxy-State attribute. For those systems, you _must_ look + # at the actual packets to determine what to do. It may be + # that the only way to fix the vulnerability is to upgrade + # the WLC, and set "require_message_authenticator" to "yes". + # + # * "auto" - Automatically determine the value of the flag, + # based on the first packet received from that client. + # + # If the packet contains Proxy-State but no + # Message-Authenticator, then the value of the flag is + # automatically switched to "no". + # + # For all other situations, the value of the flag is + # automatically switched to "yes". + # + # WARNING: This switch is done for the first packet + # received from that client. The change does NOT persist + # across server restarts. You MUST change the to "yes" + # manually, in order to make a permanent change to the + # configuration. + # + # WARNING: If there are multiple NASes with the same source + # IP and client definitions, BUT the NASes have different + # behavior, then this flag WILL LIKELY BREAK YOUR NETWORK. + # + # That is, when there are multiple different RADIUS clients + # behind one NATed IP address, then these security settings + # have to be set to allow the MOST INSECURE packets to be + # processed. This is a terrible idea, and will leave your + # network vulnerable to the attack. Please upgrade all + # clients immediately. + # + # The only solution to that rare configuration is to set + # this flag to "no", in which case the network will work, + # but will be vulnerable to the attack. + # + limit_proxy_state = auto + @openssl_version_check_config@ } @@ -754,19 +1000,19 @@ modules { # directory from start to finish. Which means that the # modules are read off of disk randomly. # - # As of 3.0.18, you can list individual modules *before* the - # directory inclusion. Those modules will be loaded first. - # Then, when the directory is read, those modules will be - # skipped and not read twice. + # You can list individual modules *before* the directory + # inclusion. Those modules will be loaded first. Then, when + # the directory is read, those modules will be skipped and + # not read twice. # # $INCLUDE mods-enabled/sql # - # As of 3.0, modules are in mods-enabled/. Files matching - # the regex /[a-zA-Z0-9_.]+/ are loaded. The modules are - # initialized ONLY if they are referenced in a processing - # section, such as authorize, authenticate, accounting, - # pre/post-proxy, etc. + # All modules are in ther mods-enabled/ directory. Files + # matching the regex /[a-zA-Z0-9_.]+/ are read. The + # modules are initialized ONLY if they are referenced in a + # processing section, such as authorize, authenticate, + # accounting, pre/post-proxy, etc. # $INCLUDE mods-enabled/ } 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 b/share/dictionary.freeradius index c92c5214ad..bfab9296c4 100644 --- a/share/dictionary.freeradius +++ b/share/dictionary.freeradius @@ -107,6 +107,8 @@ VALUE FreeRADIUS-Stats-Server-State Alive 0 VALUE FreeRADIUS-Stats-Server-State Zombie 1 VALUE FreeRADIUS-Stats-Server-State Dead 2 VALUE FreeRADIUS-Stats-Server-State Idle 3 +VALUE FreeRADIUS-Stats-Server-State Admin-Down 4 +VALUE FreeRADIUS-Stats-Server-State Connection-Fail 5 # # When a home server is marked "dead" or "alive" @@ -182,4 +184,7 @@ ATTRIBUTE FreeRADIUS-EAP-FAST-PKCS 186.20 octets ATTRIBUTE FreeRADIUS-Stats-Error 187 string +ATTRIBUTE FreeRADIUS-Stats-Client-IPv6-Address 188 ipv6addr +ATTRIBUTE FreeRADIUS-Stats-Server-IPv6-Address 189 ipv6addr + END-VENDOR FreeRADIUS 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..c5eaa45457 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 */ @@ -66,6 +82,7 @@ extern "C" { * Macros for controlling warnings in GCC >= 4.2 and clang >= 2.8 */ #if defined(__GNUC__) && ((__GNUC__ * 100) + __GNUC_MINOR__) >= 402 +# define DIAG_UNKNOWN_PRAGMAS pragmas # define DIAG_PRAGMA(_x) PRAGMA(GCC diagnostic _x) # if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406 # define DIAG_OFF(_x) DIAG_PRAGMA(push) DIAG_PRAGMA(ignored JOINSTR(-W,_x)) @@ -75,10 +92,12 @@ extern "C" { # define DIAG_ON(_x) DIAG_PRAGMA(warning JOINSTR(-W,_x)) # endif #elif defined(__clang__) && ((__clang_major__ * 100) + __clang_minor__ >= 208) +# define DIAG_UNKNOWN_PRAGMAS unknown-pragmas # define DIAG_PRAGMA(_x) PRAGMA(clang diagnostic _x) # define DIAG_OFF(_x) DIAG_PRAGMA(push) DIAG_PRAGMA(ignored JOINSTR(-W,_x)) # define DIAG_ON(_x) DIAG_PRAGMA(pop) #else +# define DIAG_UNKNOWN_PRAGMAS # define DIAG_OFF(_x) # define DIAG_ON(_x) #endif @@ -137,6 +156,12 @@ extern "C" { # endif #endif +#define PRINTF_LIKE(n) CC_HINT(format(printf, n, n+1)) +#define NEVER_RETURNS CC_HINT(noreturn) +#define HIDDEN CC_HINT(visibility("hidden")) +#define UNUSED CC_HINT(unused) +#define BLANK_FORMAT " " /* GCC_LINT whines about empty formats */ + #ifdef __cplusplus } #endif diff --git a/src/include/clients.h b/src/include/clients.h index 560211557f..7d3288a489 100644 --- a/src/include/clients.h +++ b/src/include/clients.h @@ -26,10 +26,14 @@ * @copyright 2015 The FreeRADIUS server project */ +typedef struct radclient_list RADCLIENT_LIST; + + /** Describes a host allowed to send packets to the server * */ typedef struct radclient { + RADCLIENT_LIST *list; //!< parent list fr_ipaddr_t ipaddr; //!< IPv4/IPv6 address of the host. fr_ipaddr_t src_ipaddr; //!< IPv4/IPv6 address to send responses //!< from (family must match ipaddr). @@ -39,7 +43,11 @@ typedef struct radclient { char const *secret; //!< Secret PSK. - bool message_authenticator; //!< Require RADIUS message authenticator in requests. + fr_bool_auto_t require_ma; //!< Require RADIUS message authenticator in requests. + + bool dynamic_require_ma; //!< for dynamic clients + + fr_bool_auto_t limit_proxy_state; //!< Limit Proxy-State in requests char const *nas_type; //!< Type of client (arbitrary). @@ -91,16 +99,14 @@ typedef struct radclient { #ifdef WITH_COA char const *coa_name; //!< Name of the CoA home server or pool. - home_server_t *coa_server; //!< The CoA home_server_t the client is associated with. + home_server_t *coa_home_server; //!< The CoA home_server_t the client is associated with. //!< Must be used exclusively from coa_pool. - home_pool_t *coa_pool; //!< The CoA home_pool_t the client is associated with. + home_pool_t *coa_home_pool; //!< The CoA home_pool_t the client is associated with. //!< Must be used exclusively from coa_server. bool defines_coa_server; //!< Client also defines a home_server. #endif } RADCLIENT; -typedef struct radclient_list RADCLIENT_LIST; - /** Callback for retrieving values when building client sections * * Example: diff --git a/src/include/conffile.h b/src/include/conffile.h index 8cb045c946..237469c880 100644 --- a/src/include/conffile.h +++ b/src/include/conffile.h @@ -140,6 +140,7 @@ typedef struct timeval _timeval_t; #define PW_TYPE_MULTI (1 << 18) //!< CONF_PAIR can have multiple copies. #define PW_TYPE_NOT_EMPTY (1 << 19) //!< CONF_PAIR is required to have a non zero length value. #define PW_TYPE_FILE_EXISTS ((1 << 20) | PW_TYPE_STRING) //!< File matching value must exist +#define PW_TYPE_IGNORE_DEFAULT (1 << 21) //!< don't set from .dflt if the CONF_PAIR is missing /* @} **/ #define FR_INTEGER_COND_CHECK(_name, _var, _cond, _new)\ @@ -273,6 +274,7 @@ int cf_pair_count(CONF_SECTION const *cs); CONF_SECTION *cf_item_parent(CONF_ITEM const *ci); bool cf_item_is_section(CONF_ITEM const *item); bool cf_item_is_pair(CONF_ITEM const *item); +bool cf_item_is_data(CONF_ITEM const *item); CONF_PAIR *cf_item_to_pair(CONF_ITEM const *item); CONF_SECTION *cf_item_to_section(CONF_ITEM const *item); CONF_ITEM *cf_pair_to_item(CONF_PAIR const *cp); diff --git a/src/include/event.h b/src/include/event.h index a29c9562a9..884edec526 100644 --- a/src/include/event.h +++ b/src/include/event.h @@ -40,6 +40,7 @@ typedef void (*fr_event_fd_handler_t)(fr_event_list_t *el, int sock, void *ctx); fr_event_list_t *fr_event_list_create(TALLOC_CTX *ctx, fr_event_status_t status); int fr_event_list_num_fds(fr_event_list_t *el); +int fr_event_list_full(fr_event_list_t *el); int fr_event_list_num_elements(fr_event_list_t *el); int fr_event_insert(fr_event_list_t *el, @@ -53,6 +54,8 @@ int fr_event_now(fr_event_list_t *el, struct timeval *when); int fr_event_fd_insert(fr_event_list_t *el, int type, int fd, fr_event_fd_handler_t handler, void *ctx); +int fr_event_fd_write_handler(fr_event_list_t *el, int type, int fd, + fr_event_fd_handler_t write_handler, void *ctx); int fr_event_fd_delete(fr_event_list_t *el, int type, int fd); int fr_event_loop(fr_event_list_t *el); void fr_event_loop_exit(fr_event_list_t *el, int code); diff --git a/src/include/libradius.h b/src/include/libradius.h index ce2f713de1..1b975517b5 100644 --- a/src/include/libradius.h +++ b/src/include/libradius.h @@ -57,7 +57,13 @@ RCSIDH(libradius_h, "$Id$") * Talloc memory allocation is used in preference to malloc throughout * the libraries and server. */ +#ifdef HAVE_WDOCUMENTATION +DIAG_OFF(documentation) +#endif #include +#ifdef HAVE_WDOCUMENTATION +DIAG_ON(documentation) +#endif /* * Defines signatures for any missing functions. @@ -162,11 +168,6 @@ typedef void (*sig_t)(int); #define PAD(_x, _y) (_y - ((_x) % _y)) -#define PRINTF_LIKE(n) CC_HINT(format(printf, n, n+1)) -#define NEVER_RETURNS CC_HINT(noreturn) -#define UNUSED CC_HINT(unused) -#define BLANK_FORMAT " " /* GCC_LINT whines about empty formats */ - typedef struct attr_flags { unsigned int is_unknown : 1; //!< Attribute number or vendor is unknown. unsigned int is_tlv : 1; //!< Is a sub attribute. @@ -189,6 +190,10 @@ typedef struct attr_flags { unsigned int compare : 1; //!< has a paircompare registered + unsigned int is_dup : 1; //!< is a duplicate of another attribute + + unsigned int secret : 1; //!< is a secret thingy + uint8_t encrypt; //!< Ecryption method. uint8_t length; } ATTR_FLAGS; @@ -402,6 +407,11 @@ typedef struct radius_packet { size_t partial; int proto; #endif + bool tls; //!< uses secure transport + + bool message_authenticator; + bool proxy_state; + bool eap_message; } RADIUS_PACKET; typedef enum { @@ -443,7 +453,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 +482,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); @@ -507,6 +519,13 @@ DICT_VENDOR *dict_vendorbyvalue(int vendor); /* radius.c */ int rad_send(RADIUS_PACKET *, RADIUS_PACKET const *, char const *secret); bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason); + +/* + * 1 == require_ma + * 2 == msg_peek + * 4 == limit_proxy_state + * 8 == require_ma for Access-* replies and Protocol-Error + */ RADIUS_PACKET *rad_recv(TALLOC_CTX *ctx, int fd, int flags); ssize_t rad_recv_header(int sockfd, fr_ipaddr_t *src_ipaddr, uint16_t *src_port, int *code); void rad_recv_discard(int sockfd); @@ -586,6 +605,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 +631,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 +653,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, @@ -694,7 +715,7 @@ extern bool fr_dns_lookups; /* do IP -> hostname lookups? */ extern bool fr_hostname_lookups; /* do hostname -> IP lookups? */ extern int fr_debug_lvl; /* 0 = no debugging information */ extern uint32_t fr_max_attributes; /* per incoming packet */ -#define FR_MAX_PACKET_CODE (52) +#define FR_MAX_PACKET_CODE (53) extern char const *fr_packet_codes[FR_MAX_PACKET_CODE]; #define is_radius_code(_x) ((_x > 0) && (_x < FR_MAX_PACKET_CODE)) extern FILE *fr_log_fp; @@ -932,6 +953,12 @@ int fr_socket_wait_for_connect(int sockfd, struct timeval *timeout); } #endif +typedef enum { + FR_BOOL_FALSE = 0, + FR_BOOL_TRUE, + FR_BOOL_AUTO, +} fr_bool_auto_t; + #include #ifdef WITH_TCP diff --git a/src/include/listen.h b/src/include/listen.h index 4f50bbf808..2b4e02bd54 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,24 +70,41 @@ 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; + bool dead; uint32_t workers; #ifdef WITH_TLS fr_tls_server_conf_t *tls; + bool check_client_connections; + bool nonblock; + bool blocked; #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; @@ -143,9 +162,16 @@ typedef struct listen_socket_t { tls_session_t *ssn; REQUEST *request; /* horrible hacks */ VALUE_PAIR *certs; + uint32_t connect_timeout; pthread_mutex_t mutex; uint8_t *data; size_t partial; + enum { + LISTEN_TLS_INIT = 0, + LISTEN_TLS_CHECKING, + LISTEN_TLS_SETUP, + LISTEN_TLS_RUNNING, + } state; #endif RADCLIENT_LIST *clients; diff --git a/src/include/md4.h b/src/include/md4.h index b7bdd6a15e..1492bd4a5c 100644 --- a/src/include/md4.h +++ b/src/include/md4.h @@ -26,6 +26,10 @@ RCSIDH(md4_h, "$Id$") #include +#ifdef WITH_FIPS +#undef HAVE_OPENSSL_MD4_H +#endif + #ifdef HAVE_OPENSSL_MD4_H # include #endif @@ -71,14 +75,58 @@ void fr_md4_final(uint8_t out[MD4_DIGEST_LENGTH], FR_MD4_CTX *ctx) void fr_md4_transform(uint32_t buf[4], uint8_t const inc[MD4_BLOCK_LENGTH]) CC_BOUNDED(__size__, 1, 4, 4) CC_BOUNDED(__minbytes__, 2, MD4_BLOCK_LENGTH); +# define fr_md4_destroy(_x) #else /* HAVE_OPENSSL_MD4_H */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L USES_APPLE_DEPRECATED_API # define FR_MD4_CTX MD4_CTX # define fr_md4_init MD4_Init # define fr_md4_update MD4_Update # define fr_md4_final MD4_Final # define fr_md4_transform MD4_Transform -#endif +# define fr_md4_destroy(_x) +#else +#include + +/* + * Wrappers for OpenSSL3, so we don't have to butcher the rest of + * the code too much. + */ +typedef struct FR_MD4_CTX { + EVP_MD_CTX *ctx; + EVP_MD const *md; + unsigned int len; +} FR_MD4_CTX; + +static inline void fr_md4_init(FR_MD4_CTX *ctx) +{ + ctx->ctx = EVP_MD_CTX_new(); +// ctx->md = EVP_MD_fetch(NULL, "MD4", "provider=legacy"); + ctx->md = EVP_md4(); + ctx->len = MD4_DIGEST_LENGTH; + + EVP_MD_CTX_set_flags(ctx->ctx, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); + EVP_DigestInit_ex(ctx->ctx, ctx->md, NULL); +} + +static inline void fr_md4_update(FR_MD4_CTX *ctx, uint8_t const *in, size_t inlen) +{ + EVP_DigestUpdate(ctx->ctx, in, inlen); +} + +static inline void fr_md4_final(uint8_t out[MD4_DIGEST_LENGTH], FR_MD4_CTX *ctx) +{ + EVP_DigestFinal_ex(ctx->ctx, out, &(ctx->len)); +} + +static inline void fr_md4_destroy(FR_MD4_CTX *ctx) +{ + EVP_MD_CTX_destroy(ctx->ctx); +// EVP_MD_free(ctx->md); +} + +#endif /* OPENSSL3 */ +#endif /* HAVE_OPENSSL_MD4_H */ /* md4.c */ void fr_md4_calc(uint8_t out[MD4_DIGEST_LENGTH], uint8_t const *in, size_t inlen); diff --git a/src/include/md5.h b/src/include/md5.h index a44584564f..b7d571ac7b 100644 --- a/src/include/md5.h +++ b/src/include/md5.h @@ -26,6 +26,10 @@ RCSIDH(md5_h, "$Id$") # include +#ifdef WITH_FIPS +#undef HAVE_OPENSSL_MD5_H +#endif + #ifdef HAVE_OPENSSL_MD5_H # include #endif @@ -68,14 +72,41 @@ void fr_md5_final(uint8_t out[MD5_DIGEST_LENGTH], FR_MD5_CTX *ctx) void fr_md5_transform(uint32_t state[4], uint8_t const block[MD5_BLOCK_LENGTH]) CC_BOUNDED(__size__, 1, 4, 4) CC_BOUNDED(__minbytes__, 2, MD5_BLOCK_LENGTH); +# define fr_md5_destroy(_x) +# define fr_md5_copy(_dst, _src) _dst = _src #else /* HAVE_OPENSSL_MD5_H */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L USES_APPLE_DEPRECATED_API # define FR_MD5_CTX MD5_CTX # define fr_md5_init MD5_Init # define fr_md5_update MD5_Update # define fr_md5_final MD5_Final # define fr_md5_transform MD5_Transform -#endif +# define fr_md5_copy(_dst, _src) _dst = _src +# define fr_md5_destroy(_x) +#else +#include + +/* + * Wrappers for OpenSSL3, so we don't have to butcher the rest of + * the code too much. + */ +typedef EVP_MD_CTX* FR_MD5_CTX; + +# define fr_md5_init(_ctx) \ + do { \ + *_ctx = EVP_MD_CTX_new(); \ + EVP_MD_CTX_set_flags(*_ctx, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); \ + EVP_DigestInit_ex(*_ctx, EVP_md5(), NULL); \ + } while (0) +# define fr_md5_update(_ctx, _str, _len) \ + EVP_DigestUpdate(*_ctx, _str, _len) +# define fr_md5_final(_out, _ctx) \ + EVP_DigestFinal_ex(*_ctx, _out, NULL) +# define fr_md5_destroy(_ctx) EVP_MD_CTX_destroy(*_ctx) +# define fr_md5_copy(_dst, _src) EVP_MD_CTX_copy_ex(_dst, _src) +#endif /* OPENSSL3 */ +#endif /* HAVE_OPENSSL_MD5_H */ /* hmac.c */ void fr_hmac_md5(uint8_t digest[MD5_DIGEST_LENGTH], uint8_t const *text, size_t text_len, diff --git a/src/include/missing-h b/src/include/missing-h index bb74e060b9..5698797b0e 100644 --- a/src/include/missing-h +++ b/src/include/missing-h @@ -516,6 +516,10 @@ size_t SSL_SESSION_get_master_key(const SSL_SESSION *s, #define O_DIRECTORY 0 #endif +#ifndef O_NOFOLLOW +#define O_NOFOLLOW 0 +#endif + /* * Not really missing, but may be submitted as patches * to the talloc project at some point in the future. diff --git a/src/include/openssl3.h b/src/include/openssl3.h new file mode 100644 index 0000000000..4423ee538a --- /dev/null +++ b/src/include/openssl3.h @@ -0,0 +1,109 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ +#ifndef FR_OPENSSL3_H +#define FR_OPENSSL3_H +/** + * $Id$ + * + * @file openssl3.h + * @brief Wrappers to shut up OpenSSL3 + * + * @copyright 2021 Network RADIUS SAS (legal@networkradius.com) + */ + +RCSIDH(openssl3_h, "$Id$") + +/* + * The HMAC APIs are deprecated in OpenSSL3. We don't want to + * fill the code with ifdef's, so we define some horrific + * wrappers here. + * + * This file should be included AFTER all OpenSSL header files. + */ +#ifdef HAVE_OPENSSL_SSL_H +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include +#include +#include + +typedef struct { + EVP_MAC *mac; + EVP_MAC_CTX *ctx; +} HMAC3_CTX; +#define HMAC_CTX HMAC3_CTX + +#define HMAC_CTX_new HMAC3_CTX_new +static inline HMAC3_CTX *HMAC3_CTX_new(void) +{ + HMAC3_CTX *h = calloc(1, sizeof(*h)); + + return h; +} + +#define HMAC_Init_ex(_ctx, _key, _keylen, _md, _engine) HMAC3_Init_ex(_ctx, _key, _keylen, _md, _engine) +static inline int HMAC3_Init_ex(HMAC3_CTX *ctx, const unsigned char *key, unsigned int keylen, const EVP_MD *md, UNUSED void *engine) +{ + OSSL_PARAM params[2], *p = params; + char const *name; + char *unconst; + + ctx->mac = EVP_MAC_fetch(NULL, "HMAC", NULL); + if (!ctx->mac) return 0; + + ctx->ctx = EVP_MAC_CTX_new(ctx->mac); + if (!ctx->ctx) return 0; + + name = EVP_MD_get0_name(md); + memcpy(&unconst, &name, sizeof(name)); /* const issues */ + + p[0] = OSSL_PARAM_construct_utf8_string(OSSL_ALG_PARAM_DIGEST, unconst, 0); + p[1] = OSSL_PARAM_construct_end(); + + return EVP_MAC_init(ctx->ctx, key, keylen, params); +} + +#define HMAC_Update HMAC3_Update +static inline int HMAC3_Update(HMAC3_CTX *ctx, const unsigned char *data, unsigned int datalen) +{ + return EVP_MAC_update(ctx->ctx, data, datalen); +} + +#define HMAC_Final HMAC3_Final +static inline int HMAC3_Final(HMAC3_CTX *ctx, unsigned char *out, unsigned int *len) +{ + size_t mylen = *len; + + if (!EVP_MAC_final(ctx->ctx, out, &mylen, mylen)) return 0; + + *len = mylen; + return 1; +} + +#define HMAC_CTX_free HMAC3_CTX_free +static inline void HMAC3_CTX_free(HMAC3_CTX *ctx) +{ + if (!ctx) return; + + EVP_MAC_free(ctx->mac); + EVP_MAC_CTX_free(ctx->ctx); + free(ctx); +} + +#define HMAC_CTX_set_flags(_ctx, _flags) + +#endif /* OPENSSL_VERSION_NUMBER */ +#endif +#endif /* FR_OPENSSL3_H */ diff --git a/src/include/process.h b/src/include/process.h index 8c3c7275e0..35a91bfa55 100644 --- a/src/include/process.h +++ b/src/include/process.h @@ -47,6 +47,11 @@ typedef enum fr_state_action_t { /* server action */ FR_ACTION_PROXY_REPLY, #endif FR_ACTION_CANCELLED, + FR_ACTION_CONFLICT, + FR_ACTION_MAX_TIME, + FR_ACTION_INTERNAL_FAILURE, + FR_ACTION_CLEANUP_DELAY, + FR_ACTION_COA_CANCELLED, } fr_state_action_t; /* @@ -66,9 +71,13 @@ int request_enqueue(REQUEST *request); int request_receive(TALLOC_CTX *ctx, rad_listen_t *listener, RADIUS_PACKET *packet, RADCLIENT *client, RAD_REQUEST_FUNP fun); +void request_inject(REQUEST *request); #ifdef WITH_PROXY int request_proxy_reply(RADIUS_PACKET *packet); + +void proxy_listener_freeze(rad_listen_t *listener, fr_event_fd_handler_t write_handler); +void proxy_listener_thaw(rad_listen_t *listener); #endif #ifdef __cplusplus diff --git a/src/include/radius.h b/src/include/radius.h index 473528d65d..147d674eed 100644 --- a/src/include/radius.h +++ b/src/include/radius.h @@ -61,6 +61,7 @@ typedef enum { PW_CODE_COA_REQUEST = 43, //!< RFC3575/RFC5176 - CoA-Request PW_CODE_COA_ACK = 44, //!< RFC3575/RFC5176 - CoA-Ack (positive) PW_CODE_COA_NAK = 45, //!< RFC3575/RFC5176 - CoA-Nak (not willing to perform) + PW_CODE_PROTOCOL_ERROR = 52, //!< RFC7930 - Protocol layer issue PW_CODE_MAX = 255, //!< Maximum possible code } PW_CODE; diff --git a/src/include/radiusd.h b/src/include/radiusd.h index b2a0a0f642..2b9bc19f15 100644 --- a/src/include/radiusd.h +++ b/src/include/radiusd.h @@ -114,6 +114,7 @@ typedef struct main_config { fr_ipaddr_t myip; //!< IP to bind to. Set on command line. uint16_t port; //!< Port to bind to. Set on command line. + bool suppress_secrets; //!< for debug levels < 3 bool log_auth; //!< Log all authentication attempts. bool log_accept; //!< Log Access-Accept bool log_reject; //!< Log Access-Reject @@ -140,6 +141,8 @@ typedef struct main_config { uint32_t cleanup_delay; //!< How long before cleaning up cached responses. uint32_t max_requests; + bool postauth_client_lost; //!< Whether to run Post-Auth-Type Client-Lost section + uint32_t debug_level; char const *log_file; int syslog_facility; @@ -171,6 +174,9 @@ typedef struct main_config { bool exiting; //!< are we exiting? + fr_bool_auto_t require_ma; //!< global configuration for all clients and home servers + + fr_bool_auto_t limit_proxy_state; //!< global configuration for all clients #ifdef ENABLE_OPENSSL_VERSION_CHECK char const *allow_vulnerable_openssl; //!< The CVE number of the last security issue acknowledged. @@ -191,9 +197,8 @@ typedef struct main_config { typedef enum { REQUEST_ACTIVE = 1, REQUEST_STOP_PROCESSING, - REQUEST_COUNTED } rad_master_state_t; -#define REQUEST_MASTER_NUM_STATES (REQUEST_COUNTED + 1) +#define REQUEST_MASTER_NUM_STATES (REQUEST_STOP_PROCESSING + 1) typedef enum { REQUEST_QUEUED = 1, @@ -312,8 +317,10 @@ struct rad_request { #define RAD_REQUEST_LVL_DEBUG3 (3) #define RAD_REQUEST_LVL_DEBUG4 (4) -#define RAD_REQUEST_OPTION_COA (1 << 0) -#define RAD_REQUEST_OPTION_CTX (1 << 1) +#define RAD_REQUEST_OPTION_COA (1 << 0) +#define RAD_REQUEST_OPTION_CTX (1 << 1) +#define RAD_REQUEST_OPTION_CANCELLED (1 << 2) +#define RAD_REQUEST_OPTION_STATS (1 << 3) #define SECONDS_PER_DAY 86400 #define MAX_REQUEST_TIME 30 @@ -351,8 +358,8 @@ extern char const *radacct_dir; extern char const *radlog_dir; extern char const *radlib_dir; extern bool log_stripped_names; -extern char const *radiusd_version; -extern char const *radiusd_version_short; +extern HIDDEN char const *radiusd_version; +extern HIDDEN char const *radiusd_version_short; void radius_signal_self(int flag); typedef enum { @@ -374,8 +381,8 @@ int rad_accounting(REQUEST *); int rad_coa_recv(REQUEST *request); /* session.c */ -int rad_check_ts(uint32_t nasaddr, uint32_t nas_port, char const *user, char const *sessionid); -int session_zap(REQUEST *request, uint32_t nasaddr, +int rad_check_ts(fr_ipaddr_t const *nas_addr, uint32_t nas_port, char const *user, char const *sessionid); +int session_zap(REQUEST *request, fr_ipaddr_t const *nas_addr, uint32_t nas_port, char const *user, char const *sessionid, uint32_t cliaddr, char proto, int session_time); @@ -558,6 +565,8 @@ int main_config_free(void); void main_config_hup(void); void hup_logfile(void); +int fr_bool_auto_parse(CONF_PAIR *cp, fr_bool_auto_t *out, char const *str); + /* listen.c */ void listen_free(rad_listen_t **head); int listen_init(CONF_SECTION *cs, rad_listen_t **head, bool spawn_flag); @@ -583,7 +592,7 @@ void radius_event_free(void); int radius_event_process(void); void radius_update_listener(rad_listen_t *listener); void revive_home_server(void *ctx); -void mark_home_server_dead(home_server_t *home, struct timeval *when); +void mark_home_server_dead(home_server_t *home, struct timeval *when, bool down); /* evaluate.c */ typedef struct fr_cond_t fr_cond_t; diff --git a/src/include/realms.h b/src/include/realms.h index 6dae8b4f85..171a449112 100644 --- a/src/include/realms.h +++ b/src/include/realms.h @@ -21,10 +21,10 @@ typedef enum { HOME_TYPE_INVALID = 0, HOME_TYPE_AUTH, //!< Authentication server HOME_TYPE_ACCT, //!< Accounting server - HOME_TYPE_AUTH_ACCT //!< Authentication and accounting server + HOME_TYPE_AUTH_ACCT, //!< Authentication and accounting server #ifdef WITH_COA - ,HOME_TYPE_COA //!< CoA destination (NAS or Proxy) + HOME_TYPE_COA, //!< CoA destination (NAS or Proxy) #endif } home_type_t; @@ -36,13 +36,16 @@ typedef enum { } home_ping_check_t; typedef enum { - HOME_STATE_UNKNOWN = 0, - HOME_STATE_ALIVE, + HOME_STATE_ALIVE = 0, HOME_STATE_ZOMBIE, HOME_STATE_IS_DEAD, + HOME_STATE_UNKNOWN, + HOME_STATE_ADMIN_DOWN, HOME_STATE_CONNECTION_FAIL, } home_state_t; +#define HOME_SERVER_IS_DEAD(_x) (((_x)->state == HOME_STATE_IS_DEAD) || ((_x)->state == HOME_STATE_ADMIN_DOWN) || ((_x)->state == HOME_STATE_CONNECTION_FAIL)) + typedef struct fr_socket_limit_t { uint32_t max_connections; uint32_t num_connections; @@ -59,7 +62,11 @@ typedef struct home_server { //!< stats or when specifying home servers for a pool. bool dual; //!< One of a pair of homeservers on consecutive ports. - char const *server; //!< For internal proxying + bool dynamic; //!< is this a dynamically added home server? + bool nonblock; //!< Enable a socket non-blocking to the home server. + fr_bool_auto_t require_ma; //!< for all replies to Access-Request and Status-Server + + char const *virtual_server; //!< For internal proxying char const *parent_server; fr_ipaddr_t ipaddr; //!< IP address of home server. @@ -122,6 +129,8 @@ typedef struct home_server { #endif #ifdef WITH_TLS fr_tls_server_conf_t *tls; + uint32_t connect_timeout; + rbtree_t *listeners; #endif #ifdef WITH_STATS @@ -202,10 +211,14 @@ CONF_SECTION *home_server_cs_afrom_client(CONF_SECTION *client); home_server_t *home_server_byname(char const *name, int type); #endif #ifdef WITH_STATS +extern int home_server_max_number; home_server_t *home_server_bynumber(int number); #endif home_pool_t *home_pool_byname(char const *name, int type); +int home_server_afrom_file(char const *filename); +int home_server_delete(char const *name, char const *type); + #ifdef __cplusplus } #endif diff --git a/src/include/socket.h b/src/include/socket.h new file mode 100644 index 0000000000..821c2a0b7a --- /dev/null +++ b/src/include/socket.h @@ -0,0 +1,53 @@ +#pragma once + +/* + * 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 + */ + +/** Functions for establishing and managing low level sockets + * + * @file src/include/socket.h + * + * @author Arran Cudbard-Bell (a.cudbardb@freeradius.org) + * @author Alan DeKok (aland@freeradius.org) + * + * @copyright 2015 The FreeRADIUS project + */ +RCSIDH(socket_h, "$Id$") + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#ifdef HAVE_SYS_UN_H +# include +/* + * The linux headers define the macro as: + * + * # define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ + * + strlen ((ptr)->sun_path)) + * + * Which trips UBSAN, because it sees an operation on a NULL pointer. + */ +# undef SUN_LEN +# define SUN_LEN(su) (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path)) +#endif + +#ifdef __cplusplus +} +#endif 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/event.c b/src/lib/event.c index 0c2976b3c9..d912955fe8 100644 --- a/src/lib/event.c +++ b/src/lib/event.c @@ -28,6 +28,8 @@ RCSID("$Id$") #include #include +#include + #ifdef HAVE_KQUEUE #ifndef HAVE_SYS_EVENT_H #error kqueue requires @@ -39,7 +41,9 @@ RCSID("$Id$") typedef struct fr_event_fd_t { int fd; + fr_event_fd_handler_t handler; + fr_event_fd_handler_t write_handler; void *ctx; } fr_event_fd_t; @@ -61,13 +65,15 @@ struct fr_event_list_t { int num_readers; #ifndef HAVE_KQUEUE int max_readers; + int max_fd; - bool changed; - + fd_set read_fds; + fd_set write_fds; #else int kq; struct kevent events[FR_EV_MAX_FDS]; /* so it doesn't go on the stack every time */ #endif + fr_event_fd_t readers[FR_EV_MAX_FDS]; }; @@ -139,8 +145,9 @@ fr_event_list_t *fr_event_list_create(TALLOC_CTX *ctx, fr_event_status_t status) } #ifndef HAVE_KQUEUE - el->changed = true; /* force re-set of fds's */ - + el->max_fd = 0; + FD_ZERO(&el->read_fds); + FD_ZERO(&el->write_fds); #else el->kq = kqueue(); if (el->kq < 0) { @@ -161,6 +168,11 @@ int fr_event_list_num_fds(fr_event_list_t *el) return el->num_readers; } +int fr_event_list_full(fr_event_list_t *el) +{ + return (el->num_readers >= FR_EV_MAX_FDS); +} + int fr_event_list_num_elements(fr_event_list_t *el) { if (!el) return 0; @@ -432,6 +444,9 @@ int fr_event_fd_insert(fr_event_list_t *el, int type, int fd, el->num_readers++; if (i == el->max_readers) el->max_readers = i + 1; + + FD_SET(fd, &el->read_fds); + if (el->max_fd <= fd) el->max_fd = fd; break; } } @@ -446,13 +461,69 @@ int fr_event_fd_insert(fr_event_list_t *el, int type, int fd, ef->handler = handler; ef->ctx = ctx; -#ifndef HAVE_KQUEUE - el->changed = true; -#endif - return 1; } +int fr_event_fd_write_handler(fr_event_list_t *el, int type, int fd, + fr_event_fd_handler_t write_handler, void *ctx) +{ + int i; + + if (!el || (fd < 0)) return 0; + + if (type != 0) return 0; + +#ifdef HAVE_KQUEUE + for (i = 0; i < FR_EV_MAX_FDS; i++) { + int j; + struct kevent evset; + + j = (i + fd) & (FR_EV_MAX_FDS - 1); + + if (el->readers[j].fd != fd) continue; + + fr_assert(ctx = el->readers[j].ctx); + + /* + * Tell us when the socket is ready for writing + */ + if (write_handler) { + fr_assert(!el->readers[j].write_handler); + + el->readers[j].write_handler = write_handler; + + EV_SET(&evset, fd, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, &el->readers[j]); + } else { + fr_assert(el->readers[j].write_handler); + + el->readers[j].write_handler = NULL; + + EV_SET(&evset, fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); + } + if (kevent(el->kq, &evset, 1, NULL, 0, NULL) < 0) { + fr_strerror_printf("Failed inserting event for FD %i: %s", fd, fr_syserror(errno)); + return 0; + } + + return 1; + } + +#else + + for (i = 0; i < el->max_readers; i++) { + if (el->readers[i].fd != fd) continue; + + fr_assert(ctx = el->readers[i].ctx); + el->readers[i].write_handler = write_handler; + + FD_SET(fd, &el->write_fds); /* fd MUST already be in the set of readers! */ + return 1; + } +#endif /* HAVE_KQUEUE */ + + return 0; +} + int fr_event_fd_delete(fr_event_list_t *el, int type, int fd) { int i; @@ -480,6 +551,14 @@ int fr_event_fd_delete(fr_event_list_t *el, int type, int fd) EV_SET(&evset, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); (void) kevent(el->kq, &evset, 1, NULL, 0, NULL); + /* + * Delete the write handler if it exits. + */ + if (el->readers[j].write_handler) { + EV_SET(&evset, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); + (void) kevent(el->kq, &evset, 1, NULL, 0, NULL); + } + el->readers[j].fd = -1; el->num_readers--; @@ -487,14 +566,18 @@ int fr_event_fd_delete(fr_event_list_t *el, int type, int fd) } #else - for (i = 0; i < el->max_readers; i++) { if (el->readers[i].fd == fd) { el->readers[i].fd = -1; el->num_readers--; if ((i + 1) == el->max_readers) el->max_readers = i; - el->changed = true; + FD_CLR(fd, &el->read_fds); + FD_CLR(fd, &el->write_fds); + + /* + * @todo - update el->max_fd, too. + */ return 1; } } @@ -523,39 +606,13 @@ int fr_event_loop(fr_event_list_t *el) #ifdef HAVE_KQUEUE struct timespec ts_when, *ts_wake; #else - int maxfd = 0; - fd_set read_fds, master_fds; - - el->changed = true; + fd_set read_fds, write_fds; #endif el->exit = 0; el->dispatch = true; while (!el->exit) { -#ifndef HAVE_KQUEUE - /* - * Cache the list of FD's to watch. - */ - if (el->changed) { -#ifdef __clang_analyzer__ - memset(&master_fds, 0, sizeof(master_fds)); -#else - FD_ZERO(&master_fds); -#endif - for (i = 0; i < el->max_readers; i++) { - if (el->readers[i].fd < 0) continue; - - if (el->readers[i].fd > maxfd) { - maxfd = el->readers[i].fd; - } - FD_SET(el->readers[i].fd, &master_fds); - } - - el->changed = false; - } -#endif /* HAVE_KQUEUE */ - /* * Find the first event. If there's none, we wait * on the socket forever. @@ -604,8 +661,9 @@ int fr_event_loop(fr_event_list_t *el) if (el->status) el->status(wake); #ifndef HAVE_KQUEUE - read_fds = master_fds; - rcode = select(maxfd + 1, &read_fds, NULL, NULL, wake); + read_fds = el->read_fds; + write_fds = el->write_fds; + rcode = select(el->max_fd + 1, &read_fds, &write_fds, NULL, wake); if ((rcode < 0) && (errno != EINTR)) { fr_strerror_printf("Failed in select: %s", fr_syserror(errno)); el->dispatch = false; @@ -618,6 +676,7 @@ int fr_event_loop(fr_event_list_t *el) ts_wake = &ts_when; ts_when.tv_sec = when.tv_sec; ts_when.tv_nsec = when.tv_usec * 1000; + } else { ts_wake = NULL; } @@ -644,11 +703,16 @@ int fr_event_loop(fr_event_list_t *el) if (ef->fd < 0) continue; + /* + * Check if the socket is available for writing. + */ + if (ef->write_handler && FD_ISSET(ef->fd, &write_fds)) { + ef->write_handler(el, ef->fd, ef->ctx); + } + if (!FD_ISSET(ef->fd, &read_fds)) continue; ef->handler(el, ef->fd, ef->ctx); - - if (el->changed) break; } #else /* HAVE_KQUEUE */ @@ -673,6 +737,11 @@ int fr_event_loop(fr_event_list_t *el) continue; } + if (el->events[i].filter == EVFILT_WRITE) { + ef->write_handler(el, ef->fd, ef->ctx); + continue; + } + /* * Else it's our event. We only set * EVFILT_READ, so it must be a read @@ -719,7 +788,7 @@ static uint32_t event_rand(void) { uint32_t num; - num = rand_pool.randrsl[rand_pool.randcnt++]; + num = rand_pool.randrsl[rand_pool.randcnt++ & 0xff]; if (rand_pool.randcnt == 256) { fr_isaac(&rand_pool); rand_pool.randcnt = 0; diff --git a/src/lib/hmacmd5.c b/src/lib/hmacmd5.c index 1cca00fa2a..2aad490e30 100644 --- a/src/lib/hmacmd5.c +++ b/src/lib/hmacmd5.c @@ -34,8 +34,9 @@ RCSID("$Id$") #include #include +#include -#ifdef HAVE_OPENSSL_EVP_H +#if defined(HAVE_OPENSSL_EVP_H) && !defined(WITH_FIPS) /** Calculate HMAC using OpenSSL's MD5 implementation * * @param digest Caller digest to be filled in. @@ -49,6 +50,7 @@ void fr_hmac_md5(uint8_t digest[MD5_DIGEST_LENGTH], uint8_t const *text, size_t uint8_t const *key, size_t key_len) { HMAC_CTX *ctx = HMAC_CTX_new(); + unsigned int len = MD5_DIGEST_LENGTH; #ifdef EVP_MD_CTX_FLAG_NON_FIPS_ALLOW /* Since MD5 is not allowed by FIPS, explicitly allow it. */ @@ -57,7 +59,7 @@ void fr_hmac_md5(uint8_t digest[MD5_DIGEST_LENGTH], uint8_t const *text, size_t HMAC_Init_ex(ctx, key, key_len, EVP_md5(), NULL); HMAC_Update(ctx, text, text_len); - HMAC_Final(ctx, digest, NULL); + HMAC_Final(ctx, digest, &len); HMAC_CTX_free(ctx); } #else diff --git a/src/lib/hmacsha1.c b/src/lib/hmacsha1.c index 211470ea35..8711f983b7 100644 --- a/src/lib/hmacsha1.c +++ b/src/lib/hmacsha1.c @@ -13,6 +13,7 @@ RCSID("$Id$") #ifdef HAVE_OPENSSL_EVP_H #include #include +#include #endif #include @@ -35,9 +36,11 @@ void fr_hmac_sha1(uint8_t digest[SHA1_DIGEST_LENGTH], uint8_t const *text, size_ uint8_t const *key, size_t key_len) { HMAC_CTX *ctx = HMAC_CTX_new(); + unsigned int len = SHA1_DIGEST_LENGTH; + HMAC_Init_ex(ctx, key, key_len, EVP_sha1(), NULL); HMAC_Update(ctx, text, text_len); - HMAC_Final(ctx, digest, NULL); + HMAC_Final(ctx, digest, &len); HMAC_CTX_free(ctx); } diff --git a/src/lib/md4.c b/src/lib/md4.c index 0515fca1ae..7169000381 100644 --- a/src/lib/md4.c +++ b/src/lib/md4.c @@ -28,6 +28,7 @@ void fr_md4_calc(uint8_t out[MD4_DIGEST_LENGTH], uint8_t const *in, size_t inlen fr_md4_init(&ctx); fr_md4_update(&ctx, in, inlen); fr_md4_final(out, &ctx); + fr_md4_destroy(&ctx); } #ifndef HAVE_OPENSSL_MD4_H diff --git a/src/lib/md5.c b/src/lib/md5.c index 9858175bd4..b5c1729148 100644 --- a/src/lib/md5.c +++ b/src/lib/md5.c @@ -30,6 +30,7 @@ void fr_md5_calc(uint8_t *out, uint8_t const *in, size_t inlen) fr_md5_init(&ctx); fr_md5_update(&ctx, in, inlen); fr_md5_final(out, &ctx); + fr_md5_destroy(&ctx); } #ifndef HAVE_OPENSSL_MD5_H diff --git a/src/lib/pair.c b/src/lib/pair.c index d711f90c5d..146c82f95b 100644 --- a/src/lib/pair.c +++ b/src/lib/pair.c @@ -45,7 +45,7 @@ static int _fr_pair_free(VALUE_PAIR *vp) { return 0; } -static VALUE_PAIR *fr_pair_alloc(TALLOC_CTX *ctx) +VALUE_PAIR *fr_pair_alloc(TALLOC_CTX *ctx) { VALUE_PAIR *vp; @@ -121,24 +121,7 @@ VALUE_PAIR *fr_pair_afrom_num(TALLOC_CTX *ctx, unsigned int attr, unsigned int v DICT_ATTR const *da; da = dict_attrbyvalue(attr, vendor); - if (!da) { - VALUE_PAIR *vp; - - vp = fr_pair_alloc(ctx); - if (!vp) return NULL; - - /* - * Ensure that the DA is parented by the VP. - */ - da = dict_unknown_afrom_fields(vp, attr, vendor); - if (!da) { - talloc_free(vp); - return NULL; - } - - vp->da = da; - return vp; - } + if (!da) return NULL; return fr_pair_afrom_da(ctx, da); } @@ -286,6 +269,43 @@ void fr_pair_add(VALUE_PAIR **first, VALUE_PAIR *add) i->next = add; } +/** Add a VP to the start of the list. + * + * Links the new VP to 'first', then points 'first' at the new VP. + * + * @param[in] first VP in linked list. Will add new VP to the start of this list. + * @param[in] add VP to add to list. + */ +void fr_pair_prepend(VALUE_PAIR **first, VALUE_PAIR *add) +{ + VALUE_PAIR *i; + + if (!add) return; + + VERIFY_VP(add); + + if (*first == NULL) { + *first = add; + return; + } + + /* + * Find the end of the list to be prepended + */ + for (i = add; i->next; i = i->next) { +#ifdef WITH_VERIFY_PTR + VERIFY_VP(i); + /* + * The same VP should never by added multiple times + * to the same list. + */ + fr_assert(*first != i); +#endif + } + i->next = *first; + *first = add; +} + /** Replace all matching VPs * * Walks over 'first', and replaces the first VP that matches 'replace'. @@ -851,13 +871,15 @@ void fr_pair_steal(TALLOC_CTX *ctx, VALUE_PAIR *vp) * @param[in] ctx for talloc * @param[in,out] to destination list. * @param[in,out] from source list. + * @param[in] op operator for move. * * @see radius_pairmove */ -void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from) +void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from, FR_TOKEN op) { VALUE_PAIR *i, *found; VALUE_PAIR *head_new, **tail_new; + VALUE_PAIR *head_prepend; VALUE_PAIR **tail_from; if (!to || !from || !*from) return; @@ -871,6 +893,12 @@ void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from) head_new = NULL; tail_new = &head_new; + /* + * Any attributes that are requested to be prepended + * are added to a temporary list here + */ + head_prepend = NULL; + /* * We're looping over the "from" list, moving some * attributes out, but leaving others in place. @@ -983,13 +1011,36 @@ void fr_pair_list_move(TALLOC_CTX *ctx, VALUE_PAIR **to, VALUE_PAIR **from) fr_pair_steal(ctx, i); tail_new = &(i->next); continue; + case T_OP_PREPEND: + i->next = head_prepend; + head_prepend = i; + fr_pair_steal(ctx, i); + continue; } } /* loop over the "from" list. */ /* - * Take the "new" list, and append it to the "to" list. + * If the op parameter was prepend, add the "new" list + * attributes first as those whose individual operator + * is prepend should be prepended to the resulting list */ - fr_pair_add(to, head_new); + if (op == T_OP_PREPEND) { + fr_pair_prepend(to, head_new); + } + + /* + * If there are any items in the prepend list prepend + * it to the "to" list + */ + fr_pair_prepend(to, head_prepend); + + /* + * If the op parameter was not prepend, take the "new" + * list, and append it to the "to" list. + */ + if (op != T_OP_PREPEND) { + fr_pair_add(to, head_new); + } } /** Move matching pairs between VALUE_PAIR lists @@ -1823,7 +1874,7 @@ FR_TOKEN fr_pair_raw_from_str(char const **ptr, VALUE_PAIR_RAW *raw) break; default: - fr_strerror_printf("Failed to find expected value on right hand side"); + fr_strerror_printf("Failed to find expected value on right hand side in %s", raw->l_opand); return T_INVALID; } diff --git a/src/lib/print.c b/src/lib/print.c index 9ac927358b..57455b6f30 100644 --- a/src/lib/print.c +++ b/src/lib/print.c @@ -495,33 +495,28 @@ char *vp_aprints_type(TALLOC_CTX *ctx, PW_TYPE type) * @param out Where to write the string. * @param outlen Length of output buffer. * @param vp to print. + * @param raw_value if true, the raw value is printed and not the enumerated attribute value * @return the length of data written to out, or a value >= outlen on truncation. */ -size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp) +size_t vp_prints_value_json(char *out, size_t outlen, VALUE_PAIR const *vp, bool raw_value) { char const *q; size_t len, freespace = outlen; + /* attempt to print raw_value when has_value is false, or raw_value is false, but only + if has_tag is also false */ + bool raw = (raw_value || !vp->da->flags.has_value) && !vp->da->flags.has_tag; - if (!vp->da->flags.has_tag) { + if (raw) { switch (vp->da->type) { case PW_TYPE_INTEGER: - if (vp->da->flags.has_value) break; - return snprintf(out, freespace, "%u", vp->vp_integer); case PW_TYPE_SHORT: - if (vp->da->flags.has_value) break; - return snprintf(out, freespace, "%u", (unsigned int) vp->vp_short); case PW_TYPE_BYTE: - if (vp->da->flags.has_value) break; - return snprintf(out, freespace, "%u", (unsigned int) vp->vp_byte); - case PW_TYPE_SIGNED: - return snprintf(out, freespace, "%d", vp->vp_signed); - default: break; } @@ -775,7 +770,7 @@ char *vp_aprints(TALLOC_CTX *ctx, VALUE_PAIR const *vp, char quote) value = vp_aprints_value(ctx, vp, quote); - if (vp->da->flags.has_tag) { + if (vp->da->flags.has_tag && (vp->tag != TAG_ANY)) { if (quote && (vp->da->type == PW_TYPE_STRING)) { str = talloc_asprintf(ctx, "%s:%d %s %c%s%c", vp->da->name, vp->tag, token, quote, value, quote); } else { diff --git a/src/lib/radius.c b/src/lib/radius.c index 3881111f7d..6dcee141e2 100644 --- a/src/lib/radius.c +++ b/src/lib/radius.c @@ -28,6 +28,7 @@ RCSID("$Id$") #include #include +#include #include #include @@ -142,8 +143,9 @@ char const *fr_packet_codes[FR_MAX_PACKET_CODE] = { "47", "48", "49", - "IP-Address-Allocate", - "IP-Address-Release", //!< 50 + "IP-Address-Allocate", //!< 50 + "IP-Address-Release", + "Protocol-Error", }; @@ -528,6 +530,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 +566,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 +577,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 +590,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 +661,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 +672,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 +690,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 +1563,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 +1607,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. */ @@ -1700,6 +1795,14 @@ int rad_vp2attr(RADIUS_PACKET const *packet, RADIUS_PACKET const *original, return rad_vp2vsa(packet, original, secret, pvp, start, room); } +static const bool code2ma[FR_MAX_PACKET_CODE] = { + [ PW_CODE_ACCESS_REQUEST ] = true, + [ PW_CODE_ACCESS_ACCEPT ] = true, + [ PW_CODE_ACCESS_REJECT ] = true, + [ PW_CODE_ACCESS_CHALLENGE ] = true, + [ PW_CODE_STATUS_SERVER ] = true, + [ PW_CODE_PROTOCOL_ERROR ] = true, +}; /** Encode a packet * @@ -1712,6 +1815,7 @@ int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original, uint16_t total_length; int len; VALUE_PAIR const *reply; + bool seen_ma = false; /* * A 4K packet, aligned on 64-bits. @@ -1775,6 +1879,27 @@ int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original, * memcpy. */ + /* + * Always add Message-Authenticator for replies to + * Access-Request packets, and for all Access-Accept, + * Access-Reject, Access-Challenge. + * + * It must be the FIRST attribute in the packet. + */ + if (!packet->tls && + ((code2ma[packet->code]) || (original && code2ma[original->code]))) { + seen_ma = true; + + packet->offset = RADIUS_HDR_LEN; + + ptr[0] = PW_MESSAGE_AUTHENTICATOR; + ptr[1] = 18; + memset(ptr + 2, 0, 16); + + ptr += 18; + total_length += 18; + } + /* * Loop over the reply attributes for the packet. */ @@ -1832,6 +1957,14 @@ int rad_encode(RADIUS_PACKET *packet, RADIUS_PACKET const *original, * length and initial value. */ if (!reply->da->vendor && (reply->da->attr == PW_MESSAGE_AUTHENTICATOR)) { + /* + * We have already encoded the Message-Authenticator, don't do it again. + */ + if (seen_ma) { + reply = reply->next; + continue; + } + if (room < 18) break; /* @@ -1973,11 +2106,7 @@ int rad_sign(RADIUS_PACKET *packet, RADIUS_PACKET const *original, case PW_CODE_ACCOUNTING_REQUEST: case PW_CODE_DISCONNECT_REQUEST: - case PW_CODE_DISCONNECT_ACK: - case PW_CODE_DISCONNECT_NAK: case PW_CODE_COA_REQUEST: - case PW_CODE_COA_ACK: - case PW_CODE_COA_NAK: memset(hdr->vector, 0, AUTH_VECTOR_LEN); break; @@ -1985,6 +2114,10 @@ int rad_sign(RADIUS_PACKET *packet, RADIUS_PACKET const *original, case PW_CODE_ACCESS_ACCEPT: case PW_CODE_ACCESS_REJECT: case PW_CODE_ACCESS_CHALLENGE: + case PW_CODE_DISCONNECT_ACK: + case PW_CODE_DISCONNECT_NAK: + case PW_CODE_COA_ACK: + case PW_CODE_COA_NAK: memcpy(hdr->vector, original->vector, AUTH_VECTOR_LEN); break; @@ -2036,6 +2169,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 +2293,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 +2333,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. @@ -2323,6 +2459,8 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) radius_packet_t *hdr; char host_ipaddr[128]; bool require_ma = false; + bool limit_proxy_state = false; + bool seen_proxy_state = false; bool seen_ma = false; uint32_t num_attributes; decode_fail_t failure = DECODE_FAIL_NONE; @@ -2371,15 +2509,23 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) } /* - * Message-Authenticator is required in Status-Server - * packets, otherwise they can be trivially forged. + * If the caller requires Message-Authenticator, then set + * the flag. + * + * We also require Message-Authenticator if the packet + * code is Status-Server. + * + * If we're receiving packets from a proxy socket, then + * require Message-Authenticator for Access-* replies, + * and for Protocol-Error. */ - if (hdr->code == PW_CODE_STATUS_SERVER) require_ma = true; + require_ma = ((flags & 0x01) != 0) || (hdr->code == PW_CODE_STATUS_SERVER) || (((flags & 0x08) != 0) && code2ma[hdr->code]); /* - * It's also required if the caller asks for it. + * We only limit Proxy-State if we're not requiring + * Message-Authenticator. */ - if (flags) require_ma = true; + limit_proxy_state = ((flags & 0x04) != 0) && !require_ma; /* * Repeat the length checks. This time, instead of @@ -2534,6 +2680,7 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) case PW_EAP_MESSAGE: require_ma = true; eap = true; + packet->eap_message = true; break; case PW_USER_PASSWORD: @@ -2542,6 +2689,11 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) non_eap = true; break; + case PW_PROXY_STATE: + seen_proxy_state = true; + packet->proxy_state = true; + break; + case PW_MESSAGE_AUTHENTICATOR: if (attr[1] != 2 + AUTH_VECTOR_LEN) { FR_DEBUG_STRERROR_PRINTF("Malformed RADIUS packet from host %s: Message-Authenticator has invalid length %d", @@ -2553,6 +2705,7 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) goto finish; } seen_ma = true; + packet->message_authenticator = true; break; } @@ -2609,7 +2762,19 @@ bool rad_packet_ok(RADIUS_PACKET *packet, int flags, decode_fail_t *reason) * Message-Authenticator attributes. */ if (require_ma && !seen_ma) { - FR_DEBUG_STRERROR_PRINTF("Insecure packet from host %s: Packet does not contain required Message-Authenticator attribute", + FR_DEBUG_STRERROR_PRINTF("Insecure packet from host %s: Packet does not contain required Message-Authenticator attribute. You may need to set \"require_message_authenticator = no\" in the configuration.", + inet_ntop(packet->src_ipaddr.af, + &packet->src_ipaddr.ipaddr, + host_ipaddr, sizeof(host_ipaddr))); + failure = DECODE_FAIL_MA_MISSING; + goto finish; + } + + /* + * The client is a NAS which shouldn't send Proxy-State, but it did! + */ + if (limit_proxy_state && seen_proxy_state && !seen_ma) { + FR_DEBUG_STRERROR_PRINTF("Insecure packet from host %s: Packet does not contain required Message-Authenticator attribute, but still has one or more Proxy-State attributes", inet_ntop(packet->src_ipaddr.af, &packet->src_ipaddr.ipaddr, host_ipaddr, sizeof(host_ipaddr))); @@ -2896,6 +3061,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 * */ @@ -3014,6 +3288,7 @@ ssize_t rad_data2vp_tlvs(TALLOC_CTX *ctx, tlv_len = data2vp(ctx, packet, original, secret, child, data + 2, data[1] - 2, data[1] - 2, tail); if (tlv_len < 0) { + dict_attr_free(&child); /* only frees unknowns */ fr_pair_list_free(&head); return -1; } @@ -3101,7 +3376,10 @@ static ssize_t data2vp_vsa(TALLOC_CTX *ctx, RADIUS_PACKET *packet, attrlen - (dv->type + dv->length), attrlen - (dv->type + dv->length), pvp); - if (my_len < 0) return my_len; + if (my_len < 0) { + dict_attr_free(&da); /* only frees unknowns */ + return my_len; + } return attrlen; } @@ -3154,7 +3432,10 @@ static ssize_t data2vp_extended(TALLOC_CTX *ctx, RADIUS_PACKET *packet, rcode = data2vp(ctx, packet, original, secret, child, data, attrlen, attrlen, pvp); - if (rcode < 0) return rcode; + if (rcode < 0) { + dict_attr_free(&child); /* only frees unknowns */ + return rcode; + } return attrlen; } @@ -3165,7 +3446,6 @@ static ssize_t data2vp_extended(TALLOC_CTX *ctx, RADIUS_PACKET *packet, rcode = data2vp(ctx, packet, original, secret, da, data + 2, attrlen - 2, attrlen - 2, pvp); - if ((rcode < 0) || (((size_t) rcode + 2) != attrlen)) goto raw; /* didn't decode all of the data */ return attrlen; } @@ -3307,7 +3587,10 @@ static ssize_t data2vp_wimax(TALLOC_CTX *ctx, rcode = data2vp(ctx, packet, original, secret, child, data, attrlen, attrlen, pvp); - if (rcode < 0) return rcode; + if (rcode < 0) { + dict_attr_free(&child); /* only frees unknowns */ + return rcode; + } return attrlen; } @@ -3503,7 +3786,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 +3893,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; } /* @@ -3874,8 +4150,10 @@ ssize_t data2vp(TALLOC_CTX *ctx, /* * This requires a whole lot more work. */ - return data2vp_extended(ctx, packet, original, secret, child, - start, attrlen, packetlen, pvp); + rcode = data2vp_extended(ctx, packet, original, secret, child, + start, attrlen, packetlen, pvp); + if (rcode < 0) dict_attr_free(&child); /* only frees unknowns */ + return rcode; case PW_TYPE_EVS: if (datalen < 6) goto raw; /* vid, vtype, value */ @@ -3926,44 +4204,47 @@ 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; + if (!vp) { + dict_attr_free(&da); /* only frees unknowns */ + 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); @@ -4068,10 +4349,13 @@ ssize_t data2vp(TALLOC_CTX *ctx, fail: #endif default: + dict_attr_free(&da); /* only frees unknowns */ fr_pair_list_free(&vp); fr_strerror_printf("Internal sanity check %d", __LINE__); return -1; } + +done: vp->type = VT_DATA; *pvp = vp; @@ -4112,6 +4396,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 @@ -4120,7 +4409,10 @@ ssize_t rad_attr2vp(TALLOC_CTX *ctx, */ rcode = data2vp(ctx, packet, original, secret, da, data + 2, data[1] - 2, length - 2, pvp); - if (rcode < 0) return rcode; + if (rcode < 0) { + dict_attr_free(&da); /* only frees unknowns */ + return rcode; + } return 2 + rcode; } @@ -4261,7 +4553,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 +4677,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 +4690,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 +4702,9 @@ int rad_pwencode(char *passwd, size_t *pwlen, char const *secret, } } + fr_md5_destroy(&old); + fr_md5_destroy(&context); + return 0; } @@ -4441,8 +4737,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 +4749,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 +4757,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 +4770,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 +4909,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 +4938,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 @@ -4647,6 +4948,8 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, reallen = passwd[2] ^ digest[0]; if (reallen > encrypted_len) { fr_strerror_printf("tunnel password is too long for the attribute"); + fr_md5_destroy(&old); + fr_md5_destroy(&context); return -1; } @@ -4657,7 +4960,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 +4972,9 @@ ssize_t rad_tunnel_pwdecode(uint8_t *passwd, size_t *pwlen, char const *secret, *pwlen = reallen; passwd[reallen] = 0; + fr_md5_destroy(&old); + fr_md5_destroy(&context); + return reallen; } diff --git a/src/lib/token.c b/src/lib/token.c index 8ae41b392f..a7978622e6 100644 --- a/src/lib/token.c +++ b/src/lib/token.c @@ -42,6 +42,7 @@ const FR_NAME_NUMBER fr_tokens[] = { { "=*", T_OP_CMP_TRUE, }, { "!*", T_OP_CMP_FALSE, }, { "==", T_OP_CMP_EQ, }, + { "^=", T_OP_PREPEND, }, { "=", T_OP_EQ, }, { "!=", T_OP_NE, }, { ">=", T_OP_GE, }, @@ -78,9 +79,10 @@ const bool fr_assignment_op[] = { false, /* =* 20 */ false, /* !* */ false, /* == */ - false, /* # */ - false, /* bare word */ - false, /* "foo" 25 */ + true, /* ^= */ + false, /* # */ + false, /* bare word 25 */ + false, /* "foo" */ false, /* 'foo' */ false, /* `foo` */ false @@ -108,12 +110,13 @@ const bool fr_equality_op[] = { true, /* < */ true, /* =~ */ true, /* !~ */ - true, /* =* 20 */ + true, /* =* 20 */ true, /* !* */ true, /* == */ - false, /* # */ - false, /* bare word */ - false, /* "foo" 25 */ + false, /* ^= */ + false, /* # */ + false, /* bare word 25 */ + false, /* "foo" */ false, /* 'foo' */ false, /* `foo` */ false @@ -144,9 +147,10 @@ const bool fr_str_tok[] = { false, /* =* 20 */ false, /* !* */ false, /* == */ - false, /* # */ - true, /* bare word */ - true, /* "foo" 25 */ + false, /* ^= */ + false, /* # */ + true, /* bare word 25 */ + true, /* "foo" */ true, /* 'foo' */ true, /* `foo` */ false diff --git a/src/main/cb.c b/src/main/cb.c index 4ae14e575b..f8b2edbecc 100644 --- a/src/main/cb.c +++ b/src/main/cb.c @@ -29,45 +29,94 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ #ifdef WITH_TLS void cbtls_info(SSL const *s, int where, int ret) { - char const *str, *state; + char const *role, *state; REQUEST *request = SSL_get_ex_data(s, FR_TLS_EX_INDEX_REQUEST); if ((where & ~SSL_ST_MASK) & SSL_ST_CONNECT) { - str="TLS_connect"; + role = "Client "; } else if (((where & ~SSL_ST_MASK)) & SSL_ST_ACCEPT) { - str="TLS_accept"; + role = "Server "; } else { - str="(other)"; + role = ""; } state = SSL_state_string_long(s); state = state ? state : ""; if ((where & SSL_CB_LOOP) || (where & SSL_CB_HANDSHAKE_START) || (where & SSL_CB_HANDSHAKE_DONE)) { - RDEBUG2("%s: %s", str, state); + if (RDEBUG_ENABLED3) { + char const *abbrv = SSL_state_string(s); + size_t len; +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + STACK_OF(SSL_CIPHER) *client_ciphers; + STACK_OF(SSL_CIPHER) *server_ciphers; +#endif + + /* + * Trim crappy OpenSSL state strings... + */ + len = strlen(abbrv); + if ((len > 1) && (abbrv[len - 1] == ' ')) len--; + + RDEBUG3("(TLS) Handshake state [%.*s] - %s%s (%d)", + (int)len, abbrv, role, state, SSL_get_state(s)); + + /* + * After a ClientHello, list all the proposed ciphers from the client + */ +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + if (SSL_get_state(s) == TLS_ST_SR_CLNT_HELLO) { + int i; + int num_ciphers; + const SSL_CIPHER *this_cipher; + + server_ciphers = SSL_get_ciphers(s); + if (server_ciphers) { + RDEBUG3("Server preferred ciphers (by priority)"); + num_ciphers = sk_SSL_CIPHER_num(server_ciphers); + for (i = 0; i < num_ciphers; i++) { + this_cipher = sk_SSL_CIPHER_value(server_ciphers, i); + RDEBUG3("(TLS) [%i] %s", i, SSL_CIPHER_get_name(this_cipher)); + } + } + + client_ciphers = SSL_get_client_ciphers(s); + if (client_ciphers) { + RDEBUG3("Client preferred ciphers (by priority)"); + num_ciphers = sk_SSL_CIPHER_num(client_ciphers); + for (i = 0; i < num_ciphers; i++) { + this_cipher = sk_SSL_CIPHER_value(client_ciphers, i); + RDEBUG3("(TLS) [%i] %s", i, SSL_CIPHER_get_name(this_cipher)); + } + } + } +#endif + } else { + RDEBUG2("(TLS) Handshake state - %s%s", role, state); + } return; } if (where & SSL_CB_ALERT) { if ((ret & 0xff) == SSL_AD_CLOSE_NOTIFY) return; - RERROR("TLS Alert %s:%s:%s", (where & SSL_CB_READ) ? "read": "write", + RERROR("(TLS) Alert %s:%s:%s", (where & SSL_CB_READ) ? "read": "write", SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); return; } if (where & SSL_CB_EXIT) { if (ret == 0) { - RERROR("%s: Failed in %s", str, state); + RERROR("(TLS) %s: Failed in %s", role, state); return; } if (ret < 0) { if (SSL_want_read(s)) { - RDEBUG2("%s: Need to read more data: %s", str, state); + RDEBUG2("(TLS) %s: Need to read more data: %s", role, state); return; } - ERROR("tls: %s: Error in %s", str, state); + RERROR("(TLS) %s: Error in %s", role, state); } } } @@ -87,14 +136,24 @@ void cbtls_msg(int write_p, int msg_version, int content_type, * content types. Which breaks our tracking of * the SSL Session state. */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L if ((msg_version == 0) && (content_type > UINT8_MAX)) { - DEBUG4("Ignoring cbtls_msg call with pseudo content type %i, version %i", +#else + /* + * "...we do not see the need to resolve application breakage + * just because the documentation now is incorrect." + * + * https://github.com/openssl/openssl/issues/17262 + */ + if ((content_type > UINT8_MAX) && (content_type != SSL3_RT_INNER_CONTENT_TYPE)) { +#endif + DEBUG4("(TLS) Ignoring cbtls_msg call with pseudo content type %i, version %i", content_type, msg_version); return; } if ((write_p != 0) && (write_p != 1)) { - DEBUG4("Ignoring cbtls_msg call with invalid write_p %d", write_p); + DEBUG4("(TLS) Ignoring cbtls_msg call with invalid write_p %d", write_p); return; } @@ -104,6 +163,25 @@ void cbtls_msg(int write_p, int msg_version, int content_type, */ if (!state) return; + if (rad_debug_lvl > 3) { + size_t i, j, data_len = len; + char buffer[3*16 + 1]; + uint8_t const *in = inbuf; + + DEBUG("(TLS) Received %zu bytes of TLS data", len); + if (data_len > 256) data_len = 256; + + for (i = 0; i < data_len; i += 16) { + for (j = 0; j < 16; j++) { + if ((i + j) >= data_len) break; + + sprintf(buffer + 3 * j, "%02x ", in[i + j]); + } + + DEBUG("(TLS) %s", buffer); + } + } + /* * 0 - received (from peer) * 1 - sending (to peer) @@ -111,7 +189,6 @@ void cbtls_msg(int write_p, int msg_version, int content_type, state->info.origin = write_p; state->info.content_type = content_type; state->info.record_len = len; - state->info.version = msg_version; state->info.initialized = true; if (content_type == SSL3_RT_ALERT) { @@ -124,6 +201,12 @@ void cbtls_msg(int write_p, int msg_version, int content_type, state->info.alert_level = 0x00; state->info.alert_description = 0x00; +#if OPENSSL_VERSION_NUMBER >= 0x10101000L + } else if (content_type == SSL3_RT_INNER_CONTENT_TYPE && buf[0] == SSL3_RT_APPLICATION_DATA) { + /* let tls_ack_handler set application_data */ + state->info.content_type = SSL3_RT_HANDSHAKE; +#endif + #ifdef SSL3_RT_HEARTBEAT } else if (content_type == TLS1_RT_HEARTBEAT) { uint8_t *p = buf; @@ -141,16 +224,24 @@ void cbtls_msg(int write_p, int msg_version, int content_type, } #endif } + tls_session_information(state); } int cbtls_password(char *buf, - int num UNUSED, + int num, int rwflag UNUSED, void *userdata) { - strcpy(buf, (char *)userdata); - return(strlen((char *)userdata)); + size_t len; + + len = strlcpy(buf, (char *)userdata, num); + if (len >= (size_t) num) { + ERROR("Password too long. Maximum length is %i bytes", num - 1); + return 0; + } + + return len; } #endif diff --git a/src/main/client.c b/src/main/client.c index 6228438c47..4d67a27688 100644 --- a/src/main/client.c +++ b/src/main/client.c @@ -41,11 +41,16 @@ RCSID("$Id$") #endif struct radclient_list { + char const *name; /* name of this list */ + char const *server; /* virtual server associated with this client list */ + /* * FIXME: One set of trees for IPv4, and another for IPv6? */ rbtree_t *trees[129]; /* for 0..128, inclusive. */ uint32_t min_prefix; + + bool parsed; }; @@ -145,6 +150,15 @@ RADCLIENT_LIST *client_list_init(CONF_SECTION *cs) clients->min_prefix = 128; + /* + * Associate the "clients" list with the virtual server. + */ + if (cs && (cf_data_add(cs, "clients", clients, NULL) < 0)) { + ERROR("Failed to associate client list with section %s\n", cf_section_name1(cs)); + client_list_free(clients); + return false; + } + return clients; } @@ -161,6 +175,18 @@ bool client_add(RADCLIENT_LIST *clients, RADCLIENT *client) if (!client) return false; + /* + * Initialize the global list, if not done already. + */ + if (!root_clients) { + root_clients = cf_data_find(main_config.config, "clients"); + if (!root_clients) root_clients = client_list_init(main_config.config); + if (!root_clients) { + ERROR("Cannot add client - failed creating client list"); + return false; + } + } + /* * Hack to fixup wildcard clients * @@ -188,66 +214,85 @@ bool client_add(RADCLIENT_LIST *clients, RADCLIENT *client) /* * If the client also defines a server, do that now. */ - if (client->defines_coa_server) if (!realm_home_server_add(client->coa_server)) return false; + if (client->defines_coa_server) if (!realm_home_server_add(client->coa_home_server)) return false; /* - * If "clients" is NULL, it means add to the global list, - * unless we're trying to add it to a virtual server... + * If there's no client list, BUT there's a virtual + * server, try to add the client to the appropriate + * "clients" section for that virtual server. */ - if (!clients) { - if (client->server != NULL) { - CONF_SECTION *cs; - CONF_SECTION *subcs; - - cs = cf_section_sub_find_name2(main_config.config, "server", client->server); - if (!cs) { - ERROR("Failed to find virtual server %s", client->server); - return false; - } + if (!clients && client->server) { + CONF_SECTION *cs; + CONF_SECTION *subcs; + CONF_PAIR *cp; + char const *section_name; - /* - * If this server has no "listen" section, add the clients - * to the global client list. - */ - subcs = cf_section_sub_find(cs, "listen"); - if (!subcs) { - DEBUG("No 'listen' section in virtual server %s. Adding client to global client list", - client->server); - goto global_clients; - } + cs = cf_section_sub_find_name2(main_config.config, "server", client->server); + if (!cs) { + ERROR("Cannot add client - virtual server %s does not exist", client->server); + return false; + } - /* - * If the client list already exists, use that. - * Otherwise, create a new client list. - */ - clients = cf_data_find(cs, "clients"); - if (!clients) { - clients = client_list_init(cs); - if (!clients) { - ERROR("Out of memory"); - return false; - } + /* + * If this server has no "listen" section, add the clients + * to the global client list. + */ + subcs = cf_section_sub_find(cs, "listen"); + if (!subcs) { + DEBUG("No 'listen' section in virtual server %s. Adding client to global client list", + client->server); + goto check_list; + } - if (cf_data_add(cs, "clients", clients, (void (*)(void *)) client_list_free) < 0) { - ERROR("Failed to associate clients with virtual server %s", client->server); - client_list_free(clients); - return false; - } - } + cp = cf_pair_find(subcs, "clients"); + if (!cp) { + DEBUG("No 'clients' configuration item in first listener of virtual server %s. Adding client to global client list", + client->server); + goto check_list; + } - } else { - global_clients: - /* - * Initialize the global list, if not done already. - */ - if (!root_clients) { - root_clients = client_list_init(NULL); - if (!root_clients) return false; - } - clients = root_clients; + /* + * Duplicate the lookup logic in common_socket_parse() + * + * Explicit list given: use it. + */ + section_name = cf_pair_value(cp); + if (!section_name) goto check_list; + + subcs = cf_section_sub_find_name2(main_config.config, "clients", section_name); + if (!subcs) { + subcs = cf_section_find(section_name); + } + if (!subcs) { + cf_log_err_cs(cs, + "Failed to find clients %s {...}", + section_name); + return false; + } + + DEBUG("Adding client to client list %s", section_name); + + /* + * If the client list already exists, use that. + * Otherwise, create a new client list. + * + * @todo - add the client to _all_ listeners? + */ + clients = cf_data_find(subcs, "clients"); + if (clients) goto check_list; + + clients = client_list_init(subcs); + if (!clients) { + ERROR("Cannot add client - failed creating client list %s for server %s", section_name, + client->server); + return false; } } +check_list: + if (!clients) clients = root_clients; + client->list = clients; + /* * Create a tree for it. */ @@ -280,10 +325,11 @@ bool client_add(RADCLIENT_LIST *clients, RADCLIENT *client) #endif #ifdef WITH_COA namecmp(coa_name) && - (old->coa_server == client->coa_server) && - (old->coa_pool == client->coa_pool) && + (old->coa_home_server == client->coa_home_server) && + (old->coa_home_pool == client->coa_home_pool) && #endif - (old->message_authenticator == client->message_authenticator)) { + (old->require_ma == client->require_ma) && + (old->limit_proxy_state == client->limit_proxy_state)) { WARN("Ignoring duplicate client %s", client->longname); client_free(client); return true; @@ -445,6 +491,8 @@ static fr_ipaddr_t cl_ipaddr; static uint32_t cl_netmask; static char const *cl_srcipaddr = NULL; static char const *hs_proto = NULL; +static char const *require_message_authenticator = NULL; +static char const *limit_proxy_state = NULL; #ifdef WITH_TCP static CONF_PARSER limit_config[] = { @@ -467,7 +515,8 @@ static const CONF_PARSER client_config[] = { { "src_ipaddr", FR_CONF_POINTER(PW_TYPE_STRING, &cl_srcipaddr), NULL }, - { "require_message_authenticator", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, message_authenticator), "no" }, + { "require_message_authenticator", FR_CONF_POINTER(PW_TYPE_STRING| PW_TYPE_IGNORE_DEFAULT, &require_message_authenticator), NULL }, + { "limit_proxy_state", FR_CONF_POINTER(PW_TYPE_STRING| PW_TYPE_IGNORE_DEFAULT, &limit_proxy_state), NULL }, { "secret", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, RADCLIENT, secret), NULL }, { "shortname", FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, shortname), NULL }, @@ -512,14 +561,34 @@ RADCLIENT_LIST *client_list_parse_section(CONF_SECTION *section, UNUSED bool tls * it. Otherwise create a new one. */ clients = cf_data_find(section, "clients"); - if (clients) return clients; - - clients = client_list_init(section); - if (!clients) return NULL; + if (clients) { + /* + * Modules are initialized before the listeners. + * Which means that we MIGHT have read clients + * from SQL before parsing this "clients" + * section. So there may already be a clients + * list. + * + * But the list isn't _our_ list that we parsed, + * so we still need to parse the clients here. + */ + if (clients->parsed) return clients; + } else { + clients = client_list_init(section); + if (!clients) return NULL; + } - if (cf_top_section(section) == section) global = true; + if (cf_top_section(section) == section) { + global = true; + clients->name = "global"; + clients->server = NULL; + } - if (strcmp("server", cf_section_name1(section)) == 0) in_server = true; + if (strcmp("server", cf_section_name1(section)) == 0) { + clients->name = NULL; + clients->server = cf_section_name2(section); + in_server = true; + } for (cs = cf_subsection_find_next(section, NULL, "client"); cs; @@ -592,8 +661,8 @@ RADCLIENT_LIST *client_list_parse_section(CONF_SECTION *section, UNUSED bool tls * Check for valid characters */ for (p = dp->d_name; *p != '\0'; p++) { - if (isalpha((int)*p) || - isdigit((int)*p) || + if (isalpha((uint8_t)*p) || + isdigit((uint8_t)*p) || (*p == ':') || (*p == '.')) continue; break; @@ -632,15 +701,6 @@ RADCLIENT_LIST *client_list_parse_section(CONF_SECTION *section, UNUSED bool tls } - /* - * Associate the clients structure with the section. - */ - if (cf_data_add(section, "clients", clients, NULL) < 0) { - cf_log_err_cs(section, "Failed to associate clients with section %s", cf_section_name1(section)); - client_list_free(clients); - return NULL; - } - /* * Replace the global list of clients with the new one. * The old one is still referenced from the original @@ -648,6 +708,7 @@ RADCLIENT_LIST *client_list_parse_section(CONF_SECTION *section, UNUSED bool tls */ if (global) root_clients = clients; + clients->parsed = true; return clients; } @@ -663,7 +724,7 @@ static const CONF_PARSER dynamic_config[] = { { "FreeRADIUS-Client-Src-IP-Address", FR_CONF_OFFSET(PW_TYPE_IPV4_ADDR, RADCLIENT, src_ipaddr), NULL }, { "FreeRADIUS-Client-Src-IPv6-Address", FR_CONF_OFFSET(PW_TYPE_IPV6_ADDR, RADCLIENT, src_ipaddr), NULL }, - { "FreeRADIUS-Client-Require-MA", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, message_authenticator), NULL }, + { "FreeRADIUS-Client-Require-MA", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, RADCLIENT, dynamic_require_ma), NULL }, { "FreeRADIUS-Client-Secret", FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, secret), "" }, { "FreeRADIUS-Client-Shortname", FR_CONF_OFFSET(PW_TYPE_STRING, RADCLIENT, shortname), "" }, @@ -824,7 +885,7 @@ int client_map_section(CONF_SECTION *out, CONF_SECTION const *map, client_value_ * @param ctx to allocate new clients in. * @param cs to process as a client. * @param in_server Whether the client should belong to a specific virtual server. - * @param with_coa If true and coa_server or coa_pool aren't specified automatically, + * @param with_coa If true and coa_home_server or coa_home_pool aren't specified automatically, * create a coa home_server section and add it to the client CONF_SECTION. * @return new RADCLIENT struct. */ @@ -845,8 +906,19 @@ RADCLIENT *client_afrom_cs(TALLOC_CTX *ctx, CONF_SECTION *cs, bool in_server, bo c = talloc_zero(ctx, RADCLIENT); c->cs = cs; + /* + * Set the "require message authenticator" and "limit + * proxy state" flags from the global default. If the + * configuration item exists, AND is set, it will + * over-ride the flag. + */ + c->require_ma = main_config.require_ma; + c->limit_proxy_state = main_config.limit_proxy_state; + memset(&cl_ipaddr, 0, sizeof(cl_ipaddr)); cl_netmask = 255; + require_message_authenticator = NULL; + limit_proxy_state = NULL; if (cf_section_parse(cs, c, client_config) < 0) { cf_log_err_cs(cs, "Error parsing client section"); @@ -856,6 +928,8 @@ RADCLIENT *client_afrom_cs(TALLOC_CTX *ctx, CONF_SECTION *cs, bool in_server, bo hs_proto = NULL; cl_srcipaddr = NULL; #endif + require_message_authenticator = NULL; + limit_proxy_state = NULL; return NULL; } @@ -1054,11 +1128,11 @@ RADCLIENT *client_afrom_cs(TALLOC_CTX *ctx, CONF_SECTION *cs, bool in_server, bo cp = cf_pair_find(cs, "coa_server"); if (cp) { c->coa_name = cf_pair_value(cp); - c->coa_pool = home_pool_byname(c->coa_name, HOME_TYPE_COA); - if (!c->coa_pool) { - c->coa_server = home_server_byname(c->coa_name, HOME_TYPE_COA); + c->coa_home_pool = home_pool_byname(c->coa_name, HOME_TYPE_COA); + if (!c->coa_home_pool) { + c->coa_home_server = home_server_byname(c->coa_name, HOME_TYPE_COA); } - if (!c->coa_pool && !c->coa_server) { + if (!c->coa_home_pool && !c->coa_home_server) { cf_log_err_cs(cs, "No such home_server or home_server_pool \"%s\"", c->coa_name); goto error; } @@ -1096,7 +1170,7 @@ RADCLIENT *client_afrom_cs(TALLOC_CTX *ctx, CONF_SECTION *cs, bool in_server, bo rad_assert(home->type == HOME_TYPE_COA); - c->coa_server = home; + c->coa_home_server = home; c->defines_coa_server = true; } } @@ -1114,6 +1188,16 @@ done_coa: } #endif + if (fr_bool_auto_parse(cf_pair_find(cs, "require_message_authenticator"), &c->require_ma, require_message_authenticator) < 0) { + goto error; + } + + if (c->require_ma != FR_BOOL_TRUE) { + if (fr_bool_auto_parse(cf_pair_find(cs, "limit_proxy_state"), &c->limit_proxy_state, limit_proxy_state) < 0) { + goto error; + } + } + return c; } @@ -1158,7 +1242,7 @@ RADCLIENT *client_afrom_query(TALLOC_CTX *ctx, char const *identifier, char cons if (shortname) c->shortname = talloc_typed_strdup(c, shortname); if (type) c->nas_type = talloc_typed_strdup(c, type); if (server) c->server = talloc_typed_strdup(c, server); - c->message_authenticator = require_ma; + c->require_ma = require_ma; return c; } @@ -1175,7 +1259,6 @@ RADCLIENT *client_afrom_request(RADCLIENT_LIST *clients, REQUEST *request) int i, *pi; char **p; RADCLIENT *c; - CONF_PAIR *cp = NULL; char buffer[128]; vp_cursor_t cursor; @@ -1206,6 +1289,7 @@ RADCLIENT *client_afrom_request(RADCLIENT_LIST *clients, REQUEST *request) for (i = 0; dynamic_config[i].name != NULL; i++) { DICT_ATTR const *da; char *strvalue = NULL; + CONF_PAIR *cp = NULL; da = dict_attrbyname(dynamic_config[i].name); if (!da) { @@ -1344,10 +1428,10 @@ RADCLIENT *client_afrom_request(RADCLIENT_LIST *clients, REQUEST *request) *pi = vp->vp_integer; /* - * Same nastiness as above. + * Same nastiness as above, but hard-coded for require Message-Authenticator. */ for (parse = client_config; parse->name; parse++) { - if (parse->offset == dynamic_config[i].offset) break; + if (parse->type == PW_TYPE_BOOLEAN) break; } if (!parse) break; @@ -1377,6 +1461,8 @@ RADCLIENT *client_afrom_request(RADCLIENT_LIST *clients, REQUEST *request) fr_cursor_first(&cursor); vp = fr_cursor_remove(&cursor); if (vp) { + CONF_PAIR *cp; + do { char *value; @@ -1436,6 +1522,11 @@ validate: goto error; } + /* + * It can't be set to "auto". Too bad. + */ + c->require_ma = (fr_bool_auto_t) c->dynamic_require_ma; + if (!client_add_dynamic(clients, request->client, c)) { return NULL; } diff --git a/src/main/command.c b/src/main/command.c index 844898eed1..706260b150 100644 --- a/src/main/command.c +++ b/src/main/command.c @@ -27,19 +27,14 @@ #include #include #include +#include +#include #include #ifdef HAVE_INTTYPES_H #include #endif -#ifdef HAVE_SYS_UN_H -#include -#ifndef SUN_LEN -#define SUN_LEN(su) (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path)) -#endif -#endif - #ifdef HAVE_SYS_STAT_H #include #endif @@ -1009,6 +1004,11 @@ static char const *method_names[MOD_COUNT] = { "pre-proxy", "post-proxy", "post-auth" +#ifdef WITH_COA + , + "recv-coa", + "send-coa" +#endif }; @@ -1138,7 +1138,7 @@ static int command_show_modules(rad_listen_t *listener, UNUSED int argc, UNUSED } #ifdef WITH_PROXY -static int command_show_home_servers(rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[]) +static int command_show_home_servers(rad_listen_t *listener, int argc, char *argv[]) { int i; home_server_t *home; @@ -1146,9 +1146,10 @@ static int command_show_home_servers(rad_listen_t *listener, UNUSED int argc, UN char buffer[256]; - for (i = 0; i < 256; i++) { - home = home_server_bynumber(i); - if (!home) break; + for (i = 0; i < home_server_max_number; i++) { + + if ((home = home_server_bynumber(i)) == NULL) + continue; /* * Internal "virtual" home server. @@ -1190,6 +1191,9 @@ static int command_show_home_servers(rad_listen_t *listener, UNUSED int argc, UN } else if (home->state == HOME_STATE_IS_DEAD) { state = "dead"; + } else if (home->state == HOME_STATE_ADMIN_DOWN) { + state = "down"; + } else if (home->state == HOME_STATE_UNKNOWN) { time_t now = time(NULL); @@ -1212,10 +1216,19 @@ static int command_show_home_servers(rad_listen_t *listener, UNUSED int argc, UN } else continue; - cprintf(listener, "%s\t%d\t%s\t%s\t%s\t%d\n", - ip_ntoh(&home->ipaddr, buffer, sizeof(buffer)), - home->port, proto, type, state, - home->currently_outstanding); + if (argc > 0 && !strcmp(argv[0], "all")) { + char const *dynamic = home->dynamic ? "yes" : "no"; + + cprintf(listener, "%s\t%d\t%s\t%s\t%s\t%d\t(name=%s, dynamic=%s)\n", + ip_ntoh(&home->ipaddr, buffer, sizeof(buffer)), + home->port, proto, type, state, + home->currently_outstanding, home->name, dynamic); + } else { + cprintf(listener, "%s\t%d\t%s\t%s\t%s\t%d\n", + ip_ntoh(&home->ipaddr, buffer, sizeof(buffer)), + home->port, proto, type, state, + home->currently_outstanding); + } } return CMD_OK; @@ -1239,27 +1252,107 @@ static int command_show_client_config(rad_listen_t *listener, int argc, char *ar return 1; } +/* + * @todo - copied from clients.c. Better to re-use, but whatever. + */ +struct radclient_list { + char const *name; /* name of this list */ + char const *server; /* virtual server associated with this client list */ + + /* + * FIXME: One set of trees for IPv4, and another for IPv6? + */ + rbtree_t *trees[129]; /* for 0..128, inclusive. */ + uint32_t min_prefix; +}; + -static int command_show_clients(rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[]) +static int command_show_clients(rad_listen_t *listener, int argc, char *argv[]) { int i; RADCLIENT *client; char buffer[256]; - for (i = 0; i < 256; i++) { - client = client_findbynumber(NULL, i); - if (!client) break; + if (argc == 0) { + for (i = 0; (client = client_findbynumber(NULL, i)) != NULL; i++) { + ip_ntoh(&client->ipaddr, buffer, sizeof(buffer)); + + if (((client->ipaddr.af == AF_INET) && + (client->ipaddr.prefix != 32)) || + ((client->ipaddr.af == AF_INET6) && + (client->ipaddr.prefix != 128))) { + cprintf(listener, "%s/%d\n", buffer, client->ipaddr.prefix); + } else { + cprintf(listener, "%s\n", buffer); + } + } - ip_ntoh(&client->ipaddr, buffer, sizeof(buffer)); + return CMD_OK; + } - if (((client->ipaddr.af == AF_INET) && - (client->ipaddr.prefix != 32)) || - ((client->ipaddr.af == AF_INET6) && - (client->ipaddr.prefix != 128))) { - cprintf(listener, "%s/%d\n", buffer, client->ipaddr.prefix); + if (argc != 1) { + cprintf_error(listener, "Unknown command %s %s ...\n", argv[0], argv[1]); + return -1; + } + + if (strcmp(argv[0], "verbose") != 0) { + cprintf_error(listener, "Unknown command %s\n", argv[0]); + return -1; + } + + for (i = 0; (client = client_findbynumber(NULL, i)) != NULL; i++) { + if (client->cs) { + cprintf(listener, "client %s {\n", cf_section_name2(client->cs)); } else { - cprintf(listener, "%s\n", buffer); + cprintf(listener, "client {\n"); + } + + fr_ntop(buffer, sizeof(buffer), &client->ipaddr); + cprintf(listener, "\tipaddr = %s\n", buffer); + + if (client->src_ipaddr.af != AF_UNSPEC) { + fr_ntop(buffer, sizeof(buffer), &client->src_ipaddr); + cprintf(listener, "\tsrc_ipaddr = %s\n", buffer); + } + + if (client->proto == IPPROTO_UDP) { + cprintf(listener, "\tproto = udp\n"); + } else if (client->proto == IPPROTO_TCP) { + cprintf(listener, "\tproto = tcp\n"); + } else { + cprintf(listener, "\tproto = *\n"); + } + + cprintf(listener, "\tsecret = %s\n", client->secret); + cprintf(listener, "\tlongname = %s\n", client->longname); + cprintf(listener, "\tshortname = %s\n", client->shortname); + if (client->nas_type) cprintf(listener, "\tnas_type = %s\n", client->nas_type); + cprintf(listener, "\tnumber = %d\n", client->number); + + if (client->server) { + cprintf(listener, "\tvirtual_server = %s\n", client->server); + } + +#ifdef WITH_DYNAMIC_CLIENTS + if (client->dynamic) { + cprintf(listener, "\tdynamic = yes\n"); + cprintf(listener, "\tlifetime = %u\n", client->lifetime); + } +#endif + +#ifdef WITH_TLS + if (client->tls_required) { + cprintf(listener, "\ttls = yes\n"); } +#endif + + if (client->list && client->list->server) { + cprintf(listener, "\tparent_virtual_server = %s\n", client->list->server); + } else { + cprintf(listener, "\tglobal = yes\n"); + } + + cprintf(listener, "}\n"); } return CMD_OK; @@ -1301,7 +1394,7 @@ static int command_debug_file(rad_listen_t *listener, int argc, char *argv[]) return -1; } - if ((argc > 0) && (strchr(argv[0], FR_DIR_SEP) != NULL)) { + if ((argc > 0) && (strchr(argv[0], FR_DIR_SEP) == argv[0])) { cprintf_error(listener, "Cannot direct debug logs to absolute path.\n"); } @@ -1646,8 +1739,14 @@ static int command_set_home_server_state(rad_listen_t *listener, int argc, char } else if (strcmp(argv[last], "dead") == 0) { struct timeval now; - gettimeofday(&now, NULL); /* we do this WAY too ofetn */ - mark_home_server_dead(home, &now); + gettimeofday(&now, NULL); /* we do this WAY too often */ + mark_home_server_dead(home, &now, false); + + } else if (strcmp(argv[last], "down") == 0) { + struct timeval now; + + gettimeofday(&now, NULL); /* we do this WAY too often */ + mark_home_server_dead(home, &now, true); } else { cprintf_error(listener, "Unknown state \"%s\"\n", argv[last]); @@ -1677,6 +1776,10 @@ static int command_show_home_server_state(rad_listen_t *listener, int argc, char cprintf(listener, "zombie\n"); break; + case HOME_STATE_ADMIN_DOWN: + cprintf(listener, "down\n"); + break; + case HOME_STATE_UNKNOWN: cprintf(listener, "unknown\n"); break; @@ -2047,7 +2150,7 @@ static fr_command_table_t command_table_show_client[] = { "- show configuration for given client", command_show_client_config, NULL }, { "list", FR_READ, - "show client list - shows list of global clients", + "show client list [verbose] - shows list of global clients", command_show_clients, NULL }, { NULL, 0, NULL, NULL, NULL } @@ -2056,7 +2159,7 @@ static fr_command_table_t command_table_show_client[] = { #ifdef WITH_PROXY static fr_command_table_t command_table_show_home[] = { { "list", FR_READ, - "show home_server list - shows list of home servers", + "show home_server list [all] - shows list of home servers", command_show_home_servers, NULL }, { "state", FR_READ, "show home_server state [udp|tcp] [src ] - shows state of given home server", @@ -2597,6 +2700,36 @@ static int command_del_client(rad_listen_t *listener, int argc, char *argv[]) } +static int command_del_home_server(rad_listen_t *listener, int argc, char *argv[]) +{ + if (argc < 2) { + cprintf_error(listener, " and are required\n"); + return 0; + } + + if (home_server_delete(argv[0], argv[1]) < 0) { + cprintf_error(listener, "Failed deleted home_server %s - %s\n", argv[1], fr_strerror()); + return 0; + } + + return CMD_OK; +} + +static int command_add_home_server_file(rad_listen_t *listener, int argc, char *argv[]) +{ + if (argc < 1) { + cprintf_error(listener, " is required\n"); + return 0; + } + + if (home_server_afrom_file(argv[0]) < 0) { + cprintf_error(listener, "Unable to add home server - %s\n", fr_strerror()); + return 0; + } + + return CMD_OK; +} + static fr_command_table_t command_table_del_client[] = { { "ipaddr", FR_WRITE, "del client ipaddr [udp|tcp] [listen ] - Delete a dynamically created client", @@ -2605,16 +2738,26 @@ static fr_command_table_t command_table_del_client[] = { { NULL, 0, NULL, NULL, NULL } }; +static fr_command_table_t command_table_del_home_server[] = { + { "file", FR_WRITE, + "del home_server file [auth|acct|coa] - Delete a dynamically created home_server", + command_del_home_server, NULL }, + + { NULL, 0, NULL, NULL, NULL } +}; static fr_command_table_t command_table_del[] = { { "client", FR_WRITE, "del client - Delete client configuration commands", NULL, command_table_del_client }, + { "home_server", FR_WRITE, + "del home_server - Delete home_server configuration commands", + NULL, command_table_del_home_server }, + { NULL, 0, NULL, NULL, NULL } }; - static fr_command_table_t command_table_add_client[] = { { "file", FR_WRITE, "add client file - Add new client definition from ", @@ -2623,12 +2766,23 @@ static fr_command_table_t command_table_add_client[] = { { NULL, 0, NULL, NULL, NULL } }; +static fr_command_table_t command_table_add_home_server[] = { + { "file", FR_WRITE, + "add home_server file - Add new home serverdefinition from ", + command_add_home_server_file, NULL }, + + { NULL, 0, NULL, NULL, NULL } +}; static fr_command_table_t command_table_add[] = { { "client", FR_WRITE, "add client - Add client configuration commands", NULL, command_table_add_client }, + { "home_server", FR_WRITE, + "add home_server - Add home server configuration commands", + NULL, command_table_add_home_server }, + { NULL, 0, NULL, NULL, NULL } }; #endif @@ -2636,7 +2790,7 @@ static fr_command_table_t command_table_add[] = { #ifdef WITH_PROXY static fr_command_table_t command_table_set_home[] = { { "state", FR_WRITE, - "set home_server state [udp|tcp] [src ] [alive|dead] - set state for given home server", + "set home_server state [udp|tcp] [src ] [alive|dead|down] - set state for given home server", command_set_home_server_state, NULL }, { NULL, 0, NULL, NULL, NULL } diff --git a/src/main/conffile.c b/src/main/conffile.c index a8c667bfb5..d458792cd5 100644 --- a/src/main/conffile.c +++ b/src/main/conffile.c @@ -353,6 +353,7 @@ error: file->filename = filename; file->cs = cs; + file->from_dir = from_dir; if (fstat(fd, &file->buf) == 0) { #ifdef S_IWOTH @@ -1174,10 +1175,14 @@ static char const *cf_expand_variables(char const *cf, int *lineno, return NULL; } + /* + * Might as well make + * non-existent string be the + * empty string. + */ if (!cp->value) { - ERROR("%s[%d]: Reference \"%s\" has no value", - cf, *lineno, input); - return NULL; + *p = '\0'; + goto skip_value; } if (p + strlen(cp->value) >= output + outsize) { @@ -1188,6 +1193,7 @@ static char const *cf_expand_variables(char const *cf, int *lineno, strcpy(p, cp->value); p += strlen(p); + skip_value: ptr = end + 1; } else if (ci->type == CONF_ITEM_SECTION) { @@ -1418,6 +1424,7 @@ int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *d { int rcode; bool deprecated, required, attribute, secret, file_input, cant_be_empty, tmpl, multi, file_exists; + bool ignore_dflt; char **q; char const *value; CONF_PAIR *cp = NULL; @@ -1441,6 +1448,7 @@ int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *d cant_be_empty = (type & PW_TYPE_NOT_EMPTY); tmpl = (type & PW_TYPE_TMPL); multi = (type & PW_TYPE_MULTI); + ignore_dflt = (type & PW_TYPE_IGNORE_DEFAULT); if (attribute) required = true; if (required) cant_be_empty = true; /* May want to review this in the future... */ @@ -1464,7 +1472,7 @@ int cf_item_parse(CONF_SECTION *cs, char const *name, unsigned int type, void *d * section, use the default value. */ if (!cp) { - if (deprecated) return 0; /* Don't set the default value */ + if (deprecated || ignore_dflt) return 0; /* Don't set the default value */ rcode = 1; value = dflt; @@ -1798,6 +1806,7 @@ static void cf_section_parse_init(CONF_SECTION *cs, void *base, CONF_PARSER const *variables) { int i; + void *data; for (i = 0; variables[i].name != NULL; i++) { if (variables[i].type == PW_TYPE_SUBSECTION) { @@ -1822,9 +1831,13 @@ static void cf_section_parse_init(CONF_SECTION *cs, void *base, subcs->item.lineno = cs->item.lineno; cf_item_add(cs, &(subcs->item)); } + if (base) { + data = ((uint8_t *)base) + variables[i].offset; + } else { + data = NULL; + } - cf_section_parse_init(subcs, (uint8_t *)base + variables[i].offset, - (CONF_PARSER const *) variables[i].dflt); + cf_section_parse_init(subcs, data, (CONF_PARSER const *) variables[i].dflt); continue; } @@ -1922,8 +1935,13 @@ int cf_section_parse(CONF_SECTION *cs, void *base, CONF_PARSER const *variables) goto finish; } - ret = cf_section_parse(subcs, (uint8_t *)base + variables[i].offset, - (CONF_PARSER const *) variables[i].dflt); + if (base) { + data = ((uint8_t *)base) + variables[i].offset; + } else { + data = NULL; + } + + ret = cf_section_parse(subcs, data, (CONF_PARSER const *) variables[i].dflt); if (ret < 0) goto finish; continue; } /* else it's a CONF_PAIR */ @@ -2313,7 +2331,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, if (has_spaces) { ptr = cbuf; - while (isspace((int) *ptr)) ptr++; + while (isspace((uint8_t) *ptr)) ptr++; if (ptr > cbuf) { memmove(cbuf, ptr, len - (ptr - cbuf)); @@ -2329,7 +2347,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, if (at_eof) break; ptr = buf; - while (*ptr && isspace((int) *ptr)) ptr++; + while (*ptr && isspace((uint8_t) *ptr)) ptr++; if (!*ptr || (*ptr == '#')) continue; @@ -2511,6 +2529,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, */ while ((dp = readdir(dir)) != NULL) { char const *p; + int slen; if (dp->d_name[0] == '.') continue; @@ -2518,8 +2537,8 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, * Check for valid characters */ for (p = dp->d_name; *p != '\0'; p++) { - if (isalpha((int)*p) || - isdigit((int)*p) || + if (isalpha((uint8_t)*p) || + isdigit((uint8_t)*p) || (*p == '-') || (*p == '_') || (*p == '.')) continue; @@ -2527,8 +2546,12 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, } if (*p != '\0') continue; - snprintf(buf2, sizeof(buf2), "%s%s", - value, dp->d_name); + slen = snprintf(buf2, sizeof(buf2), "%s%s", + value, dp->d_name); + if (slen >= (int) sizeof(buf2) || slen < 0) { + ERROR("%s: Full file path is too long.", dp->d_name); + return -1; + } if ((stat(buf2, &stat_buf) != 0) || S_ISDIR(stat_buf.st_mode)) continue; @@ -2794,7 +2817,15 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, case T_OP_EQ: case T_OP_SET: - while (isspace((int) *ptr)) ptr++; + case T_OP_PREPEND: + while (isspace((uint8_t) *ptr)) ptr++; + + /* + * Be a little more forgiving. + */ + if (*ptr == '#') { + t3 = T_HASH; + } else /* * New parser: non-quoted strings are @@ -2809,7 +2840,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, t3 = T_BARE_WORD; while (*q && (*q >= ' ') && (*q != ',') && - !isspace(*q)) q++; + !isspace((uint8_t) *q)) q++; if ((size_t) (q - ptr) >= sizeof(buf3)) { ERROR("%s[%d]: Parse error: value too long", @@ -2891,7 +2922,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, /* * Require a comma, unless there's a comment. */ - while (isspace(*ptr)) ptr++; + while (isspace((uint8_t) *ptr)) ptr++; if (*ptr == ',') { ptr++; @@ -2964,7 +2995,7 @@ static int cf_section_read(char const *filename, int *lineno, FILE *fp, /* * Done parsing one thing. Skip to EOL if possible. */ - while (isspace(*ptr)) ptr++; + while (isspace((uint8_t) *ptr)) ptr++; if (*ptr == '#') continue; @@ -3561,6 +3592,10 @@ bool cf_item_is_pair(CONF_ITEM const *item) return item->type == CONF_ITEM_PAIR; } +bool cf_item_is_data(CONF_ITEM const *item) +{ + return item->type == CONF_ITEM_DATA; +} static CONF_DATA *cf_data_alloc(CONF_SECTION *parent, char const *name, void *data, void (*data_free)(void *)) diff --git a/src/main/listen.c b/src/main/listen.c index ebf7f5221c..6cda245ef0 100644 --- a/src/main/listen.c +++ b/src/main/listen.c @@ -52,6 +52,17 @@ RCSID("$Id$") #include #endif +#ifdef WITH_TLS +#include + +# if defined(__APPLE__) || defined(__FreeBSD__) +# if !defined(SOL_TCP) && defined(IPPROTO_TCP) +# define SOL_TCP IPPROTO_TCP +# endif +# endif + +#endif + #ifdef DEBUG_PRINT_PACKET static void print_packet(RADIUS_PACKET *packet) { @@ -217,7 +228,7 @@ RADCLIENT *client_listener_find(rad_listen_t *listener, client_delete(clients, client); /* - * Add a timer to free the client in 120s + * Add a timer to free the client 20s after it's already timed out. */ el = radius_event_list_corral(EVENT_CORRAL_MAIN); @@ -349,7 +360,6 @@ RADCLIENT *client_listener_find(rad_listen_t *listener, static int listen_bind(rad_listen_t *this); - /* * Process and reply to a server-status request. * Like rad_authenticate and rad_accounting this should @@ -360,6 +370,52 @@ int rad_status_server(REQUEST *request) int rcode = RLM_MODULE_OK; DICT_VALUE *dval; +#ifdef WITH_TLS + if (request->listener->tls) { + listen_socket_t *sock = request->listener->data; + + if (sock->state == LISTEN_TLS_CHECKING) { + int autz_type = PW_AUTZ_TYPE; + char const *name = "Autz-Type"; + + if (request->listener->type == RAD_LISTEN_ACCT) { + autz_type = PW_ACCT_TYPE; + name = "Acct-Type"; + } + + RDEBUG("(TLS) Checking connection to see if it is authorized."); + + dval = dict_valbyname(autz_type, 0, "New-TLS-Connection"); + if (dval) { + rcode = process_authorize(dval->value, request); + } else { + rcode = RLM_MODULE_OK; + RWDEBUG("(TLS) Did not find '%s New-TLS-Connection' - defaulting to accept", name); + } + + if ((rcode == RLM_MODULE_OK) || (rcode == RLM_MODULE_UPDATED)) { + RDEBUG("(TLS) Connection is authorized"); + request->reply->code = PW_CODE_ACCESS_ACCEPT; + } else { + RWDEBUG("(TLS) Connection is not authorized - closing TCP socket."); + request->reply->code = PW_CODE_ACCESS_REJECT; + } + + return 0; + } + } +#endif + +#ifdef WITH_STATS + /* + * Full statistics are available only on a statistics + * socket. + */ + if (request->listener->type == RAD_LISTEN_NONE) { + request_stats_reply(request); + } +#endif + switch (request->listener->type) { #ifdef WITH_STATS case RAD_LISTEN_NONE: @@ -443,17 +499,124 @@ int rad_status_server(REQUEST *request) return 0; } -#ifdef WITH_STATS + return 0; +} + +static void blastradius_checks(RADIUS_PACKET *packet, RADCLIENT *client) +{ + if (client->require_ma == FR_BOOL_TRUE) return; + + if (client->require_ma == FR_BOOL_AUTO) { + if (!packet->message_authenticator) { + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + ERROR("BlastRADIUS check: Received packet without Message-Authenticator."); + ERROR("Setting \"require_message_authenticator = false\" for client %s", client->shortname); + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + ERROR("UPGRADE THE CLIENT AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); + ERROR("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname); + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + client->require_ma = FR_BOOL_FALSE; + + /* + * And fall through to the + * limit_proxy_state checks, which might + * complain again. Oh well, maybe that + * will make people read the messages. + */ + + } else if (packet->eap_message) { + /* + * Don't set it to "true" for packets + * with EAP-Message. It's already + * required there, and we might get a + * non-EAP packet with (or without) + * Message-Authenticator + */ + return; + } else { + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + ERROR("BlastRADIUS check: Received packet with Message-Authenticator."); + ERROR("Setting \"require_message_authenticator = true\" for client %s", client->shortname); + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + ERROR("It looks like the client has been updated to protect from the BlastRADIUS attack."); + ERROR("Please set \"require_message_authenticator = true\" for client %s", client->shortname); + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + client->require_ma = FR_BOOL_TRUE; + return; + } + + } + /* - * Full statistics are available only on a statistics - * socket. + * If all of the checks are turned off, then complain for every packet we receive. */ - if (request->listener->type == RAD_LISTEN_NONE) { - request_stats_reply(request); + if (client->limit_proxy_state == FR_BOOL_FALSE) { + /* + * We have a Message-Authenticator, and it's valid. We don't need to compain. + */ + if (packet->message_authenticator) return; + + if (!fr_debug_lvl) return; /* easier than checking for each line below */ + + DEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + DEBUG("BlastRADIUS check: Received packet without Message-Authenticator."); + DEBUG("YOU MUST SET \"require_message_authenticator = true\", or"); + DEBUG("YOU MUST SET \"limit_proxy_state = true\" for client %s", client->shortname); + DEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + DEBUG("The packet does not contain Message-Authenticator, which is a security issue"); + DEBUG("UPGRADE THE CLIENT AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); + DEBUG("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname); + DEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + return; } -#endif - return 0; + /* + * Don't complain here. rad_packet_ok() will instead + * complain about every packet with Proxy-State but which + * is missing Message-Authenticator. + */ + if (client->limit_proxy_state == FR_BOOL_TRUE) { + return; + } + + if (packet->proxy_state && !packet->message_authenticator) { + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + ERROR("BlastRADIUS check: Received packet with Proxy-State, but without Message-Authenticator."); + ERROR("This is either a BlastRADIUS attack, OR"); + ERROR("the client is a proxy RADIUS server which has not been upgraded."); + ERROR("Setting \"limit_proxy_state = false\" for client %s", client->shortname); + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + ERROR("UPGRADE THE CLIENT AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); + ERROR("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname); + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + client->limit_proxy_state = FR_BOOL_FALSE; + + } else { + client->limit_proxy_state = FR_BOOL_TRUE; + + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + if (!packet->proxy_state) { + ERROR("BlastRADIUS check: Received packet without Proxy-State."); + } else { + ERROR("BlastRADIUS check: Received packet with Proxy-State and Message-Authenticator."); + } + + ERROR("Setting \"limit_proxy_state = true\" for client %s", client->shortname); + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + if (!packet->message_authenticator) { + ERROR("The packet does not contain Message-Authenticator, which is a security issue."); + ERROR("UPGRADE THE CLIENT AS YOUR NETWORK MAY BE VULNERABLE TO THE BLASTRADIUS ATTACK."); + ERROR("Once the client is upgraded, set \"require_message_authenticator = true\" for client %s", client->shortname); + } else { + ERROR("The packet contains Message-Authenticator."); + if (!packet->eap_message) ERROR("The client has likely been upgraded to protect from the attack."); + ERROR("Please set \"require_message_authenticator = true\" for client %s", client->shortname); + } + ERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + } } #ifdef WITH_TCP @@ -532,13 +695,37 @@ static int dual_tcp_recv(rad_listen_t *listener) switch (packet->code) { case PW_CODE_ACCESS_REQUEST: if (listener->type != RAD_LISTEN_AUTH) goto bad_packet; + + /* + * Enforce BlastRADIUS checks on TCP, too. + */ + if (!rad_packet_ok(packet, (client->require_ma == FR_BOOL_TRUE) | ((client->limit_proxy_state == FR_BOOL_TRUE) << 2), NULL)) { + FR_STATS_INC(auth, total_malformed_requests); + rad_free(&sock->packet); + return 0; + } + + /* + * Perform BlastRADIUS checks and warnings. + */ + if (packet->code == PW_CODE_ACCESS_REQUEST) blastradius_checks(packet, client); + FR_STATS_INC(auth, total_requests); fun = rad_authenticate; break; #ifdef WITH_ACCOUNTING case PW_CODE_ACCOUNTING_REQUEST: - if (listener->type != RAD_LISTEN_ACCT) goto bad_packet; + if (listener->type != RAD_LISTEN_ACCT) { + /* + * Allow auth + dual. Disallow + * everything else. + */ + if (!((listener->type == RAD_LISTEN_AUTH) && + (listener->dual))) { + goto bad_packet; + } + } FR_STATS_INC(acct, total_requests); fun = rad_accounting; break; @@ -574,6 +761,79 @@ static int dual_tcp_recv(rad_listen_t *listener) return 1; } +#ifdef WITH_TLS +typedef struct { + char const *name; + SSL_CTX *ctx; +} fr_realm_ctx_t; /* hack from tls. */ + +static int tls_sni_callback(SSL *ssl, UNUSED int *al, void *arg) +{ + fr_tls_server_conf_t *conf = arg; + char const *name, *p; + int type; + fr_realm_ctx_t my_r, *r; + REQUEST *request; + char buffer[PATH_MAX]; + + /* + * No SNI, that's fine. + */ + type = SSL_get_servername_type(ssl); + if (type < 0) return SSL_TLSEXT_ERR_OK; + + /* + * No realms configured, just use the default context. + */ + if (!conf->realms) return SSL_TLSEXT_ERR_OK; + + name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (!name) return SSL_TLSEXT_ERR_OK; + + /* + * RFC Section 6066 Section 3 says that the names are + * ASCII, without a trailing dot. i.e. punycode. + */ + for (p = name; *p != '\0'; p++) { + if (*p == '-') continue; + if (*p == '.') continue; + if ((*p >= 'A') && (*p <= 'Z')) continue; + if ((*p >= 'a') && (*p <= 'z')) continue; + if ((*p >= '0') && (*p <= '9')) continue; + + /* + * Anything else, fail. + */ + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + /* + * Too long, fail. + */ + if ((p - name) > 255) return SSL_TLSEXT_ERR_ALERT_FATAL; + + snprintf(buffer, sizeof(buffer), "%s/%s.pem", conf->realm_dir, name); + + my_r.name = buffer; + r = fr_hash_table_finddata(conf->realms, &my_r); + + /* + * If found, switch certs. Otherwise use the default + * one. + */ + if (r) (void) SSL_set_SSL_CTX(ssl, r->ctx); + + /* + * Set an attribute saying which server has been selected. + */ + request = (REQUEST *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST); + if (request) { + (void) pair_make_config("TLS-Server-Name-Indication", name, T_OP_SET); + } + + return SSL_TLSEXT_ERR_OK; +} +#endif static int dual_tcp_accept(rad_listen_t *listener) { @@ -731,6 +991,14 @@ static int dual_tcp_accept(rad_listen_t *listener) if (this->tls) { this->recv = dual_tls_recv; this->send = dual_tls_send; + + /* + * Set up SNI callback. We don't do it + * in the main TLS code, because EAP + * doesn't need or use SNI. + */ + SSL_CTX_set_tlsext_servername_callback(this->tls->ctx, tls_sni_callback); + SSL_CTX_set_tlsext_servername_arg(this->tls->ctx, this->tls); } #endif } @@ -932,7 +1200,6 @@ static CONF_PARSER limit_config[] = { CONF_PARSER_TERMINATOR }; - #ifdef WITH_TCP /* * TLS requires child threads to handle the listeners. Which @@ -1018,6 +1285,7 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) } else if (strcmp(proto, "tcp") == 0) { sock->proto = IPPROTO_TCP; + } else { cf_log_err_cs(cs, "Unknown proto name \"%s\"", proto); @@ -1050,6 +1318,12 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) return -1; } + /* + * Allow non-blocking for TLS sockets + */ + rcode = cf_item_parse(cs, "nonblock", FR_ITEM_POINTER(PW_TYPE_BOOLEAN, &this->nonblock), NULL); + if (rcode < 0) return -1; + /* * If unset, set to default. */ @@ -1068,6 +1342,8 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) } #endif + rcode = cf_item_parse(cs, "check_client_connections", FR_ITEM_POINTER(PW_TYPE_BOOLEAN, &this->check_client_connections), "no"); + if (rcode < 0) return -1; } #else /* WITH_TLS */ /* @@ -1312,6 +1588,11 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) */ this->recv = dual_tcp_accept; + /* + * @todo - add a free function? Though this only + * matters when we're tearing down the server, so + * perhaps it's less relevant. + */ this->children = rbtree_create(this, listener_cmp, NULL, 0); if (!this->children) { cf_log_err_cs(cs, "Failed to create child list for TCP socket."); @@ -1324,12 +1605,12 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this) } /* - * Send an authentication response packet + * Send a response packet */ -static int auth_socket_send(rad_listen_t *listener, REQUEST *request) +static int common_socket_send(rad_listen_t *listener, REQUEST *request) { rad_assert(request->listener == listener); - rad_assert(listener->send == auth_socket_send); + rad_assert(listener->send == common_socket_send); if (request->reply->code == 0) return 0; @@ -1355,46 +1636,6 @@ static int auth_socket_send(rad_listen_t *listener, REQUEST *request) } -#ifdef WITH_ACCOUNTING -/* - * Send an accounting response packet (or not) - */ -static int acct_socket_send(rad_listen_t *listener, REQUEST *request) -{ - rad_assert(request->listener == listener); - rad_assert(listener->send == acct_socket_send); - - /* - * Accounting reject's are silently dropped. - * - * We do it here to avoid polluting the rest of the - * code with this knowledge - */ - if (request->reply->code == 0) return 0; - -#ifdef WITH_UDPFROMTO - /* - * Overwrite the src ip address on the outbound packet - * with the one specified by the client. - * This is useful to work around broken DSR implementations - * and other routing issues. - */ - if (request->client->src_ipaddr.af != AF_UNSPEC) { - request->reply->src_ipaddr = request->client->src_ipaddr; - } -#endif - - if (rad_send(request->reply, request->packet, - request->client->secret) < 0) { - RERROR("Failed sending reply: %s", - fr_strerror()); - return -1; - } - - return 0; -} -#endif - #ifdef WITH_PROXY /* * Send a packet to a home server. @@ -1404,7 +1645,7 @@ static int acct_socket_send(rad_listen_t *listener, REQUEST *request) static int proxy_socket_send(rad_listen_t *listener, REQUEST *request) { rad_assert(request->proxy_listener == listener); - rad_assert(listener->send == proxy_socket_send); + rad_assert(listener->proxy_send == proxy_socket_send); if (rad_send(request->proxy, NULL, request->home_server->secret) < 0) { @@ -1436,8 +1677,6 @@ static int stats_socket_recv(rad_listen_t *listener) rcode = rad_recv_header(listener->fd, &src_ipaddr, &src_port, &code); if (rcode < 0) return 0; - FR_STATS_INC(auth, total_requests); - if (rcode < 20) { /* RADIUS_HDR_LEN */ if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror()); FR_STATS_INC(auth, total_malformed_requests); @@ -1485,7 +1724,6 @@ static int stats_socket_recv(rad_listen_t *listener) } #endif - /* * Check if an incoming request is "ok" * @@ -1521,13 +1759,12 @@ static int auth_socket_recv(rad_listen_t *listener) return 0; } - FR_STATS_TYPE_INC(client->auth.total_requests); - /* * Some sanity checks, based on the packet code. */ switch (code) { case PW_CODE_ACCESS_REQUEST: + FR_STATS_TYPE_INC(client->auth.total_requests); fun = rad_authenticate; break; @@ -1562,7 +1799,7 @@ static int auth_socket_recv(rad_listen_t *listener) * Now that we've sanity checked everything, receive the * packet. */ - packet = rad_recv(ctx, listener->fd, client->message_authenticator); + packet = rad_recv(ctx, listener->fd, (client->require_ma == FR_BOOL_TRUE) | ((client->limit_proxy_state == FR_BOOL_TRUE) << 2)); if (!packet) { FR_STATS_INC(auth, total_malformed_requests); if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror()); @@ -1570,6 +1807,11 @@ static int auth_socket_recv(rad_listen_t *listener) return 0; } + /* + * Perform BlastRADIUS checks and warnings. + */ + if (packet->code == PW_CODE_ACCESS_REQUEST) blastradius_checks(packet, client); + #ifdef __APPLE__ #ifdef WITH_UDPFROMTO /* @@ -1637,13 +1879,12 @@ static int acct_socket_recv(rad_listen_t *listener) return 0; } - FR_STATS_TYPE_INC(client->acct.total_requests); - /* * Some sanity checks, based on the packet code. */ switch (code) { case PW_CODE_ACCOUNTING_REQUEST: + FR_STATS_TYPE_INC(client->acct.total_requests); fun = rad_accounting; break; @@ -1729,7 +1970,6 @@ static int do_proxy(REQUEST *request) */ vp = fr_pair_find_by_num(request->config, PW_PACKET_DST_IP_ADDRESS, 0, TAG_ANY); if (!vp) vp = fr_pair_find_by_num(request->config, PW_PACKET_DST_IPV6_ADDRESS, 0, TAG_ANY); - if (!vp) return 0; return 1; @@ -1955,7 +2195,7 @@ static int coa_socket_recv(rad_listen_t *listener) * Now that we've sanity checked everything, receive the * packet. */ - packet = rad_recv(ctx, listener->fd, client->message_authenticator); + packet = rad_recv(ctx, listener->fd, client->require_ma); if (!packet) { FR_STATS_INC(coa, total_malformed_requests); if (DEBUG_ENABLED) ERROR("Receive - %s", fr_strerror()); @@ -2049,19 +2289,67 @@ static int proxy_socket_recv(rad_listen_t *listener) */ static int proxy_socket_tcp_recv(rad_listen_t *listener) { + int rcode; RADIUS_PACKET *packet; listen_socket_t *sock = listener->data; - char buffer[128]; + char buffer[256]; if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0; - packet = fr_tcp_recv(listener->fd, 0); - if (!packet) { + if (!sock->packet) { + sock->packet = rad_alloc(sock, false); + if (!sock->packet) return 0; + + sock->packet->sockfd = listener->fd; + sock->packet->src_ipaddr = sock->other_ipaddr; + sock->packet->src_port = sock->other_port; + sock->packet->dst_ipaddr = sock->my_ipaddr; + sock->packet->dst_port = sock->my_port; + sock->packet->proto = sock->proto; + } + + packet = sock->packet; + + rcode = fr_tcp_read_packet(packet, 0); + + /* + * Still only a partial packet. Put it back, and return, + * so that we'll read more data when it's ready. + */ + if (rcode == 0) { + return 0; + } + + if (rcode == -1) { /* error reading packet */ + ERROR("Invalid packet from %s port %d, closing socket: %s", + ip_ntoh(&packet->src_ipaddr, buffer, sizeof(buffer)), + packet->src_port, fr_strerror()); + } + + if (rcode < 0) { /* error or connection reset */ listener->status = RAD_LISTEN_STATUS_EOL; + + /* + * Tell the event handler that an FD has disappeared. + */ + DEBUG("Home server %s port %d has closed connection", + ip_ntoh(&packet->src_ipaddr, buffer, sizeof(buffer)), + packet->src_port); + radius_update_listener(listener); + + /* + * Do NOT free the listener here. It's in use by + * a request, and will need to hang around until + * all of the requests are done. + * + * It is instead free'd in remove_from_request_hash() + */ return 0; } + sock->packet = NULL; /* we have no need for more partial reads */ + /* * FIXME: Client MIB updates? */ @@ -2089,10 +2377,6 @@ static int proxy_socket_tcp_recv(rad_listen_t *listener) return 0; } - packet->src_ipaddr = sock->other_ipaddr; - packet->src_port = sock->other_port; - packet->dst_ipaddr = sock->my_ipaddr; - packet->dst_port = sock->my_port; /* * FIXME: Have it return an indication of packets that @@ -2113,11 +2397,26 @@ static int proxy_socket_tcp_recv(rad_listen_t *listener) #endif #endif +#ifdef WITH_TLS +#define TLS_UNUSED +#else +#define TLS_UNUSED UNUSED +#endif -static int client_socket_encode(UNUSED rad_listen_t *listener, REQUEST *request) +static int client_socket_encode(TLS_UNUSED rad_listen_t *listener, REQUEST *request) { +#ifdef WITH_TLS + /* + * Don't encode fake packets. + */ + listen_socket_t *sock = listener->data; + if (sock->state == LISTEN_TLS_CHECKING) return 0; +#endif + if (!request->reply->code) return 0; + if (request->reply->data) return 0; /* already encoded */ + if (rad_encode(request->reply, request->packet, request->client->secret) < 0) { RERROR("Failed encoding packet: %s", fr_strerror()); @@ -2142,7 +2441,7 @@ static int client_socket_encode(UNUSED rad_listen_t *listener, REQUEST *request) static int client_socket_decode(UNUSED rad_listen_t *listener, REQUEST *request) { #ifdef WITH_TLS - listen_socket_t *sock; + listen_socket_t *sock = request->listener->data; #endif if (rad_verify(request->packet, NULL, @@ -2151,9 +2450,6 @@ static int client_socket_decode(UNUSED rad_listen_t *listener, REQUEST *request) } #ifdef WITH_TLS - sock = request->listener->data; - rad_assert(sock != NULL); - /* * FIXME: Add the rest of the TLS parameters, too? But * how do we separate EAP-TLS parameters from RADIUS/TLS @@ -2218,7 +2514,7 @@ static fr_protocol_t master_listen[RAD_LISTEN_MAX] = { #ifdef WITH_STATS { RLM_MODULE_INIT, "status", sizeof(listen_socket_t), NULL, common_socket_parse, NULL, - stats_socket_recv, auth_socket_send, + stats_socket_recv, common_socket_send, common_socket_print, client_socket_encode, client_socket_decode }, #else /* @@ -2241,14 +2537,14 @@ static fr_protocol_t master_listen[RAD_LISTEN_MAX] = { /* authentication */ { RLM_MODULE_INIT, "auth", sizeof(listen_socket_t), NULL, common_socket_parse, common_socket_free, - auth_socket_recv, auth_socket_send, + auth_socket_recv, common_socket_send, common_socket_print, client_socket_encode, client_socket_decode }, #ifdef WITH_ACCOUNTING /* accounting */ { RLM_MODULE_INIT, "acct", sizeof(listen_socket_t), NULL, common_socket_parse, common_socket_free, - acct_socket_recv, acct_socket_send, + acct_socket_recv, common_socket_send, common_socket_print, client_socket_encode, client_socket_decode}, #else { 0, "acct", 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, @@ -2282,7 +2578,7 @@ static fr_protocol_t master_listen[RAD_LISTEN_MAX] = { /* Change of Authorization */ { RLM_MODULE_INIT, "coa", sizeof(listen_socket_t), NULL, common_socket_parse, NULL, - coa_socket_recv, auth_socket_send, /* CoA packets are same as auth */ + coa_socket_recv, common_socket_send, common_socket_print, client_socket_encode, client_socket_decode }, #else { 0, "coa", 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }, @@ -2755,6 +3051,9 @@ static int _listener_free(rad_listen_t *this) rad_assert(!sock->ssn || (talloc_parent(sock->ssn) == sock)); rad_assert(!sock->request || (talloc_parent(sock->request) == sock)); + + if (sock->home && sock->home->listeners) (void) rbtree_deletebydata(sock->home->listeners, this); + #ifdef HAVE_PTHREAD_H pthread_mutex_destroy(&(sock->mutex)); #endif @@ -2780,8 +3079,16 @@ static rad_listen_t *listen_alloc(TALLOC_CTX *ctx, RAD_LISTEN_TYPE type) this->recv = master_listen[this->type].recv; this->send = master_listen[this->type].send; this->print = master_listen[this->type].print; - this->encode = master_listen[this->type].encode; - this->decode = master_listen[this->type].decode; + + if (type != RAD_LISTEN_PROXY) { + this->encode = master_listen[this->type].encode; + this->decode = master_listen[this->type].decode; + } else { + this->send = NULL; /* proxy packets shouldn't call this! */ + this->proxy_send = master_listen[this->type].send; + this->proxy_encode = master_listen[this->type].encode; + this->proxy_decode = master_listen[this->type].decode; + } talloc_set_destructor(this, _listener_free); @@ -2806,7 +3113,7 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t if (!home) return NULL; - rad_assert(home->server == NULL); /* we only open real sockets */ + rad_assert(home->virtual_server == NULL); /* we only open real sockets */ if ((home->limit.max_connections > 0) && (home->limit.num_connections >= home->limit.max_connections)) { @@ -2847,11 +3154,20 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t * FIXME: connect() is blocking! * We do this with the proxy mutex locked, which may * cause large delays! - * - * http://www.developerweb.net/forum/showthread.php?p=13486 */ this->fd = fr_socket_client_tcp(&home->src_ipaddr, - &home->ipaddr, home->port, false); + &home->ipaddr, home->port, +#ifdef WITH_TLS + !this->nonblock +#else + false +#endif + ); + + /* + * Set max_requests, lifetime, and idle_timeout from the home server. + */ + sock->limit = home->limit; } else #endif this->fd = fr_socket(&home->src_ipaddr, src_port); @@ -2869,17 +3185,74 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t #ifdef WITH_TCP #ifdef WITH_TLS if ((home->proto == IPPROTO_TCP) && home->tls) { - DEBUG("Trying SSL to port %d\n", home->port); + DEBUG("(TLS) Trying new outgoing proxy connection to %s", buffer); + + /* + * Set SNI, if configured. + * + * The OpenSSL API says the filename is "char + * const *", but some versions have it as "void + * *", without the "const". So we un-const it + * here through various C magic. + */ + if (home->tls->client_hostname) { + (void) SSL_set_tlsext_host_name(sock->ssn->ssl, (void *) (uintptr_t) home->tls->client_hostname); + } + + this->nonblock |= home->nonblock; + + /* + * Set non-blocking if it's configured. + */ + if (this->nonblock) { + if (fr_nonblock(this->fd) < 0) { + ERROR("(TLS) Failed setting nonblocking for proxy socket '%s' - %s", buffer, fr_strerror()); + goto error; + } + + rad_assert(home->listeners != NULL); + + if (!rbtree_insert(home->listeners, this)) { + ERROR("(TLS) Failed adding tracking informtion for proxy socket '%s'", buffer); + goto error; + } + +#ifdef TCP_NODELAY + /* + * Also set TCP_NODELAY, to force the data to be written quickly. + */ + if (sock->proto == IPPROTO_TCP) { + int on = 1; + + if (setsockopt(this->fd, SOL_TCP, TCP_NODELAY, &on, sizeof(on)) < 0) { + ERROR("(TLS) Failed to set TCP_NODELAY: %s", fr_syserror(errno)); + goto error; + } + } +#endif + } + + /* + * This is blocking. :( + */ sock->ssn = tls_new_client_session(sock, home->tls, this->fd, &sock->certs); if (!sock->ssn) { - ERROR("Failed starting SSL to new proxy socket '%s'", buffer); - home->last_failed_open = now; - listen_free(&this); - return NULL; + ERROR("(TLS) Failed opening connection on proxy socket '%s'", buffer); + goto error; } + sock->connect_timeout = home->connect_timeout; + this->recv = proxy_tls_recv; - this->send = proxy_tls_send; + this->proxy_send = proxy_tls_send; + +#ifdef HAVE_PTHREAD_H + if (pthread_mutex_init(&sock->mutex, NULL) < 0) { + rad_assert(0 == 1); + listen_free(&this); + return 0; + } +#endif } #endif #endif @@ -2895,6 +3268,8 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t &sizeof_src) < 0) { ERROR("Failed getting socket name for '%s': %s", buffer, fr_syserror(errno)); + error: + close(this->fd); home->last_failed_open = now; listen_free(&this); return NULL; @@ -2903,9 +3278,7 @@ rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t if (!fr_sockaddr2ipaddr(&src, sizeof_src, &sock->my_ipaddr, &sock->my_port)) { ERROR("Socket has unsupported address family for '%s'", buffer); - home->last_failed_open = now; - listen_free(&this); - return NULL; + goto error; } this->print(this, buffer, sizeof(buffer)); @@ -2966,6 +3339,9 @@ static rad_listen_t *listen_parse(CONF_SECTION *cs, char const *server) char const *value; fr_dlhandle handle; CONF_SECTION *server_cs; +#ifdef WITH_TCP + char const *p; +#endif char buffer[32]; cp = cf_pair_find(cs, "type"); @@ -3076,10 +3452,13 @@ static rad_listen_t *listen_parse(CONF_SECTION *cs, char const *server) #ifdef WITH_TCP /* - * Special-case '+' for "auth+acct". + * Add special flags '+' for "auth+acct". */ - if (strchr(listen_type, '+') != NULL) { - this->dual = true; + p = strchr(listen_type, '+'); + if (p) { + if (strncmp(p + 1, "acct", 4) == 0) { + this->dual = true; + } } #endif @@ -3115,7 +3494,6 @@ static void *recv_thread(void *arg) while (1) { this->recv(this); - DEBUG("%p", &this); } return NULL; @@ -3207,7 +3585,9 @@ int listen_init(CONF_SECTION *config, rad_listen_t **head, bool spawn_flag) if (override) { cs = cf_section_sub_find_name2(config, "server", main_config.name); - if (cs) this->server = main_config.name; + if (!cs) cs = cf_section_sub_find_name2(config, "server", + "default"); + if (cs) this->server = cf_section_name2(cs); } *last = this; diff --git a/src/main/mainconfig.c b/src/main/mainconfig.c index e9dd412dee..957bff933f 100644 --- a/src/main/mainconfig.c +++ b/src/main/mainconfig.c @@ -73,6 +73,8 @@ static char const *gid_name = NULL; static char const *chroot_dir = NULL; static bool allow_core_dumps = false; static char const *radlog_dest = NULL; +static char const *require_message_authenticator = NULL; +static char const *limit_proxy_state = NULL; /* * These are not used anywhere else.. @@ -87,6 +89,52 @@ static bool do_colourise = false; static char const *radius_dir = NULL; //!< Path to raddb directory +static const FR_NAME_NUMBER fr_bool_auto_names[] = { + { "false", FR_BOOL_FALSE }, + { "no", FR_BOOL_FALSE }, + { "0", FR_BOOL_FALSE }, + + { "true", FR_BOOL_TRUE }, + { "yes", FR_BOOL_TRUE }, + { "1", FR_BOOL_TRUE }, + + { "auto", FR_BOOL_AUTO }, + + { NULL, 0 } +}; + +/* + * Get decent values for false / true / auto + */ +int fr_bool_auto_parse(CONF_PAIR *cp, fr_bool_auto_t *out, char const *str) +{ + int value; + + /* + * Don't change anything. + */ + if (!str) return 0; + + value = fr_str2int(fr_bool_auto_names, str, -1); + if (value >= 0) { + *out = value; + return 0; + } + + /* + * This should never happen, as the defaults are in the + * source code. If there's no CONF_PAIR, and there's a + * parse error, then the source code is wrong. + */ + if (!cp) { + fprintf(stderr, "%s: Error - Invalid value in configuration", main_config.name); + return -1; + } + + cf_log_err(cf_pair_to_item(cp), "Invalid value for \"%s\"", cf_pair_attr(cp)); + return -1; +} + /********************************************************************** * * We need to figure out where the logs go, before doing anything @@ -148,6 +196,7 @@ static const CONF_PARSER log_config[] = { { "colourise",FR_CONF_POINTER(PW_TYPE_BOOLEAN, &do_colourise), NULL }, { "use_utc", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &log_dates_utc), NULL }, { "msg_denied", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.denied_msg), "You are already logged in - access denied" }, + { "suppress_secrets", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &main_config.suppress_secrets), NULL }, CONF_PARSER_TERMINATOR }; @@ -159,6 +208,8 @@ static const CONF_PARSER security_config[] = { { "max_attributes", FR_CONF_POINTER(PW_TYPE_INTEGER, &fr_max_attributes), STRINGIFY(0) }, { "reject_delay", FR_CONF_POINTER(PW_TYPE_TIMEVAL, &main_config.reject_delay), STRINGIFY(0) }, { "status_server", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &main_config.status_server), "no"}, + { "require_message_authenticator", FR_CONF_POINTER(PW_TYPE_STRING, &require_message_authenticator), "auto"}, + { "limit_proxy_state", FR_CONF_POINTER(PW_TYPE_STRING, &limit_proxy_state), "auto"}, #ifdef ENABLE_OPENSSL_VERSION_CHECK { "allow_vulnerable_openssl", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.allow_vulnerable_openssl), "no"}, #endif @@ -196,6 +247,7 @@ static const CONF_PARSER server_config[] = { { "max_request_time", FR_CONF_POINTER(PW_TYPE_INTEGER, &main_config.max_request_time), STRINGIFY(MAX_REQUEST_TIME) }, { "cleanup_delay", FR_CONF_POINTER(PW_TYPE_INTEGER, &main_config.cleanup_delay), STRINGIFY(CLEANUP_DELAY) }, { "max_requests", FR_CONF_POINTER(PW_TYPE_INTEGER, &main_config.max_requests), STRINGIFY(MAX_REQUESTS) }, + { "postauth_client_lost", FR_CONF_POINTER(PW_TYPE_BOOLEAN, &main_config.postauth_client_lost), "no" }, { "pidfile", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.pid_file), "${run_dir}/radiusd.pid"}, { "checkrad", FR_CONF_POINTER(PW_TYPE_STRING, &main_config.checkrad), "${sbindir}/checkrad" }, @@ -499,14 +551,31 @@ static ssize_t xlat_listen_common(REQUEST *request, rad_listen_t *listen, VALUE_PAIR *vp; listen_socket_t *sock = listen->data; + if (!listen->tls) { + RDEBUG("Listener is not using TLS. TLS attributes are not available"); + *out = '\0'; + return 0; + } + for (vp = sock->certs; vp != NULL; vp = vp->next) { if (strcmp(fmt, vp->da->name) == 0) { return vp_prints_value(out, outlen, vp, 0); } } + + RDEBUG("Unknown TLS attribute \"%s\"", fmt); + *out = '\0'; + return 0; + } +#else + if (strncmp(fmt, "TLS-", 4) == 0) { + RDEBUG("Server is not built with TLS support"); + *out = '\0'; + return 0; } #endif + cp = cf_pair_find(listen->cs, fmt); if (!cp || !(value = cf_pair_value(cp))) { RDEBUG("Listener does not contain config item \"%s\"", fmt); @@ -560,7 +629,6 @@ static int switch_users(CONF_SECTION *cs) * initialized. */ if (fr_set_dumpable_init() < 0) { - fr_perror("%s", main_config.name); return 0; } @@ -571,8 +639,7 @@ static int switch_users(CONF_SECTION *cs) if (rad_debug_lvl && (getuid() != 0)) return 1; if (cf_section_parse(cs, NULL, bootstrap_config) < 0) { - fr_strerror_printf("%s: Error: Failed to parse user/group information.\n", - main_config.name); + fr_strerror_printf("Failed to parse user/group information."); return 0; } @@ -587,8 +654,8 @@ static int switch_users(CONF_SECTION *cs) gr = getgrnam(gid_name); if (!gr) { - fr_strerror_printf("%s: Cannot get ID for group %s: %s\n", - main_config.name, gid_name, fr_syserror(errno)); + fr_strerror_printf("Cannot get ID for group %s: %s", + gid_name, fr_syserror(errno)); return 0; } @@ -608,8 +675,8 @@ static int switch_users(CONF_SECTION *cs) struct passwd *user; if (rad_getpwnam(cs, &user, uid_name) < 0) { - fr_strerror_printf("%s: Cannot get passwd entry for user %s: %s\n", - main_config.name, uid_name, fr_strerror()); + fr_strerror_printf("Cannot get passwd entry for user %s: %s", + uid_name, fr_strerror()); return 0; } @@ -621,8 +688,8 @@ static int switch_users(CONF_SECTION *cs) do_suid = true; #ifdef HAVE_INITGROUPS if (initgroups(uid_name, server_gid) < 0) { - fr_strerror_printf("%s: Cannot initialize supplementary group list for user %s: %s\n", - main_config.name, uid_name, fr_syserror(errno)); + fr_strerror_printf("Cannot initialize supplementary group list for user %s: %s", + uid_name, fr_syserror(errno)); talloc_free(user); return 0; } @@ -637,8 +704,8 @@ static int switch_users(CONF_SECTION *cs) */ if (chroot_dir) { if (chroot(chroot_dir) < 0) { - fr_strerror_printf("%s: Failed to perform chroot %s: %s", - main_config.name, chroot_dir, fr_syserror(errno)); + fr_strerror_printf("Failed to perform chroot to %s: %s", + chroot_dir, fr_syserror(errno)); return 0; } @@ -665,8 +732,8 @@ static int switch_users(CONF_SECTION *cs) */ if (do_sgid) { if (setgid(server_gid) < 0){ - fr_strerror_printf("%s: Failed setting group to %s: %s", - main_config.name, gid_name, fr_syserror(errno)); + fr_strerror_printf("Failed setting group to %s: %s", + gid_name, fr_syserror(errno)); return 0; } } @@ -713,8 +780,8 @@ static int switch_users(CONF_SECTION *cs) default_log.fd = open(main_config.log_file, O_WRONLY | O_APPEND | O_CREAT, 0640); if (default_log.fd < 0) { - fr_strerror_printf("%s: Failed to open log file %s: %s\n", - main_config.name, main_config.log_file, fr_syserror(errno)); + fr_strerror_printf("Failed to open log file %s: %s\n", + main_config.log_file, fr_syserror(errno)); return 0; } } @@ -730,8 +797,8 @@ static int switch_users(CONF_SECTION *cs) if ((do_suid || do_sgid) && (default_log.dst == L_DST_FILES)) { if (fchown(default_log.fd, server_uid, server_gid) < 0) { - fr_strerror_printf("%s: Cannot change ownership of log file %s: %s\n", - main_config.name, main_config.log_file, fr_syserror(errno)); + fr_strerror_printf("Cannot change ownership of log file %s: %s\n", + main_config.log_file, fr_syserror(errno)); return 0; } } @@ -750,7 +817,7 @@ static int switch_users(CONF_SECTION *cs) * aren't allowed. */ if (fr_set_dumpable(allow_core_dumps) < 0) { - ERROR("%s", fr_strerror()); + WARN("Failed to allow core dumps - %s", fr_strerror()); } if (allow_core_dumps) { @@ -838,6 +905,8 @@ int main_config_init(void) if (!main_config.dictionary_dir) { main_config.dictionary_dir = DICTDIR; } + main_config.require_ma = FR_BOOL_AUTO; + main_config.limit_proxy_state = FR_BOOL_AUTO; /* * About sizeof(REQUEST) + sizeof(RADIUS_PACKET) * 2 + sizeof(VALUE_PAIR) * 400 @@ -941,6 +1010,9 @@ do {\ for (ci = cf_item_find_next(subcs, NULL); ci != NULL; ci = cf_item_find_next(subcs, ci)) { + + if (cf_item_is_data(ci)) continue; + if (!cf_item_is_pair(ci)) { cf_log_err(ci, "Unexpected item in ENV section"); goto failure; @@ -1066,7 +1138,10 @@ do {\ /* * Switch users as early as possible. */ - if (!switch_users(cs)) fr_exit(1); + if (!switch_users(cs)) { + fprintf(stderr, "%s: ERROR - %s\n", main_config.name, fr_strerror()); + fr_exit(1); + } #endif /* @@ -1127,6 +1202,23 @@ do {\ main_config.init_delay.tv_sec = 0; main_config.init_delay.tv_usec = 2* (1000000 / 3); + { + CONF_PAIR *cp = NULL; + + subcs = cf_section_sub_find(cs, "security"); + if (subcs) cp = cf_pair_find(subcs, "require_message_authenticator"); + if (fr_bool_auto_parse(cp, &main_config.require_ma, require_message_authenticator) < 0) { + cf_file_free(cs); + return -1; + } + + if (subcs) cp = cf_pair_find(subcs, "limit_proxy_state"); + if (fr_bool_auto_parse(cp, &main_config.limit_proxy_state, limit_proxy_state) < 0) { + cf_file_free(cs); + return -1; + } + } + /* * Free the old configuration items, and replace them * with the new ones. 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/process.c b/src/main/process.c index 1a48517d43..2d20ebf552 100644 --- a/src/main/process.c +++ b/src/main/process.c @@ -74,8 +74,14 @@ static char const *action_codes[] = { "dup", "timer", #ifdef WITH_PROXY - "proxy-reply" -#endif + "proxy-reply", +#endif + "request was cancelled", + "conflicting packet was received", + "max_time was reached", + "internal failure", + "cleanup_delay was reached", + "CoA packet was cancelled, and not sent", }; #ifdef DEBUG_STATE_MACHINE @@ -604,6 +610,46 @@ static void request_free(REQUEST *request) #ifdef WITH_PROXY +#ifdef WITH_TLS +void proxy_listener_freeze(rad_listen_t *listener, fr_event_fd_handler_t write_handler) +{ + PTHREAD_MUTEX_LOCK(&proxy_mutex); + if (!fr_packet_list_socket_freeze(proxy_list, + listener->fd)) { + ERROR("Fatal error freezing socket: %s", fr_strerror()); + fr_exit(1); + } + + listener->blocked = true; + + if (fr_event_fd_write_handler(el, 0, listener->fd, write_handler, listener) < 0) { + ERROR("Fatal error freezing socket: %s", fr_strerror()); + fr_exit(1); + } + + PTHREAD_MUTEX_UNLOCK(&proxy_mutex); +} + +void proxy_listener_thaw(rad_listen_t *listener) +{ + PTHREAD_MUTEX_LOCK(&proxy_mutex); + if (!fr_packet_list_socket_thaw(proxy_list, + listener->fd)) { + ERROR("Fatal error freezing socket: %s", fr_strerror()); + fr_exit(1); + } + + listener->blocked = false; + + if (fr_event_fd_write_handler(el, 0, listener->fd, NULL, listener) < 0) { + ERROR("Fatal error freezing socket: %s", fr_strerror()); + fr_exit(1); + } + + PTHREAD_MUTEX_UNLOCK(&proxy_mutex); +} +#endif /* WITH_TLS */ + static void proxy_reply_too_late(REQUEST *request) { char buffer[128]; @@ -632,9 +678,10 @@ static void proxy_reply_too_late(REQUEST *request) * } * \enddot */ -static void request_done(REQUEST *request, int action) +static void request_done(REQUEST *request, int original) { struct timeval now, when; + int action = original; VERIFY_REQUEST(request); @@ -690,7 +737,7 @@ static void request_done(REQUEST *request, int action) home_server_t *home = request->home_pool->servers[i]; if (home->state == HOME_STATE_CONNECTION_FAIL) { - mark_home_server_dead(home, &now); + mark_home_server_dead(home, &now, false); } } } @@ -700,7 +747,7 @@ static void request_done(REQUEST *request, int action) /* * If it was administratively canceled, then it's done. */ - if (action == FR_ACTION_CANCELLED) { + if (action >= FR_ACTION_CANCELLED) { action = FR_ACTION_DONE; #ifdef WITH_COA @@ -887,9 +934,10 @@ static void request_done(REQUEST *request, int action) #endif if (request->packet) { - RDEBUG2("Cleaning up request packet ID %u with timestamp +%d", + RDEBUG2("Cleaning up request packet ID %u with timestamp +%d due to %s", request->packet->id, - (unsigned int) (request->timestamp - fr_start_time)); + (unsigned int) (request->timestamp - fr_start_time), + action_codes[original]); } /* else don't print anything */ ASSERT_MASTER; @@ -958,6 +1006,12 @@ static void request_cleanup_delay_init(REQUEST *request) #ifdef HAVE_PTHREAD_H rad_assert(request->child_pid == NO_SUCH_CHILD_PID); #endif + + /* + * Set the statistics immediately if we can. + */ + request_stats_final(request); + STATE_MACHINE_TIMER(FR_ACTION_TIMER); return; } @@ -994,8 +1048,7 @@ static bool request_max_time(REQUEST *request) * stop" macro already took care of it. */ if (request->child_state == REQUEST_DONE) { - done: - request_done(request, FR_ACTION_CANCELLED); + request_done(request, FR_ACTION_DONE); return true; } @@ -1027,7 +1080,8 @@ static bool request_max_time(REQUEST *request) /* * Tell the request that it's done. */ - goto done; + request_done(request, FR_ACTION_MAX_TIME); + return true; } /* @@ -1096,7 +1150,7 @@ static void request_queue_or_run(REQUEST *request, * Otherwise we're not going to do anything with * it... */ - request_done(request, FR_ACTION_CANCELLED); + request_done(request, FR_ACTION_INTERNAL_FAILURE); return; } #endif @@ -1115,6 +1169,11 @@ static void request_queue_or_run(REQUEST *request, #endif } +void request_inject(REQUEST *request) +{ + request_queue_or_run(request, request_running); +} + static void request_dup(REQUEST *request) { @@ -1203,11 +1262,14 @@ static void request_cleanup_delay(REQUEST *request, int action) #ifdef DEBUG_STATE_MACHINE if (rad_debug_lvl) printf("(%u) ********\tNEXT-STATE %s -> %s\n", request->number, __FUNCTION__, "request_cleanup_delay"); #endif + + request_stats_final(request); + STATE_MACHINE_TIMER(FR_ACTION_TIMER); return; } /* else it's time to clean up */ - request_done(request, FR_ACTION_DONE); + request_done(request, FR_ACTION_CLEANUP_DELAY); break; default: @@ -1282,6 +1344,7 @@ static void request_response_delay(REQUEST *request, int action) } /* else it's time to send the reject */ RDEBUG2("Sending delayed response"); + request->listener->encode(request->listener, request); debug_packet(request, request->reply, false); request->listener->send(request->listener, request); @@ -1327,7 +1390,7 @@ static int request_pre_handler(REQUEST *request, UNUSED int action) /* * Ignore parse errors. */ - if (radius_evaluate_cond(request, RLM_MODULE_OK, 0, debug_condition)) { + if (radius_evaluate_cond(request, RLM_MODULE_OK, 0, debug_condition) == 1) { request->log.lvl = L_DBG_LVL_2; request->log.func = vradlog_request; } @@ -1340,7 +1403,7 @@ static int request_pre_handler(REQUEST *request, UNUSED int action) } if (rcode < 0) { - RATE_LIMIT(INFO("Dropping packet without response because of error: %s", fr_strerror())); + RATE_LIMIT(INFO("Dropping packet without response because of error: %s (from client %s)", fr_strerror(), request->client->shortname)); request->reply->offset = -2; /* bad authenticator */ return 0; } @@ -1506,7 +1569,8 @@ static void request_finish(REQUEST *request, int action) /* * See if we need to delay an Access-Reject packet. */ - if ((request->reply->code == PW_CODE_ACCESS_REJECT) && + if ((request->packet->code == PW_CODE_ACCESS_REQUEST) && + (request->reply->code == PW_CODE_ACCESS_REJECT) && (request->root->reject_delay.tv_sec > 0)) { request->response_delay = request->root->reject_delay; @@ -1534,9 +1598,14 @@ static void request_finish(REQUEST *request, int action) #ifdef WITH_PROXY /* * If we timed out a proxy packet, don't delay - * the reject any more. + * the reject any more. Or, if we proxied it to + * a real home server, then don't delay it. + * + * We don't want to have each proxy in a chain + * adding their own reject delay, which would + * result in N*reject_delays being applied. */ - if (request->proxy && !request->proxy_reply) { + if (request->proxy && (!request->proxy_reply || request->proxy->dst_port != 0)) { request->response_delay.tv_sec = 0; request->response_delay.tv_usec = 0; } @@ -1560,6 +1629,7 @@ static void request_finish(REQUEST *request, int action) } } + request->listener->encode(request->listener, request); debug_packet(request, request->reply, false); request->listener->send(request->listener, request); } @@ -1598,6 +1668,8 @@ static void request_finish(REQUEST *request, int action) */ static void request_running(REQUEST *request, int action) { + int rcode; + VERIFY_REQUEST(request); TRACE_STATE_MACHINE; @@ -1631,7 +1703,8 @@ static void request_running(REQUEST *request, int action) /* * We may need to send a proxied request. */ - if (request_will_proxy(request)) { + rcode = request_will_proxy(request); + if (rcode == 1) { #ifdef DEBUG_STATE_MACHINE if (rad_debug_lvl) printf("(%u) ********\tWill Proxy\t********\n", request->number); #endif @@ -1641,13 +1714,42 @@ static void request_running(REQUEST *request, int action) * up the post proxy fail * handler. */ + retry_proxy: if (request_proxy(request) < 0) { - if (request->home_server && request->home_server->server) goto req_finished; + if (request->home_server && request->home_server->virtual_server) goto req_finished; + + if (request->home_pool && request->home_server && + HOME_SERVER_IS_DEAD(request->home_server)) { + VALUE_PAIR *vp; + REALM *realm = NULL; + home_server_t *home = NULL; + + vp = fr_pair_find_by_num(request->config, PW_PROXY_TO_REALM, 0, TAG_ANY); + if (vp) realm = realm_find2(vp->vp_strvalue); + + /* + * Since request->home_server is dead, + * this function won't pick the same home server as before. + */ + if (realm) home = home_server_ldb(vp->vp_strvalue, request->home_pool, request); + if (home) { + home_server_update_request(home, request); + goto retry_proxy; + } + } (void) setup_post_proxy_fail(request); process_proxy_reply(request, NULL); goto req_finished; } + + } else if (rcode < 0) { + /* + * No live home servers, run Post-Proxy-Type Fail. + */ + (void) setup_post_proxy_fail(request); + process_proxy_reply(request, NULL); + goto req_finished; } else #endif { @@ -1762,7 +1864,7 @@ int request_receive(TALLOC_CTX *ctx, rad_listen_t *listener, RADIUS_PACKET *pack * the request just as we're logging the * complaint. */ - request_done(request, FR_ACTION_CANCELLED); + request_done(request, FR_ACTION_CONFLICT); request = NULL; /* @@ -1851,7 +1953,7 @@ skip_dup: if (!listener->nodup) { if (!rbtree_insert(pl, &request->packet)) { RERROR("Failed to insert request in the list of live requests: discarding it"); - request_done(request, FR_ACTION_CANCELLED); + request_done(request, FR_ACTION_INTERNAL_FAILURE); return 1; } @@ -2016,23 +2118,7 @@ static void tcp_socket_timer(void *ctx) fr_event_now(el, &now); - switch (listener->type) { -#ifdef WITH_PROXY - case RAD_LISTEN_PROXY: - limit = &sock->home->limit; - break; -#endif - - case RAD_LISTEN_AUTH: -#ifdef WITH_ACCOUNTING - case RAD_LISTEN_ACCT: -#endif - limit = &sock->limit; - break; - - default: - return; - } + limit = &sock->limit; /* * If we enforce a lifetime, do it now. @@ -2069,6 +2155,16 @@ static void tcp_socket_timer(void *ctx) * Mark the socket as "don't use if at all possible". */ listener->status = RAD_LISTEN_STATUS_FROZEN; + + /* + * If it's blocked, then push all of the requests to other sockets. + */ +#ifdef WITH_TLS + if (listener->blocked) { + listener->status = RAD_LISTEN_STATUS_REMOVE_NOW; + } +#endif + event_new_fd(listener); return; } @@ -2222,11 +2318,9 @@ static void remove_from_proxy_hash_nl(REQUEST *request, bool yank) } } -#ifdef WITH_TCP if (request->proxy_listener) { request->proxy_listener->count--; } -#endif request->proxy_listener = NULL; /* @@ -2278,7 +2372,7 @@ static int insert_into_proxy_hash(REQUEST *request) PTHREAD_MUTEX_LOCK(&proxy_mutex); - proxy_listener = NULL; + proxy_listener = request->proxy_listener; /* may or may not be NULL */ request->num_proxied_requests = 1; request->num_proxied_responses = 0; @@ -2288,8 +2382,8 @@ static int insert_into_proxy_hash(REQUEST *request) RDEBUG3("proxy: Trying to allocate ID (%d/2)", tries); success = fr_packet_list_id_alloc(proxy_list, - request->home_server->proto, - &request->proxy, &proxy_listener); + request->home_server->proto, + &request->proxy, &proxy_listener); if (success) break; if (tries > 0) continue; /* try opening new socket only once */ @@ -2298,6 +2392,16 @@ static int insert_into_proxy_hash(REQUEST *request) if (proxy_no_new_sockets) break; #endif + /* + * Don't try to add a new listener if it's not possible. + */ + if (fr_event_list_full(el)) { + RATE_LIMIT(ERROR("Cannot open a new proxy socket - too many sockets are already open")); + request->home_server->state = HOME_STATE_CONNECTION_FAIL; + PTHREAD_MUTEX_UNLOCK(&proxy_mutex); + goto fail; + } + RDEBUG3("proxy: Trying to open a new listener to the home server"); this = proxy_new_listener(proxy_ctx, request->home_server, 0); if (!this) { @@ -2362,9 +2466,7 @@ static int insert_into_proxy_hash(REQUEST *request) */ request->home_server->currently_outstanding++; -#ifdef WITH_TCP request->proxy_listener->count++; -#endif PTHREAD_MUTEX_UNLOCK(&proxy_mutex); @@ -2389,7 +2491,7 @@ static int process_proxy_reply(REQUEST *request, RADIUS_PACKET *reply) /* * There may be a proxy reply, but it may be too late. */ - if ((request->home_server && !request->home_server->server) && !request->proxy_listener) return 0; + if ((request->home_server && !request->home_server->virtual_server) && !request->proxy_listener) return 0; /* * Delete any reply we had accumulated until now. @@ -2453,7 +2555,7 @@ static int process_proxy_reply(REQUEST *request, RADIUS_PACKET *reply) * Decode the packet if required. */ if (request->proxy_listener) { - rcode = request->proxy_listener->decode(request->proxy_listener, request); + rcode = request->proxy_listener->proxy_decode(request->proxy_listener, request); debug_packet(request, reply, true); /* @@ -2473,19 +2575,18 @@ static int process_proxy_reply(REQUEST *request, RADIUS_PACKET *reply) remove_from_proxy_hash(request); } - old_server = request->server; /* - * If the home server is virtual, just run pre_proxy from - * that section. + * Run the request through the virtual server for the + * home server, OR through the virtual server for the + * home server pool. */ - if (request->home_server && request->home_server->server) { - request->server = request->home_server->server; + old_server = request->server; + if (request->home_server && request->home_server->virtual_server) { + request->server = request->home_server->virtual_server; - } else { - if (request->home_pool && request->home_pool->virtual_server) { + } else if (request->home_pool && request->home_pool->virtual_server) { request->server = request->home_pool->virtual_server; - } } /* @@ -2598,11 +2699,79 @@ int request_proxy_reply(RADIUS_PACKET *packet) * ignore it. This does the MD5 calculations in the * server core, but I guess we can fix that later. */ - if (!request->proxy_reply && - (rad_verify(packet, request->proxy, - request->home_server->secret) != 0)) { - DEBUG("Ignoring spoofed proxy reply. Signature is invalid"); - return 0; + if (!request->proxy_reply) { + decode_fail_t reason; + + /* + * If the home server configuration requires a Message-Authenticator, then set the flag, + * but only if the proxied packet is Access-Request or Status-Sercer. + * + * The realms.c file already clears require_ma for TLS connections. + */ + bool require_ma = (request->home_server->require_ma == FR_BOOL_TRUE) && (request->proxy->code == PW_CODE_ACCESS_REQUEST); + + if (!request->home_server) { + proxy_reply_too_late(request); + return 0; + } + + if (!rad_packet_ok(packet, require_ma, &reason)) { + DEBUG("Ignoring invalid packet - %s", fr_strerror()); + return 0; + } + + if (rad_verify(packet, request->proxy, + request->home_server->secret) != 0) { + DEBUG("Ignoring spoofed proxy reply. Signature is invalid"); + return 0; + } + + /* + * BlastRADIUS checks. We're running in the main + * listener thread, so there's no conflict + * checking or setting these fields. + */ + if ((request->proxy->code == PW_CODE_ACCESS_REQUEST) && +#ifdef WITH_TLS + !request->home_server->tls && +#endif + !packet->eap_message) { + if (request->home_server->require_ma == FR_BOOL_AUTO) { + if (!packet->message_authenticator) { + RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + RERROR("BlastRADIUS check: Received response to Access-Request without Message-Authenticator."); + RERROR("Setting \"require_message_authenticator = false\" for home_server %s", request->home_server->name); + RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + RERROR("UPGRADE THE HOME SERVER AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); + RERROR("Once the home_server is upgraded, set \"require_message_authenticator = true\" for home_server %s.", request->home_server->name); + RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + request->home_server->require_ma = FR_BOOL_FALSE; + } else { + RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + RERROR("BlastRADIUS check: Received response to Access-Request with Message-Authenticator."); + RERROR("Setting \"require_message_authenticator = true\" for home_server %s", request->home_server->name); + RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + RERROR("It looks like the home server has been updated to protect from the BlastRADIUS attack."); + RERROR("Please set \"require_message_authenticator = true\" for home_server %s", request->home_server->name); + RERROR("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + request->home_server->require_ma = FR_BOOL_TRUE; + } + + } else if (fr_debug_lvl && (request->home_server->require_ma == FR_BOOL_FALSE) && !packet->message_authenticator) { + /* + * If it's "no" AND we don't have a Message-Authenticator, then complain on every packet. + */ + RDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + RDEBUG("BlastRADIUS check: Received packet without Message-Authenticator from home_server %s", request->home_server->name); + RDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + RDEBUG("The packet does not contain Message-Authenticator, which is a security issue"); + RDEBUG("UPGRADE THE HOME SERVER AS YOUR NETWORK IS VULNERABLE TO THE BLASTRADIUS ATTACK."); + RDEBUG("Once the home server is upgraded, set \"require_message_authenticator = true\" for home_server %s", request->home_server->name); + RDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + } + } } /* @@ -2887,8 +3056,9 @@ static void proxy_running(REQUEST *request, int action) * The key attributes are: * - PW_PROXY_TO_REALM - Specifies a realm the request should be proxied to. * - PW_HOME_SERVER_POOL - Specifies a specific home server pool to proxy to. - * - PW_PACKET_DST_IP_ADDRESS - Specifies a specific IPv4 home server to proxy to. - * - PW_PACKET_DST_IPV6_ADDRESS - Specifies a specific IPv6 home server to proxy to. + * - PW_HOME_SERVER_NAME - Specifies a home server by name + * - PW_PACKET_DST_IP_ADDRESS - Specifies a home server by IPv4 address + * - PW_PACKET_DST_IPV6_ADDRESS - Specifies a home server by IPv5 address * * Certain packet types such as #PW_CODE_STATUS_SERVER will never be proxied. * @@ -2918,7 +3088,9 @@ static int request_will_proxy(REQUEST *request) VERIFY_REQUEST(request); - if (!request->root->proxy_requests) return 0; + if (!request->root->proxy_requests) { + return 0; + } if (request->packet->dst_port == 0) return 0; if (request->packet->code == PW_CODE_STATUS_SERVER) return 0; if (request->in_proxy_hash) return 0; @@ -3052,7 +3224,55 @@ static int request_will_proxy(REQUEST *request) * The home server is alive (or may be alive). * Send the packet to the IP. */ - if (home->state < HOME_STATE_IS_DEAD) goto do_home; + if (!HOME_SERVER_IS_DEAD(home)) goto do_home; + + /* + * The home server is dead. If you wanted + * fail-over, you should have proxied to a pool. + * Sucks to be you. + */ + + return 0; + + } else if ((vp = fr_pair_find_by_num(request->config, PW_HOME_SERVER_NAME, 0, TAG_ANY)) != NULL) { + int type; + + switch (request->packet->code) { + case PW_CODE_ACCESS_REQUEST: + type = HOME_TYPE_AUTH; + break; + +#ifdef WITH_ACCOUNTING + case PW_CODE_ACCOUNTING_REQUEST: + type = HOME_TYPE_ACCT; + break; +#endif + +#ifdef WITH_COA + case PW_CODE_COA_REQUEST: + case PW_CODE_DISCONNECT_REQUEST: + type = HOME_TYPE_COA; + break; +#endif + + default: + return 0; + } + + /* + * Find the home server by name. + */ + home = home_server_byname(vp->vp_strvalue, type); + if (!home) { + RWDEBUG("No such home server %s", vp->vp_strvalue); + return 0; + } + + /* + * The home server is alive (or may be alive). + * Send the packet to the IP. + */ + if (!HOME_SERVER_IS_DEAD(home)) goto do_home; /* * The home server is dead. If you wanted @@ -3063,6 +3283,7 @@ static int request_will_proxy(REQUEST *request) return 0; } else { + return 0; } @@ -3081,8 +3302,8 @@ static int request_will_proxy(REQUEST *request) home = home_server_ldb(realmname, pool, request); if (!home) { - REDEBUG2("Failed to find live home server: Cancelling proxy"); - return 1; + REDEBUG2("Failed to find live home server for realm %s: Cancelling proxy", realmname); + return -1; } do_home: @@ -3093,7 +3314,11 @@ do_home: * Once we've decided to proxy a request, we cannot send * a CoA packet. So we free up any CoA packet here. */ - if (request->coa) request_done(request->coa, FR_ACTION_CANCELLED); + if (request->coa) { + RWDEBUG("Cannot proxy and originate CoA packets at the same time. Cancelling CoA request"); + request_done(request->coa, FR_ACTION_COA_CANCELLED); + request->coa = NULL; + } #endif /* @@ -3179,14 +3404,14 @@ do_home: pre_proxy_type = vp->vp_integer; } - old_server = request->server; - /* - * If the home server is virtual, just run pre_proxy from - * that section. + * Run the request through the virtual server for the + * home server, OR through the virtual server for the + * home server pool. */ - if (request->home_server && request->home_server->server) { - request->server = request->home_server->server; + old_server = request->server; + if (request->home_server && request->home_server->virtual_server) { + request->server = request->home_server->virtual_server; } else { char buffer[128]; @@ -3245,7 +3470,7 @@ static int proxy_to_virtual_server(REQUEST *request) } DEBUG("Proxying to virtual server %s", - request->home_server->server); + request->home_server->virtual_server); /* * Packets to virtual servers don't get @@ -3260,7 +3485,7 @@ static int proxy_to_virtual_server(REQUEST *request) fake->packet->vps = fr_pair_list_copy(fake->packet, request->packet->vps); talloc_free(request->proxy); - fake->server = request->home_server->server; + fake->server = request->home_server->virtual_server; fake->handle = request->handle; fake->process = NULL; /* should never be run for anything */ @@ -3309,7 +3534,8 @@ static int request_proxy(REQUEST *request) #ifdef WITH_COA if (request->coa) { RWDEBUG("Cannot proxy and originate CoA packets at the same time. Cancelling CoA request"); - request_done(request->coa, FR_ACTION_CANCELLED); + request_done(request->coa, FR_ACTION_COA_CANCELLED); + request->coa = NULL; } #endif @@ -3329,7 +3555,7 @@ static int request_proxy(REQUEST *request) * * So, we have some horrible hacks to get around that. */ - if (request->home_server->server) return proxy_to_virtual_server(request); + if (request->home_server->virtual_server) return proxy_to_virtual_server(request); /* * We're actually sending a proxied packet. Do that now. @@ -3372,7 +3598,7 @@ static int request_proxy(REQUEST *request) /* * Encode the packet before we do anything else. */ - request->proxy_listener->encode(request->proxy_listener, request); + request->proxy_listener->proxy_encode(request->proxy_listener, request); debug_packet(request, request->proxy, false); /* @@ -3392,7 +3618,7 @@ static int request_proxy(REQUEST *request) /* * And send the packet. */ - request->proxy_listener->send(request->proxy_listener, request); + request->proxy_listener->proxy_send(request->proxy_listener, request); return 1; } @@ -3458,7 +3684,7 @@ static int request_proxy_anew(REQUEST *request) * If so, run that instead of doing proxying to a real * server. */ - if (home->server) { + if (home->virtual_server) { request->home_server = home; TALLOC_FREE(request->proxy); @@ -3538,7 +3764,7 @@ static void request_ping(REQUEST *request, int action) * * If it's zombie, we mark it alive immediately. */ - if ((home->state >= HOME_STATE_IS_DEAD) && + if (HOME_SERVER_IS_DEAD(home) && (home->num_received_pings < home->num_pings_to_alive)) { return; } @@ -3610,7 +3836,7 @@ static void ping_home_server(void *ctx) if (timercmp(&when, &now, <)) { DEBUG("PING: Zombie period is over for home server %s", home->log_name); - mark_home_server_dead(home, &now); + mark_home_server_dead(home, &now, false); } } @@ -3639,6 +3865,7 @@ static void ping_home_server(void *ctx) NO_CHILD_THREAD; request->proxy = rad_alloc(request, true); + request->root = &main_config; rad_assert(request->proxy != NULL); if (home->ping_check == HOME_PING_CHECK_STATUS_SERVER) { @@ -3735,9 +3962,10 @@ static void ping_home_server(void *ctx) home->num_sent_pings++; rad_assert(request->proxy_listener != NULL); + request->proxy_listener->proxy_encode(request->proxy_listener, request); debug_packet(request, request->proxy, false); - request->proxy_listener->send(request->proxy_listener, - request); + request->proxy_listener->proxy_send(request->proxy_listener, + request); /* * Add +/- 2s of jitter, as suggested in RFC 3539 @@ -3840,7 +4068,34 @@ void revive_home_server(void *ctx) home->port); } -void mark_home_server_dead(home_server_t *home, struct timeval *when) +#ifdef WITH_TLS +static int eol_home_listener(UNUSED void *ctx, void *data) +{ + rad_listen_t *this = talloc_get_type_abort(data, rad_listen_t); + + /* + * The socket isn't blocked, we can still use it. + * + * i.e. the home server is dead for a reason OTHER than + * "all available sockets are blocked". + * + * We can still ping the home server via sockets which + * are writable. + */ + if (!this->blocked) return 0; + + this->status = RAD_LISTEN_STATUS_EOL; + + FD_MUTEX_LOCK(&fd_mutex); + this->next = new_listeners; + new_listeners = this; + FD_MUTEX_UNLOCK(&fd_mutex); + + return 1; /* alway delete from this tree */ +} +#endif + +void mark_home_server_dead(home_server_t *home, struct timeval *when, bool down) { int previous_state = home->state; char buffer[128]; @@ -3853,6 +4108,34 @@ void mark_home_server_dead(home_server_t *home, struct timeval *when) home->state = HOME_STATE_IS_DEAD; home_trigger(home, "home_server.dead"); +#ifdef WITH_TLS + /* + * If the home server is dead, then close all of the sockets associated with it. + * + * Note that the "EOL listener" code expects to _also_ + * delete the listeners. At which point we end up with a + * mutex locked twice, and bad things happen. The + * solution is to move the listeners to the global + * "waiting for update" list, and then notify ourselves + * that there are listeners waiting to be updated. + */ + if (home->listeners) { + ASSERT_MASTER; + + rbtree_walk(home->listeners, RBTREE_DELETE_ORDER, eol_home_listener, NULL); + radius_signal_self(RADIUS_SIGNAL_SELF_NEW_FD); + } +#endif + + /* + * Administratively down - don't do anything to bring it + * up. + */ + if (down) { + home->state = HOME_STATE_ADMIN_DOWN; + return; + } + /* * Ping it if configured, AND we can ping it. */ @@ -3925,14 +4208,14 @@ static void proxy_wait_for_reply(REQUEST *request, int action) * The request was proxied to a virtual server. * Ignore the retransmit. */ - if (request->home_server->server) return; + if (request->home_server->virtual_server) return; /* * Failed connections get the home server marked * as dead. */ if (home->state == HOME_STATE_CONNECTION_FAIL) { - mark_home_server_dead(home, &now); + mark_home_server_dead(home, &now, false); } /* @@ -3948,7 +4231,7 @@ static void proxy_wait_for_reply(REQUEST *request, int action) * If the listener is known or frozen, use it for * retransmits. */ - if ((home->state >= HOME_STATE_IS_DEAD) || + if (HOME_SERVER_IS_DEAD(home) || !request->proxy_listener || (request->proxy_listener->status >= RAD_LISTEN_STATUS_EOL)) { request_proxy_anew(request); @@ -3980,7 +4263,7 @@ static void proxy_wait_for_reply(REQUEST *request, int action) when.tv_sec++; if (timercmp(&now, &when, <)) { - DEBUG2("Suppressing duplicate proxied request (too fast) to home server %s port %d proto TCP - ID: %d", + DEBUG2("Suppressing duplicate proxied request (too fast) to home server %s port %d - ID: %d", inet_ntop(request->proxy->dst_ipaddr.af, &request->proxy->dst_ipaddr.ipaddr, buffer, sizeof(buffer)), @@ -4014,7 +4297,7 @@ static void proxy_wait_for_reply(REQUEST *request, int action) home->last_packet_sent = now.tv_sec; request->proxy->timestamp = now; debug_packet(request, request->proxy, false); - request->proxy_listener->send(request->proxy_listener, request); + request->proxy_listener->proxy_send(request->proxy_listener, request); break; case FR_ACTION_TIMER: @@ -4023,7 +4306,7 @@ static void proxy_wait_for_reply(REQUEST *request, int action) * as dead. */ if (home->state == HOME_STATE_CONNECTION_FAIL) { - mark_home_server_dead(home, &now); + mark_home_server_dead(home, &now, false); } response_window = request_response_window(request); @@ -4175,6 +4458,7 @@ static void request_coa_originate(REQUEST *request) VALUE_PAIR *vp; REQUEST *coa; fr_ipaddr_t ipaddr; + char const *old_server; char buffer[256]; VERIFY_REQUEST(request); @@ -4202,7 +4486,7 @@ static void request_coa_originate(REQUEST *request) if (!main_config.proxy_requests) { RWDEBUG("Cannot originate CoA packets unless 'proxy_requests = yes'"); - TALLOC_FREE(request->coa); + TALLOC_FREE(request->coa); return; } @@ -4234,11 +4518,11 @@ static void request_coa_originate(REQUEST *request) /* * Prefer the pool to one server */ - } else if (request->client->coa_pool) { - coa->home_pool = request->client->coa_pool; + } else if (request->client->coa_home_pool) { + coa->home_pool = request->client->coa_home_pool; - } else if (request->client->coa_server) { - coa->home_server = request->client->coa_server; + } else if (request->client->coa_home_server) { + coa->home_server = request->client->coa_home_server; } else { /* @@ -4318,19 +4602,26 @@ static void request_coa_originate(REQUEST *request) pre_proxy_type = vp->vp_integer; } - if (coa->home_pool && coa->home_pool->virtual_server) { - char const *old_server = coa->server; + /* + * Run the request through the virtual server for the + * home server, OR through the virtual server for the + * home server pool. + */ + old_server = request->server; + if (coa->home_server && coa->home_server->virtual_server) { + coa->server = coa->home_server->virtual_server; + } else if (coa->home_pool && coa->home_pool->virtual_server) { coa->server = coa->home_pool->virtual_server; - RDEBUG2("server %s {", coa->server); - RINDENT(); - rcode = process_pre_proxy(pre_proxy_type, coa); - REXDENT(); - RDEBUG2("}"); - coa->server = old_server; - } else { - rcode = process_pre_proxy(pre_proxy_type, coa); } + + RDEBUG2("server %s {", coa->server); + RINDENT(); + rcode = process_pre_proxy(pre_proxy_type, coa); + REXDENT(); + RDEBUG2("}"); + coa->server = old_server; + switch (rcode) { default: goto fail; @@ -4379,7 +4670,7 @@ static void request_coa_originate(REQUEST *request) /* * Encode the packet before we do anything else. */ - coa->proxy_listener->encode(coa->proxy_listener, coa); + coa->proxy_listener->proxy_encode(coa->proxy_listener, coa); debug_packet(coa, coa->proxy, false); #ifdef DEBUG_STATE_MACHINE @@ -4404,7 +4695,7 @@ static void request_coa_originate(REQUEST *request) /* * And send the packet. */ - coa->proxy_listener->send(coa->proxy_listener, coa); + coa->proxy_listener->proxy_send(coa->proxy_listener, coa); } @@ -4420,11 +4711,11 @@ static void coa_retransmit(REQUEST *request) * Don't do fail-over. This is a 3.1 feature. */ if (!request->home_server || - (request->home_server->state >= HOME_STATE_IS_DEAD) || + HOME_SERVER_IS_DEAD(request->home_server) || request->proxy_reply || !request->proxy_listener || (request->proxy_listener->status >= RAD_LISTEN_STATUS_EOL)) { - request_done(request, FR_ACTION_CANCELLED); + request_done(request, FR_ACTION_COA_CANCELLED); return; } @@ -4548,8 +4839,8 @@ static void coa_retransmit(REQUEST *request) request->proxy->dst_port, request->proxy->id); - request->proxy_listener->send(request->proxy_listener, - request); + request->proxy_listener->proxy_send(request->proxy_listener, + request); } @@ -4579,7 +4870,7 @@ static bool coa_max_time(REQUEST *request) */ if (request->child_state == REQUEST_DONE) { done: - request_done(request, FR_ACTION_CANCELLED); + request_done(request, FR_ACTION_MAX_TIME); return true; } @@ -4615,7 +4906,8 @@ static bool coa_max_time(REQUEST *request) buffer, sizeof(buffer)), request->proxy->dst_port, mrd); - goto done; + request_done(request, FR_ACTION_DONE); + return true; } #ifdef HAVE_PTHREAD_H @@ -4948,15 +5240,14 @@ static void event_status(struct timeval *wake) } } -#ifdef WITH_TCP static void listener_free_cb(void *ctx) { rad_listen_t *this = talloc_get_type_abort(ctx, rad_listen_t); + listen_socket_t *sock = this->data; char buffer[1024]; if (this->count > 0) { struct timeval when; - listen_socket_t *sock = this->data; fr_event_now(el, &when); when.tv_sec += 3; @@ -4977,9 +5268,13 @@ static void listener_free_cb(void *ctx) this->print(this, buffer, sizeof(buffer)); DEBUG("... cleaning up socket %s", buffer); rad_assert(this->next == NULL); +#ifdef WITH_TCP + fr_event_delete(el, &sock->ev); +#endif talloc_free(this); } +#ifdef WITH_TCP #ifdef WITH_PROXY static int proxy_eol_cb(void *ctx, void *data) { @@ -5024,6 +5319,7 @@ static int proxy_eol_cb(void *ctx, void *data) static void event_new_fd(rad_listen_t *this) { char buffer[1024]; + listen_socket_t *sock = NULL; ASSERT_MASTER; @@ -5031,29 +5327,28 @@ static void event_new_fd(rad_listen_t *this) this->print(this, buffer, sizeof(buffer)); - if (this->status == RAD_LISTEN_STATUS_INIT) { - listen_socket_t *sock = this->data; - + if (this->type != RAD_LISTEN_DETAIL) { + sock = this->data; rad_assert(sock != NULL); + } + + if (this->status == RAD_LISTEN_STATUS_INIT) { if (just_started) { DEBUG("Listening on %s", buffer); - } else { - INFO(" ... adding new socket %s", buffer); - } #ifdef WITH_PROXY - if (!just_started && (this->type == RAD_LISTEN_PROXY)) { - home_server_t *home; - - home = sock->home; - if (!home || !home->limit.max_connections) { - INFO(" ... adding new socket %s", buffer); - } else { + } else if (this->type == RAD_LISTEN_PROXY) { + home_server_t *home = sock->home; + + if (home && home->limit.max_connections) { INFO(" ... adding new socket %s (%u of %u)", buffer, home->limit.num_connections, home->limit.max_connections); + } else { + INFO(" ... adding new socket %s", buffer); } - #endif + } else { + INFO(" ... adding new socket %s", buffer); } switch (this->type) { @@ -5084,6 +5379,7 @@ static void event_new_fd(rad_listen_t *this) */ case RAD_LISTEN_PROXY: #ifdef WITH_TCP + rad_assert(sock != NULL); rad_assert((sock->proto == IPPROTO_UDP) || (sock->home != NULL)); /* @@ -5102,6 +5398,19 @@ static void event_new_fd(rad_listen_t *this) rad_panic("Failed to insert event"); } } + + /* + * Run a callback to do any specific + * signalling on "connection up". + * + * For TLS sockets and WITH_COA_TUNNEL, + * this function should be similar to + * ping_home_server(), except that it + * should send a Status-Server packet, + * with Originating-Realm-Key as a VSA. + */ +// process_listener_up(this); + #endif /* WITH_TCP */ break; #endif /* WITH_PROXY */ @@ -5136,16 +5445,35 @@ static void event_new_fd(rad_listen_t *this) /* * All sockets: add the FD to the event handler. */ + insert_fd: if (fr_event_fd_insert(el, 0, this->fd, event_socket_handler, this)) { this->status = RAD_LISTEN_STATUS_KNOWN; return; } - ERROR("Failed adding event handler for socket: %s", fr_strerror()); - this->status = RAD_LISTEN_STATUS_REMOVE_NOW; + /* + * Print out which socket failed. + * + * If we're trying to add the socket, then + * forcibly remove it immediately, without any + * additional cleanups. There cannot, and MUST + * NOT be any packets associated with the socket. + */ + this->print(this, buffer, sizeof(buffer)); + ERROR("Failed adding event handler for socket %s: %s", buffer, fr_strerror()); + + this->status = RAD_LISTEN_STATUS_EOL; + goto listener_is_eol; } /* end of INIT */ + if (this->status == RAD_LISTEN_STATUS_PAUSE) { + fr_event_fd_delete(el, 0, this->fd); + return; + } + + if (this->status == RAD_LISTEN_STATUS_RESUME) goto insert_fd; + #ifdef WITH_TCP /* * The socket has reached a timeout. Try to close it. @@ -5157,7 +5485,6 @@ static void event_new_fd(rad_listen_t *this) */ if (this->count > 0) { struct timeval when; - listen_socket_t *sock = this->data; /* * Try again to clean up the socket in 30 @@ -5179,6 +5506,7 @@ static void event_new_fd(rad_listen_t *this) fr_event_fd_delete(el, 0, this->fd); this->status = RAD_LISTEN_STATUS_REMOVE_NOW; } +#endif /* WITH_TCP */ /* * The socket has had a catastrophic error. Close it. @@ -5189,6 +5517,7 @@ static void event_new_fd(rad_listen_t *this) */ fr_event_fd_delete(el, 0, this->fd); + listener_is_eol: #ifdef WITH_PROXY /* * Tell all requests using this socket that the socket is dead. @@ -5214,7 +5543,6 @@ static void event_new_fd(rad_listen_t *this) */ if (this->count > 0) { struct timeval when; - listen_socket_t *sock = this->data; /* * Try again to clean up the socket in 30 @@ -5238,17 +5566,16 @@ static void event_new_fd(rad_listen_t *this) */ this->status = RAD_LISTEN_STATUS_REMOVE_NOW; } /* socket is at EOL */ -#endif /* WITH_TCP */ + + if (this->dead) goto wait_some_more; /* * Nuke the socket. */ if (this->status == RAD_LISTEN_STATUS_REMOVE_NOW) { int devnull; -#ifdef WITH_TCP - listen_socket_t *sock = this->data; - struct timeval when; -#endif + + this->dead = true; /* * Re-open the socket, pointing it to /dev/null. @@ -5290,6 +5617,7 @@ static void event_new_fd(rad_listen_t *this) */ if (this->type == RAD_LISTEN_PROXY) { home_server_t *home; + sock = this->data; home = sock->home; if (!home || !home->limit.max_connections) { @@ -5307,6 +5635,20 @@ static void event_new_fd(rad_listen_t *this) buffer, fr_strerror()); fr_exit(1); } + +#ifdef WITH_TLS + /* + * Remove this socket from the list of sockets assocated with this home server. + * + * This MUST be done with the proxy mutex locked! + */ + if (home && home->tls) { + fr_assert(home->listeners); + + (void) rbtree_deletebydata(home->listeners, this); + } +#endif + PTHREAD_MUTEX_UNLOCK(&proxy_mutex); } else #endif /* WITH_PROXY */ @@ -5324,7 +5666,10 @@ static void event_new_fd(rad_listen_t *this) */ if (!spawn_flag) { ASSERT_MASTER; - if (sock->ev) fr_event_delete(el, &sock->ev); + + if (this->type != RAD_LISTEN_DETAIL && sock && sock->ev) { + fr_event_delete(el, &sock->ev); + } listen_free(&this); return; } @@ -5332,14 +5677,8 @@ static void event_new_fd(rad_listen_t *this) /* * Wait until all requests using this socket are done. */ - gettimeofday(&when, NULL); - when.tv_sec += 3; - - ASSERT_MASTER; - if (!fr_event_insert(el, listener_free_cb, this, &when, - &(sock->ev))) { - rad_panic("Failed to insert event"); - } + wait_some_more: + listener_free_cb(this); #endif /* WITH_TCP */ } @@ -5603,7 +5942,10 @@ static void check_proxy(rad_listen_t *head) rad_listen_t *this; if (check_config) return; - if (!main_config.proxy_requests) return; + if (!main_config.proxy_requests) { + DEBUG3("Cannot proxy packets unless 'proxy_requests = yes'"); + return; + } if (!head) return; #ifdef WITH_TCP if (!home_servers_udp) return; diff --git a/src/main/radclient.c b/src/main/radclient.c index 52d2872b13..b48d97235e 100644 --- a/src/main/radclient.c +++ b/src/main/radclient.c @@ -28,6 +28,10 @@ RCSID("$Id$") #include #include #include +#ifdef HAVE_OPENSSL_SSL_H +#include +#include +#endif #include #ifdef HAVE_GETOPT_H @@ -36,6 +40,8 @@ RCSID("$Id$") #include +USES_APPLE_DEPRECATED_API + typedef struct REQUEST REQUEST; /* to shut up warnings about mschap.h */ #include "smbdes.h" @@ -54,6 +60,7 @@ static fr_ipaddr_t server_ipaddr; static int resend_count = 1; static bool done = true; static bool print_filename = false; +static bool blast_radius = false; static fr_ipaddr_t client_ipaddr; static uint16_t client_port = 0; @@ -89,6 +96,7 @@ static void NEVER_RETURNS usage(void) fprintf(stderr, " One of auth, acct, status, coa, disconnect or auto.\n"); fprintf(stderr, " -4 Use IPv4 address of server\n"); fprintf(stderr, " -6 Use IPv6 address of server.\n"); + fprintf(stderr, " -b Mandate checks for Blast RADIUS (this is not set by default).\n"); fprintf(stderr, " -c Send each packet 'count' times.\n"); fprintf(stderr, " -d Set user dictionary directory (defaults to " RADDBDIR ").\n"); fprintf(stderr, " -D Set main dictionary directory (defaults to " DICTDIR ").\n"); @@ -155,9 +163,60 @@ static int _rc_request_free(rc_request_t *request) return 0; } +#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L +# include + +static OSSL_PROVIDER *openssl_default_provider = NULL; +static OSSL_PROVIDER *openssl_legacy_provider = NULL; + +static int openssl3_init(void) +{ + /* + * Load the default provider for most algorithms + */ + openssl_default_provider = OSSL_PROVIDER_load(NULL, "default"); + if (!openssl_default_provider) { + ERROR("(TLS) Failed loading default provider"); + return -1; + } + + /* + * Needed for MD4 + * + * https://www.openssl.org/docs/man3.0/man7/migration_guide.html#Legacy-Algorithms + */ + openssl_legacy_provider = OSSL_PROVIDER_load(NULL, "legacy"); + if (!openssl_legacy_provider) { + ERROR("(TLS) Failed loading legacy provider"); + return -1; + } + + return 0; +} + +static void openssl3_free(void) +{ + if (openssl_default_provider && !OSSL_PROVIDER_unload(openssl_default_provider)) { + ERROR("Failed unloading default provider"); + } + openssl_default_provider = NULL; + + if (openssl_legacy_provider && !OSSL_PROVIDER_unload(openssl_legacy_provider)) { + ERROR("Failed unloading legacy provider"); + } + openssl_legacy_provider = NULL; +} +#else +#define openssl3_init() +#define openssl3_free() +#endif + + + static int mschapv1_encode(RADIUS_PACKET *packet, VALUE_PAIR **request, char const *password) { + int rcode; unsigned int i; uint8_t *p; VALUE_PAIR *challenge, *reply; @@ -190,9 +249,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 +1018,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); /* @@ -1000,6 +1058,130 @@ static int send_one_packet(rc_request_t *request) return 0; } +/* + * Do Blast RADIUS checks. + * + * The request is an Access-Request, and does NOT contain Proxy-State. + * + * The reply is a raw packet, and is NOT yet decoded. + */ +static int blast_radius_check(rc_request_t *request, RADIUS_PACKET *reply) +{ + uint8_t *attr, *end; + VALUE_PAIR *vp; + bool have_message_authenticator = false; + + /* + * We've received a raw packet. Nothing has (as of yet) checked + * anything in it other than the length, and that it's a + * well-formed RADIUS packet. + */ + switch (reply->data[0]) { + case PW_CODE_ACCESS_ACCEPT: + case PW_CODE_ACCESS_REJECT: + case PW_CODE_ACCESS_CHALLENGE: + if (reply->data[1] != request->packet->id) { + ERROR("Invalid reply ID %d to Access-Request ID %d", reply->data[1], request->packet->id); + return -1; + } + break; + + default: + ERROR("Invalid reply code %d to Access-Request", reply->data[0]); + return -1; + } + + /* + * If the reply has a Message-Authenticator, then it MIGHT be fine. + */ + attr = reply->data + 20; + end = reply->data + reply->data_len; + + /* + * It should be the first attribute, so we warn if it isn't there. + * + * But it's not a fatal error. + */ + if (blast_radius && (attr[0] != PW_MESSAGE_AUTHENTICATOR)) { + RDEBUG("WARNING The %s reply packet does not have Message-Authenticator as the first attribute. The packet may be vulnerable to Blast RADIUS attacks.", + fr_packet_codes[reply->data[0]]); + } + + /* + * Set up for Proxy-State checks. + * + * If we see a Proxy-State in the reply which we didn't send, then it's a Blast RADIUS attack. + */ + vp = fr_pair_find_by_num(request->packet->vps, PW_PROXY_STATE, 0, TAG_ANY); + + while (attr < end) { + /* + * Blast RADIUS work-arounds require that + * Message-Authenticator is the first attribute in the + * reply. Note that we don't check for it being the + * first attribute, but simply that it exists. + * + * That check is a balance between securing the reply + * packet from attacks, and not violating the RFCs which + * say that there is no order to attributes in the + * packet. + * + * However, no matter the status of the '-b' flag we + * still can check for the signature of the attack, and + * discard packets which are suspicious. This behavior + * protects radclient from the attack, without mandating + * new behavior on the server side. + * + * Note that we don't set the '-b' flag by default. + * radclient is intended for testing / debugging, and is + * not intended to be used as part of a secure login / + * user checking system. + */ + if (attr[0] == PW_MESSAGE_AUTHENTICATOR) { + have_message_authenticator = true; + goto next; + } + + /* + * If there are Proxy-State attributes in the reply, they must + * match EXACTLY the Proxy-State attributes in the request. + * + * Note that we don't care if there are more Proxy-States + * in the request than in the reply. The Blast RADIUS + * issue requires _adding_ Proxy-State attributes, and + * cannot work when the server _deletes_ Proxy-State + * attributes. + */ + if (attr[0] == PW_PROXY_STATE) { + if (!vp || (vp->length != (size_t) (attr[1] - 2)) || (memcmp(vp->vp_octets, attr + 2, vp->length) != 0)) { + ERROR("Invalid reply to Access-Request ID %d - Discarding packet due to Blast RADIUS attack being detected.", request->packet->id); + ERROR("We received a Proxy-State in the reply which we did not send, or which is different from what we sent."); + return -1; + } + + vp = fr_pair_find_by_num(vp->next, PW_PROXY_STATE, 0, TAG_ANY); + } + + next: + attr += attr[1]; + } + + /* + * If "-b" is set, then we require Message-Authenticator in the reply. + */ + if (blast_radius && !have_message_authenticator) { + ERROR("The %s reply packet does not contain Message-Authenticator - discarding packet due to Blast RADIUS checks.", + fr_packet_codes[reply->data[0]]); + return -1; + } + + /* + * The packet doesn't look like it's a Blast RADIUS attack. The + * caller will now verify the packet signature. + */ + return 0; +} + /* * Receive one packet, maybe. */ @@ -1051,6 +1233,20 @@ static int recv_one_packet(int wait_time) } request = fr_packet2myptr(rc_request_t, packet, packet_p); + /* + * We want radclient to be able to send any packet, including + * imperfect ones. However, we do NOT want to be vulnerable to + * the "Blast RADIUS" issue. Instead of adding command-line + * flags to enable/disable similar flags to what the server + * sends, we just do a few more smart checks to double-check + * things. + */ + if ((request->packet->code == PW_CODE_ACCESS_REQUEST) && + blast_radius_check(request, reply) < 0) { + rad_free(&reply); + return -1; + } + /* * Fails the signature validation: not a real reply. * FIXME: Silently drop it and listen for another packet. @@ -1146,6 +1342,7 @@ packet_done: return 0; } +DIAG_OFF(deprecated-declarations) int main(int argc, char **argv) { int c; @@ -1183,7 +1380,7 @@ int main(int argc, char **argv) exit(1); } - while ((c = getopt(argc, argv, "46c:d:D:f:Fhn:p:qr:sS:t:vx" + while ((c = getopt(argc, argv, "46bc:d:D:f:Fhn:p:qr:sS:t:vx" #ifdef WITH_TCP "P:" #endif @@ -1196,10 +1393,16 @@ int main(int argc, char **argv) force_af = AF_INET6; break; + case 'b': + blast_radius = true; + break; + case 'c': - if (!isdigit((int) *optarg)) - usage(); + if (!isdigit((uint8_t) *optarg)) usage(); + resend_count = atoi(optarg); + + if (resend_count < 1) usage(); break; case 'D': @@ -1274,7 +1477,7 @@ int main(int argc, char **argv) break; case 'r': - if (!isdigit((int) *optarg)) usage(); + if (!isdigit((uint8_t) *optarg)) usage(); retries = atoi(optarg); if ((retries == 0) || (retries > 1000)) usage(); break; @@ -1314,7 +1517,7 @@ int main(int argc, char **argv) break; case 't': - if (!isdigit((int) *optarg)) + if (!isdigit((uint8_t) *optarg)) usage(); timeout = atof(optarg); break; @@ -1361,7 +1564,7 @@ int main(int argc, char **argv) /* * Get the request type */ - if (!isdigit((int) argv[2][0])) { + if (!isdigit((uint8_t) argv[2][0])) { packet_code = fr_str2int(request_types, argv[2], -2); if (packet_code == -2) { ERROR("Unrecognised request type \"%s\"", argv[2]); @@ -1421,6 +1624,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 +1842,9 @@ int main(int argc, char **argv) if ((stats.lost > 0) || (stats.failed > 0)) { exit(1); } + + openssl3_free(); + exit(0); } +DIAG_ON(deprecated-declarations) diff --git a/src/main/radiusd.c b/src/main/radiusd.c index 9739514509..6cece08893 100644 --- a/src/main/radiusd.c +++ b/src/main/radiusd.c @@ -710,6 +710,7 @@ cleanup: if (main_config.memory_report) { INFO("Allocated memory at time of report:"); fr_log_talloc_report(NULL); + talloc_disable_null_tracking(); } return rcode; diff --git a/src/main/radtest.in b/src/main/radtest.in index 38b1ba9a0f..af9df80eec 100644 --- a/src/main/radtest.in +++ b/src/main/radtest.in @@ -19,6 +19,7 @@ usage() { echo " -x Enable debug output" >&2 echo " -4 Use IPv4 for the NAS address (default)" >&2 echo " -6 Use IPv6 for the NAS address" >&2 + echo " -6 Mandate checks for Blast RADIUS (this is not set by default)." >&2 exit 1 } @@ -55,6 +56,10 @@ do NAS_ADDR_ATTR="NAS-IPv6-Address" shift ;; + -b) + OPTIONS="$OPTIONS -b" + shift + ;; -d) OPTIONS="$OPTIONS -d $2" shift;shift @@ -112,7 +117,7 @@ if [ "$7" ] then nas=$7 else - nas=`hostname` + nas=`(hostname || uname -n) 2>/dev/null | sed 1q` fi ( @@ -120,7 +125,6 @@ fi echo "$PASSWORD = \"$2\"" echo "$NAS_ADDR_ATTR = $nas" echo "NAS-Port = $4" - echo "Message-Authenticator = 0x00" if [ "$radclient" = "$radeapclient" ] then echo "EAP-Code = Response" diff --git a/src/main/realms.c b/src/main/realms.c index eb42598116..3f2c37270c 100644 --- a/src/main/realms.c +++ b/src/main/realms.c @@ -37,6 +37,10 @@ static rbtree_t *realms_byname = NULL; bool home_servers_udp = false; #endif +#ifdef HAVE_DIRENT_H +#include +#endif + #ifdef HAVE_REGEX typedef struct realm_regex realm_regex_t; @@ -53,6 +57,9 @@ static realm_regex_t *realms_regex = NULL; struct realm_config { CONF_SECTION *cs; +#ifdef HAVE_DIRENT_H + char const *directory; +#endif uint32_t dead_time; uint32_t retry_count; uint32_t retry_delay; @@ -89,7 +96,7 @@ static realm_config_t *realm_config = NULL; static rbtree_t *home_servers_byaddr = NULL; static rbtree_t *home_servers_byname = NULL; #ifdef WITH_STATS -static int home_server_max_number = 0; +int home_server_max_number = 0; static rbtree_t *home_servers_bynumber = NULL; #endif @@ -107,6 +114,10 @@ static const CONF_PARSER proxy_config[] = { { "dynamic", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, realm_config_t, dynamic), NULL }, +#ifdef HAVE_DIRENT_H + { "directory", FR_CONF_OFFSET(PW_TYPE_STRING, realm_config_t, directory), NULL }, +#endif + { "dead_time", FR_CONF_OFFSET(PW_TYPE_INTEGER, realm_config_t, dead_time), STRINGIFY(DEAD_TIME) }, { "wake_all_if_all_dead", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, realm_config_t, wake_all_if_all_dead), "no" }, @@ -148,12 +159,12 @@ static int home_server_addr_cmp(void const *one, void const *two) home_server_t const *a = one; home_server_t const *b = two; - if (a->server && !b->server) return -1; - if (!a->server && b->server) return +1; - if (a->server && b->server) { + if (a->virtual_server && !b->virtual_server) return -1; + if (!a->virtual_server && b->virtual_server) return +1; + if (a->virtual_server && b->virtual_server) { rcode = a->type - b->type; if (rcode != 0) return rcode; - return strcmp(a->server, b->server); + return strcmp(a->virtual_server, b->virtual_server); } if (a->port < b->port) return -1; @@ -268,6 +279,10 @@ static ssize_t xlat_home_server(UNUSED void *instance, REQUEST *request, state = "fail"; break; + case HOME_STATE_ADMIN_DOWN: + state = "down"; + break; + default: state = "unknown"; break; @@ -317,6 +332,60 @@ static ssize_t xlat_server_pool(UNUSED void *instance, REQUEST *request, return xlat_cs(request->home_pool->cs, fmt, out, outlen); } + + +/* + * Xlat for %{home_server_dynamic:foo} + */ +static ssize_t xlat_home_server_dynamic(UNUSED void *instance, REQUEST *request, + char const *fmt, char *out, size_t outlen) +{ + int type; + char const *p; + home_server_t *home; + + if (outlen < 2) return 0; + + switch (request->packet->code) { + case PW_CODE_ACCESS_REQUEST: + type = HOME_TYPE_AUTH; + break; + +#ifdef WITH_ACCOUNTING + case PW_CODE_ACCOUNTING_REQUEST: + type = HOME_TYPE_ACCT; + break; +#endif + +#ifdef WITH_COA + case PW_CODE_COA_REQUEST: + case PW_CODE_DISCONNECT_REQUEST: + type = HOME_TYPE_COA; + break; +#endif + + default: + *out = '\0'; + return 0; + } + + p = fmt; + while (isspace((uint8_t) *p)) p++; + + home = home_server_byname(p, type); + if (!home) { + *out = '\0'; + return 0; + } + + /* + * 1 for dynamic, 0 for static + */ + out[0] = '0' + home->dynamic; + out[1] = '\0'; + + return 1; +} #endif void realms_free(void) @@ -366,11 +435,15 @@ static CONF_PARSER home_server_coa[] = { }; #endif +static const char *require_message_authenticator = NULL; + static CONF_PARSER home_server_config[] = { + { "nonblock", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, home_server_t, nonblock), "no" }, + { "require_message_authenticator", FR_CONF_POINTER(PW_TYPE_STRING| PW_TYPE_IGNORE_DEFAULT, &require_message_authenticator), NULL }, { "ipaddr", FR_CONF_OFFSET(PW_TYPE_COMBO_IP_ADDR, home_server_t, ipaddr), NULL }, { "ipv4addr", FR_CONF_OFFSET(PW_TYPE_IPV4_ADDR, home_server_t, ipaddr), NULL }, { "ipv6addr", FR_CONF_OFFSET(PW_TYPE_IPV6_ADDR, home_server_t, ipaddr), NULL }, - { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_NOT_EMPTY, home_server_t, server), NULL }, + { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_NOT_EMPTY, home_server_t, virtual_server), NULL }, { "port", FR_CONF_OFFSET(PW_TYPE_SHORT, home_server_t, port), "0" }, @@ -519,7 +592,7 @@ static bool home_server_insert(home_server_t *home, CONF_SECTION *cs) return false; } - if (!home->server && !rbtree_insert(home_servers_byaddr, home)) { + if (!home->virtual_server && !rbtree_insert(home_servers_byaddr, home)) { rbtree_deletebydata(home_servers_byname, home); cf_log_err_cs(cs, "Internal error %d adding home server %s", __LINE__, home->log_name); return false; @@ -561,7 +634,7 @@ bool realm_home_server_add(home_server_t *home) return false; } - if (!home->server && (rbtree_finddata(home_servers_byaddr, home) != NULL)) { + if (!home->virtual_server && (rbtree_finddata(home_servers_byaddr, home) != NULL)) { char buffer[INET6_ADDRSTRLEN + 3]; inet_ntop(home->ipaddr.af, &home->ipaddr.ipaddr, buffer, sizeof(buffer)); @@ -620,6 +693,19 @@ bool realm_home_server_add(home_server_t *home) return true; } +#ifdef WITH_TLS +/* + * The listeners are always different. And we always look them up by *known* listener. And not "find me some random thing". + */ +static int listener_cmp(void const *one, void const *two) +{ + if (one < two) return -1; + if (one > two) return +1; + + return 0; +} +#endif + /** Alloc a new home server defined by a CONF_SECTION * * @param ctx to allocate home_server_t in. @@ -640,6 +726,9 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE home->cs = cs; home->state = HOME_STATE_UNKNOWN; home->proto = IPPROTO_UDP; + home->require_ma = main_config.require_ma; + + require_message_authenticator = false; /* * Parse the configuration into the home server @@ -647,6 +736,10 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE */ if (cf_section_parse(cs, home, home_server_config) < 0) goto error; + if (fr_bool_auto_parse(cf_pair_find(cs, "require_message_authenticator"), &home->require_ma, require_message_authenticator) < 0) { + goto error; + } + /* * It has an IP address, it must be a remote server. */ @@ -670,23 +763,25 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE } else if (cf_pair_find(cs, "virtual_server") != NULL) { home->ipaddr.af = AF_UNSPEC; /* mark ipaddr as unused */ - if (!home->server) { + if (!home->virtual_server) { cf_log_err_cs(cs, "Invalid value for virtual_server"); goto error; } /* - * Try and find a 'server' section off the root of + * Try and find a "server" section off the root of * the config with a name that matches the * virtual_server. */ - if (!cf_section_sub_find_name2(rc->cs, "server", home->server)) { - cf_log_err_cs(cs, "No such server %s", home->server); + if (!rc) goto error; + + if (!cf_section_sub_find_name2(rc->cs, "server", home->virtual_server)) { + cf_log_err_cs(cs, "No such server %s", home->virtual_server); goto error; } home->secret = ""; - home->log_name = talloc_typed_strdup(home, home->server); + home->log_name = talloc_typed_strdup(home, home->virtual_server); /* * Otherwise it's an invalid config section and we * raise an error. @@ -717,7 +812,7 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE #ifdef WITH_COA case HOME_TYPE_COA: - if (home->server != NULL) { + if (home->virtual_server != NULL) { cf_log_err_cs(cs, "Home servers of type \"coa\" cannot point to a virtual server"); goto error; } @@ -781,8 +876,9 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE cf_log_err_cs(cs, "Server not built with support for RADIUS over TCP"); goto error; #endif - if (home->ping_check != HOME_PING_CHECK_NONE) { - cf_log_err_cs(cs, "Only 'status_check = none' is allowed for home " + if ((home->ping_check != HOME_PING_CHECK_NONE) && + (home->ping_check != HOME_PING_CHECK_STATUS_SERVER)) { + cf_log_err_cs(cs, "Only 'status_check = status-server' is allowed for home " "servers with 'proto = tcp'"); goto error; } @@ -796,7 +892,7 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE home->proto = proto; } - if (!home->server && rbtree_finddata(home_servers_byaddr, home)) { + if (!home->virtual_server && rbtree_finddata(home_servers_byaddr, home)) { cf_log_err_cs(cs, "Duplicate home server"); goto error; } @@ -831,7 +927,7 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE /* * Virtual servers have some TLS restrictions. */ - if (home->server) { + if (home->virtual_server) { if (tls) { cf_log_err_cs(cs, "Virtual home_servers cannot have a \"tls\" subsection"); goto error; @@ -924,10 +1020,29 @@ home_server_t *home_server_afrom_cs(TALLOC_CTX *ctx, realm_config_t *rc, CONF_SE * Parse the SSL client configuration. */ if (tls) { + int rcode; + + /* + * We don't require this for TLS connections. + */ + home->require_ma = false; + home->tls = tls_client_conf_parse(tls); if (!home->tls) { goto error; } + + /* + * Connection timeouts for outgoing TLS connections. + */ + + rcode = cf_item_parse(tls, "connect_timeout", FR_ITEM_POINTER(PW_TYPE_INTEGER, &home->connect_timeout), NULL); + if (rcode < 0) goto error; + + if (!home->connect_timeout || (home->connect_timeout > 30)) home->connect_timeout = 30; + + home->listeners = rbtree_create(home, listener_cmp, NULL, RBTREE_FLAG_LOCK); + if (!home->listeners) goto error; } #endif } /* end of parse home server */ @@ -1173,8 +1288,17 @@ void realm_pool_free(home_pool_t *pool) } #endif /* HAVE_PTHREAD_H */ -int realm_pool_add(home_pool_t *pool, UNUSED CONF_SECTION *cs) +int realm_pool_add(home_pool_t *pool, CONF_SECTION *cs) { + home_pool_t *old; + + old = rbtree_finddata(home_pools_byname, pool); + if (old) { + cf_log_err_cs(cs, "Cannot add duplicate home server %s, original is at %s[%d]", pool->name, + cf_section_filename(old->cs), cf_section_lineno(old->cs)); + return 0; + } + /* * The structs aren't mutex protected. Refuse to destroy * the server. @@ -1265,7 +1389,7 @@ static int server_pool_add(realm_config_t *rc, goto error; } - if (!pool->fallback->server) { + if (!pool->fallback->virtual_server) { cf_log_err_cs(cs, "Fallback home_server %s does NOT contain a virtual_server directive", pool->fallback->log_name); goto error; @@ -1541,7 +1665,7 @@ static int old_server_add(realm_config_t *rc, CONF_SECTION *cs, home->src_ipaddr.af = home->ipaddr.af; } else { home->ipaddr.af = AF_UNSPEC; - home->server = server; + home->virtual_server = server; } talloc_free(q); @@ -2255,9 +2379,68 @@ int realms_init(CONF_SECTION *config) #ifdef WITH_PROXY xlat_register("home_server", xlat_home_server, NULL, NULL); xlat_register("home_server_pool", xlat_server_pool, NULL, NULL); + xlat_register("home_server_dynamic", xlat_home_server_dynamic, NULL, NULL); #endif realm_config = rc; + +#ifdef HAVE_DIRENT_H + if (!rc->dynamic) { + if (rc->directory) { + WARN("Ignoring 'directory' as dynamic home servers were not configured."); + } + } else { + DIR *dir; + struct dirent *dp; + + if (!rc->directory) { + WARN("Ignoring \"dynamic = true\" due to not set \"directory\" in proxy.conf"); + return 1; + } + + DEBUG2("including files in directory %s", rc->directory); + + dir = opendir(rc->directory); + if (!dir) { + cf_log_err_cs(config, "Error reading directory %s: %s", + rc->directory, fr_syserror(errno)); + goto error; + } + + /* + * Read the directory, ignoring "." files. + */ + while ((dp = readdir(dir)) != NULL) { + char const *p; + char conf_file[PATH_MAX]; + + if (dp->d_name[0] == '.') continue; + + /* + * Check for valid characters + */ + for (p = dp->d_name; *p != '\0'; p++) { + if (isalpha((uint8_t)*p) || + isdigit((uint8_t)*p) || + (*p == '-') || + (*p == '_') || + (*p == '.')) continue; + break; + } + if (*p != '\0') continue; + + snprintf(conf_file, sizeof(conf_file), "%s/%s", rc->directory, dp->d_name); + if (home_server_afrom_file(conf_file) < 0) { + ERROR("Failed reading home_server from %s - %s", + conf_file, fr_strerror()); + closedir(dir); + goto error; + } + } + closedir(dir); + } +#endif + return 1; } @@ -2388,7 +2571,7 @@ void home_server_update_request(home_server_t *home, REQUEST *request) * the 'hints' file. */ request->proxy->vps = fr_pair_list_copy(request->proxy, - request->packet->vps); + request->packet->vps); } /* @@ -2516,7 +2699,7 @@ home_server_t *home_server_ldb(char const *realmname, * Home servers that are unknown, alive, or zombie * are used for proxying. */ - if (home->state >= HOME_STATE_IS_DEAD) { + if (HOME_SERVER_IS_DEAD(home)) { continue; } @@ -2533,7 +2716,8 @@ home_server_t *home_server_ldb(char const *realmname, * came from this server. Don't re-proxy it * there. */ - if ((request->listener->type == RAD_LISTEN_DETAIL) && + if (request->listener && + (request->listener->type == RAD_LISTEN_DETAIL) && (request->packet->code == PW_CODE_ACCOUNTING_REQUEST) && (fr_ipaddr_cmp(&home->ipaddr, &request->packet->src_ipaddr) == 0)) { continue; @@ -2641,7 +2825,7 @@ home_server_t *home_server_ldb(char const *realmname, found = pool->fallback; WARN("Home server pool %s failing over to fallback %s", - pool->name, found->server); + pool->name, found->virtual_server); if (pool->in_fallback) goto update_and_return; pool->in_fallback = true; @@ -2680,7 +2864,7 @@ home_server_t *home_server_ldb(char const *realmname, if (!home) continue; - if ((home->state >= HOME_STATE_IS_DEAD) && + if (HOME_SERVER_IS_DEAD(home) && (home->ping_check == HOME_PING_CHECK_NONE)) { home->state = HOME_STATE_ALIVE; home->response_timeouts = 0; @@ -2740,7 +2924,7 @@ home_server_t *home_server_find(fr_ipaddr_t *ipaddr, uint16_t port, #else myhome.proto = IPPROTO_UDP; #endif - myhome.server = NULL; /* we're not called for internal proxying */ + myhome.virtual_server = NULL; /* we're not called for internal proxying */ return rbtree_finddata(home_servers_byaddr, &myhome); } @@ -2764,7 +2948,7 @@ home_server_t *home_server_find_bysrc(fr_ipaddr_t *ipaddr, uint16_t port, #else myhome.proto = IPPROTO_UDP; #endif - myhome.server = NULL; /* we're not called for internal proxying */ + myhome.virtual_server = NULL; /* we're not called for internal proxying */ return rbtree_finddata(home_servers_byaddr, &myhome); } @@ -2789,7 +2973,7 @@ home_server_t *home_server_bynumber(int number) memset(&myhome, 0, sizeof(myhome)); myhome.number = number; - myhome.server = NULL; /* we're not called for internal proxying */ + myhome.virtual_server = NULL; /* we're not called for internal proxying */ return rbtree_finddata(home_servers_bynumber, &myhome); } @@ -2805,4 +2989,116 @@ home_pool_t *home_pool_byname(char const *name, int type) return rbtree_finddata(home_pools_byname, &mypool); } +int home_server_afrom_file(char const *filename) +{ + CONF_SECTION *cs, *subcs; + char const *p; + home_server_t *home; + + if (!realm_config->dynamic) { + fr_strerror_printf("Must set \"dynamic = true\" in proxy.conf for dynamic home servers to work"); + return -1; + } + + cs = cf_section_alloc(NULL, "home", filename); + if (!cs) { + fr_strerror_printf("Failed allocating memory"); + return -1; + } + + if (cf_file_read(cs, filename) < 0) { + fr_strerror_printf("Failed reading file %s", filename); + error: + talloc_free(cs); + return -1; + } + + p = strrchr(filename, '/'); + if (p) { + p++; + } else { + p = filename; + } + + subcs = cf_section_sub_find_name2(cs, "home_server", p); + if (!subcs) { + fr_strerror_printf("No 'home_server %s' definition in the file.", p); + goto error; + } + + home = home_server_afrom_cs(realm_config, realm_config, subcs); + if (!home) { + fr_strerror_printf("Failed parsing configuration to a home_server structure"); + goto error; + } + + home->dynamic = true; + + if (home->virtual_server) { + fr_strerror_printf("Dynamic home_server '%s' cannot have 'server = %s' configuration item", p, home->virtual_server); + talloc_free(home); + goto error; + } + + if (home->dual) { + fr_strerror_printf("Dynamic home_server '%s' is missing 'type', or it is set to 'auth+acct'. Please specify 'type = auth' or 'type = acct', etc.", p); + talloc_free(home); + goto error; + } + + if (!realm_home_server_add(home)) { + fr_strerror_printf("Failed adding home_server to the internal data structures"); + talloc_free(home); + goto error; + } + + return 0; +} + +int home_server_delete(char const *name, char const *type_name) +{ + home_server_t *home; + int type; + char const *p; + + if (!realm_config->dynamic) { + fr_strerror_printf("Must set 'dynamic' in proxy.conf for dynamic home servers to work"); + return -1; + } + + type = fr_str2int(home_server_types, type_name, HOME_TYPE_INVALID); + if (type == HOME_TYPE_INVALID) { + fr_strerror_printf("Unknown home_server type '%s'", type_name); + return -1; + } + + p = strrchr(name, '/'); + if (p) { + p++; + } else { + p = name; + } + + home = home_server_byname(p, type); + if (!home) { + fr_strerror_printf("Failed to find home_server %s", p); + return -1; + } + + if (!home->dynamic) { + fr_strerror_printf("Cannot delete static home_server %s", p); + return -1; + } + + (void) rbtree_deletebydata(home_servers_byname, home); + (void) rbtree_deletebydata(home_servers_byaddr, home); +#ifdef WITH_STATS + (void) rbtree_deletebydata(home_servers_bynumber, home); +#endif + + /* + * Leak home, and home->cs. Oh well. + */ + return 0; +} #endif diff --git a/src/main/session.c b/src/main/session.c index e359010a1b..8dbf5a6f14 100644 --- a/src/main/session.c +++ b/src/main/session.c @@ -34,7 +34,7 @@ RCSID("$Id$") /* * End a session by faking a Stop packet to all accounting modules. */ -int session_zap(REQUEST *request, uint32_t nasaddr, uint32_t nas_port, +int session_zap(REQUEST *request, fr_ipaddr_t const *nasaddr, uint32_t nas_port, char const *user, char const *sessionid, uint32_t cliaddr, char proto, int session_time) @@ -64,6 +64,8 @@ int session_zap(REQUEST *request, uint32_t nasaddr, uint32_t nas_port, #define IPPAIR(n,v) PAIR(n,v,vp_ipaddr) +#define IPV6PAIR(n,v) PAIR(n,v,vp_ipv6addr) + #define STRINGPAIR(n,v) do { \ if(!(vp = fr_pair_afrom_num(stopreq->packet,n, 0))) { \ talloc_free(stopreq); \ @@ -75,7 +77,12 @@ int session_zap(REQUEST *request, uint32_t nasaddr, uint32_t nas_port, } while(0) INTPAIR(PW_ACCT_STATUS_TYPE, PW_STATUS_STOP); - IPPAIR(PW_NAS_IP_ADDRESS, nasaddr); + + if (nasaddr->af == AF_INET) { + IPPAIR(PW_NAS_IP_ADDRESS, nasaddr->ipaddr.ip4addr.s_addr); + } else { + IPV6PAIR(PW_NAS_IPV6_ADDRESS, nasaddr->ipaddr.ip6addr); + } INTPAIR(PW_EVENT_TIMESTAMP, 0); vp->vp_date = time(NULL); @@ -127,29 +134,25 @@ int session_zap(REQUEST *request, uint32_t nasaddr, uint32_t nas_port, * 1 The user is logged in. * 2 Some error occured. */ -int rad_check_ts(uint32_t nasaddr, uint32_t nas_port, char const *user, +int rad_check_ts(fr_ipaddr_t const *nasaddr, uint32_t nas_port, char const *user, char const *session_id) { pid_t pid, child_pid; int status; - char address[16]; + char address[64]; char port[11]; RADCLIENT *cl; - fr_ipaddr_t ipaddr; - - ipaddr.af = AF_INET; - ipaddr.ipaddr.ip4addr.s_addr = nasaddr; /* * Find NAS type. */ - cl = client_find_old(&ipaddr); + cl = client_find_old(nasaddr); if (!cl) { /* * Unknown NAS, so trusting radutmp. */ DEBUG2("checkrad: Unknown NAS %s, not checking", - ip_ntoa(address, nasaddr)); + inet_ntop(nasaddr->af, &(nasaddr->ipaddr), address, sizeof(address))); return 1; } @@ -202,8 +205,8 @@ int rad_check_ts(uint32_t nasaddr, uint32_t nas_port, char const *user, */ closefrom(3); - ip_ntoa(address, nasaddr); - snprintf(port, 11, "%u", nas_port); + inet_ntop(nasaddr->af, &(nasaddr->ipaddr), address, sizeof(address)); + snprintf(port, sizeof(port), "%u", nas_port); #ifdef __EMX__ /* OS/2 can't directly execute scripts then we call the command @@ -223,7 +226,7 @@ int rad_check_ts(uint32_t nasaddr, uint32_t nas_port, char const *user, exit(2); } #else -int rad_check_ts(UNUSED uint32_t nasaddr, UNUSED unsigned int nas_port, +int rad_check_ts(fr_ipaddr_t const *nasaddr, UNUSED unsigned int nas_port, UNUSED char const *user, UNUSED char const *session_id) { ERROR("Simultaneous-Use is not supported"); @@ -234,7 +237,7 @@ int rad_check_ts(UNUSED uint32_t nasaddr, UNUSED unsigned int nas_port, #else /* WITH_SESSION_MGMT */ -int session_zap(UNUSED REQUEST *request, UNUSED uint32_t nasaddr, UNUSED uint32_t nas_port, +int session_zap(UNUSED REQUEST *request, fr_ipaddr_t const *nasaddr, UNUSED uint32_t nas_port, UNUSED char const *user, UNUSED char const *sessionid, UNUSED uint32_t cliaddr, UNUSED char proto, UNUSED int session_time) @@ -242,7 +245,7 @@ int session_zap(UNUSED REQUEST *request, UNUSED uint32_t nasaddr, UNUSED uint32_ return RLM_MODULE_FAIL; } -int rad_check_ts(UNUSED uint32_t nasaddr, UNUSED unsigned int nas_port, +int rad_check_ts(fr_ipaddr_t const *nasaddr, UNUSED unsigned int nas_port, UNUSED char const *user, UNUSED char const *session_id) { ERROR("Simultaneous-Use is not supported"); diff --git a/src/main/stats.c b/src/main/stats.c index 33b5fd238a..6aa908bfea 100644 --- a/src/main/stats.c +++ b/src/main/stats.c @@ -90,44 +90,58 @@ static void stats_time(fr_stats_t *stats, struct timeval *start, void request_stats_final(REQUEST *request) { - if (request->master_state == REQUEST_COUNTED) return; + rad_listen_t *listener; + RADCLIENT *client; - if (!request->listener) return; - if (!request->client) return; + if ((request->options & RAD_REQUEST_OPTION_STATS) != 0) return; - if ((request->listener->type != RAD_LISTEN_NONE) && + /* don't count statistic requests */ + if (request->packet->code == PW_CODE_STATUS_SERVER) { + return; + } + + listener = request->listener; + if (listener) switch (listener->type) { + case RAD_LISTEN_NONE: #ifdef WITH_ACCOUNTING - (request->listener->type != RAD_LISTEN_ACCT) && + case RAD_LISTEN_ACCT: #endif #ifdef WITH_COA - (request->listener->type != RAD_LISTEN_COA) && + case RAD_LISTEN_COA: #endif - (request->listener->type != RAD_LISTEN_AUTH)) return; + case RAD_LISTEN_AUTH: + break; - /* don't count statistic requests */ - if (request->packet->code == PW_CODE_STATUS_SERVER) - return; + default: + return; + } + + /* + * Deal with TCP / TLS issues. The statistics are kept in the parent socket. + */ + if (listener && listener->parent) listener = listener->parent; + client = request->client; #undef INC_AUTH -#define INC_AUTH(_x) radius_auth_stats._x++;request->listener->stats._x++;request->client->auth._x++; +#define INC_AUTH(_x) radius_auth_stats._x++;if (listener) listener->stats._x++;if (client) client->auth._x++; #undef INC_ACCT #ifdef WITH_ACCOUNTING -#define INC_ACCT(_x) radius_acct_stats._x++;request->listener->stats._x++;request->client->acct._x++ +#define INC_ACCT(_x) radius_acct_stats._x++;if (listener) listener->stats._x++;if (client) client->acct._x++ #else #define INC_ACCT(_x) #endif #undef INC_COA #ifdef WITH_COA -#define INC_COA(_x) radius_coa_stats._x++;request->listener->stats._x++;request->client->coa._x++ +#define INC_COA(_x) radius_coa_stats._x++;if (listener) listener->stats._x++;if (client) client->coa._x++ #else #define INC_COA(_x) #endif #undef INC_DSC #ifdef WITH_DSC -#define INC_DSC(_x) radius_dsc_stats._x++;request->listener->stats._x++;request->client->dsc._x++ +#define INC_DSC(_x) radius_dsc_stats._x++;if (listener) listener->stats._x++;if (client) client->dsc._x++ #else #define INC_DSC(_x) #endif @@ -140,7 +154,7 @@ void request_stats_final(REQUEST *request) * deleted, because only the main server thread calls * this function, which makes it thread-safe. */ - if (request->reply && (request->packet->code != PW_CODE_STATUS_SERVER)) switch (request->reply->code) { + if (request->reply) switch (request->reply->code) { case PW_CODE_ACCESS_ACCEPT: INC_AUTH(total_access_accepts); @@ -268,7 +282,7 @@ void request_stats_final(REQUEST *request) if (!request->proxy_reply) goto done; /* simplifies formatting */ #undef INC -#define INC(_x) proxy_auth_stats._x += request->num_proxied_responses; request->home_server->stats._x += request->num_proxied_responses; +#define INC(_x) proxy_auth_stats._x += request->num_proxied_responses;request->home_server->stats._x += request->num_proxied_responses; switch (request->proxy_reply->code) { case PW_CODE_ACCESS_ACCEPT: @@ -339,7 +353,7 @@ void request_stats_final(REQUEST *request) done: #endif /* WITH_PROXY */ - request->master_state = REQUEST_COUNTED; + request->options |= RAD_REQUEST_OPTION_STATS; } typedef struct fr_stats2vp { @@ -582,6 +596,23 @@ void request_stats_reply(REQUEST *request) */ if (!cl) return; } +#ifdef AF_INET6 + } else { + server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IPV6_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY); + if (server_ip) { + server_port = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_PORT, VENDORPEC_FREERADIUS, TAG_ANY); + if (server_port) { + ipaddr.af = AF_INET6; + ipaddr.ipaddr.ip6addr = server_ip->vp_ipv6addr; + cl = listener_find_client_list(&ipaddr, server_port->vp_integer, IPPROTO_UDP); + + /* + * Not found: don't do anything + */ + if (!cl) return; + } + } +#endif /* AF_INET6 */ } @@ -597,6 +628,19 @@ void request_stats_reply(REQUEST *request) } #endif +#ifdef AF_INET6 + } else if ((vp = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_CLIENT_IPV6_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY)) != NULL) { + memset(&ipaddr, 0, sizeof(ipaddr)); + ipaddr.af = AF_INET6; + ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr; + client = client_find(cl, &ipaddr, IPPROTO_UDP); +#ifdef WITH_TCP + if (!client) { + client = client_find(cl, &ipaddr, IPPROTO_TCP); + } +#endif +#endif /* AF_INET6 */ + /* * Else look it up by number. */ @@ -615,23 +659,44 @@ void request_stats_reply(REQUEST *request) * When retrieving client by number, also * echo back it's IP address. */ - if ((vp->da->type == PW_TYPE_INTEGER) && - (client->ipaddr.af == AF_INET)) { - vp = radius_pair_create(request->reply, - &request->reply->vps, - PW_FREERADIUS_STATS_CLIENT_IP_ADDRESS, VENDORPEC_FREERADIUS); - if (vp) { - vp->vp_ipaddr = client->ipaddr.ipaddr.ip4addr.s_addr; + if (vp->da->type == PW_TYPE_INTEGER) { + if (client->ipaddr.af == AF_INET) { + vp = radius_pair_create(request->reply, + &request->reply->vps, + PW_FREERADIUS_STATS_CLIENT_IP_ADDRESS, VENDORPEC_FREERADIUS); + if (vp) { + vp->vp_ipaddr = client->ipaddr.ipaddr.ip4addr.s_addr; + } + + if (client->ipaddr.prefix != 32) { + vp = radius_pair_create(request->reply, + &request->reply->vps, + PW_FREERADIUS_STATS_CLIENT_NETMASK, VENDORPEC_FREERADIUS); + if (vp) { + vp->vp_integer = client->ipaddr.prefix; + } + } } - if (client->ipaddr.prefix != 32) { +#ifdef AF_INET6 + if (client->ipaddr.af == AF_INET6) { vp = radius_pair_create(request->reply, - &request->reply->vps, - PW_FREERADIUS_STATS_CLIENT_NETMASK, VENDORPEC_FREERADIUS); + &request->reply->vps, + PW_FREERADIUS_STATS_CLIENT_IPV6_ADDRESS, VENDORPEC_FREERADIUS); if (vp) { - vp->vp_integer = client->ipaddr.prefix; + vp->vp_ipv6addr = client->ipaddr.ipaddr.ip6addr; + } + + if (client->ipaddr.prefix != 128) { + vp = radius_pair_create(request->reply, + &request->reply->vps, + PW_FREERADIUS_STATS_CLIENT_NETMASK, VENDORPEC_FREERADIUS); + if (vp) { + vp->vp_integer = client->ipaddr.prefix; + } } } +#endif /* AF_INET6 */ } if (server_ip) { @@ -674,21 +739,26 @@ void request_stats_reply(REQUEST *request) * See if we need to look up the server by socket * socket. */ - server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IP_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY); - if (!server_ip) return; - server_port = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_PORT, VENDORPEC_FREERADIUS, TAG_ANY); if (!server_port) return; - ipaddr.af = AF_INET; - ipaddr.ipaddr.ip4addr.s_addr = server_ip->vp_ipaddr; - this = listener_find_byipaddr(&ipaddr, - server_port->vp_integer, - IPPROTO_UDP); + server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IP_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY); + if (server_ip) { + ipaddr.af = AF_INET; + ipaddr.ipaddr.ip4addr.s_addr = server_ip->vp_ipaddr; +#ifdef AF_INET6 + } else if ((server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IPV6_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY)) != NULL) { + ipaddr.af = AF_INET6; + ipaddr.ipaddr.ip6addr = server_ip->vp_ipv6addr; +#endif /* AF_INET6 */ + } else { + stats_error(request, "No listener IP address supplied"); + } /* * Not found: don't do anything */ + this = listener_find_byipaddr(&ipaddr, server_port->vp_integer, IPPROTO_UDP); if (!this) { stats_error(request, "No such listener"); return; @@ -730,16 +800,6 @@ void request_stats_reply(REQUEST *request) VALUE_PAIR *server_ip, *server_port; fr_ipaddr_t ipaddr; - /* - * See if we need to look up the server by socket - * socket. - */ - server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IP_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY); - if (!server_ip) { - stats_error(request, "No home server IP supplied"); - return; - } - server_port = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_PORT, VENDORPEC_FREERADIUS, TAG_ANY); if (!server_port) { stats_error(request, "No home server port supplied"); @@ -749,15 +809,30 @@ void request_stats_reply(REQUEST *request) #ifndef NDEBUG memset(&ipaddr, 0, sizeof(ipaddr)); #endif - ipaddr.af = AF_INET; - ipaddr.prefix = 32; - ipaddr.ipaddr.ip4addr.s_addr = server_ip->vp_ipaddr; - home = home_server_find(&ipaddr, server_port->vp_integer, - IPPROTO_UDP); + + /* + * See if we need to look up the server by socket + * socket. + */ + server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IP_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY); + if (server_ip) { + ipaddr.af = AF_INET; + ipaddr.prefix = 32; + ipaddr.ipaddr.ip4addr.s_addr = server_ip->vp_ipaddr; +#ifdef AF_INET6 + } else if ((server_ip = fr_pair_find_by_num(request->packet->vps, PW_FREERADIUS_STATS_SERVER_IPV6_ADDRESS, VENDORPEC_FREERADIUS, TAG_ANY)) != NULL) { + ipaddr.af = AF_INET6; + ipaddr.ipaddr.ip6addr = server_ip->vp_ipv6addr; +#endif /* AF_INET6 */ + } else { + stats_error(request, "No home server IP supplied"); + return; + } /* * Not found: don't do anything */ + home = home_server_find(&ipaddr, server_port->vp_integer, IPPROTO_UDP); if (!home) { stats_error(request, "Failed to find home server IP"); return; 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 #include +#include #include #ifdef HAVE_SYS_STAT_H @@ -37,6 +38,10 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ #include #endif +#ifdef HAVE_DIRENT_H +#include +#endif + #ifdef HAVE_UTIME_H #include #endif @@ -56,8 +61,25 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ # endif # include +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +# include + +static OSSL_PROVIDER *openssl_default_provider = NULL; +static OSSL_PROVIDER *openssl_legacy_provider = NULL; +#endif + #define LOG_PREFIX "tls" +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#define ERR_get_error_line(_file, _line) ERR_get_error_all(_file, _line, NULL, NULL, NULL) + +#define FIPS_mode(_x) EVP_default_properties_is_fips_enabled(NULL) +#define PEM_read_bio_DHparams(_bio, _x, _y, _z) PEM_read_bio_Parameters(_bio, &dh) +#define SSL_CTX_set0_tmp_dh_pkey(_ctx, _dh) SSL_CTX_set_tmp_dh(_ctx, _dh) +#define DH EVP_PKEY +#define DH_free(_dh) +#endif + #ifdef ENABLE_OPENSSL_VERSION_CHECK typedef struct libssl_defect { uint64_t high; @@ -153,6 +175,11 @@ static unsigned int record_plus(record_t *buf, void const *ptr, static unsigned int record_minus(record_t *buf, void *ptr, unsigned int size); +typedef struct { + char const *name; + SSL_CTX *ctx; +} fr_realm_ctx_t; + DIAG_OFF(format-nonliteral) /** Print errors in the TLS thread local error stack * @@ -191,9 +218,9 @@ static int tls_verror_log(REQUEST *request, char const *msg, va_list ap) /* Extra verbose */ if ((request && RDEBUG_ENABLED3) || DEBUG_ENABLED3) { - ROPTIONAL(REDEBUG, ERROR, "%s: %s[%i]:%s", p, file, line, buffer); + ROPTIONAL(REDEBUG, ERROR, "(TLS) %s: %s[%i]:%s", p, file, line, buffer); } else { - ROPTIONAL(REDEBUG, ERROR, "%s: %s", p, buffer); + ROPTIONAL(REDEBUG, ERROR, "(TLS) %s: %s", p, buffer); } talloc_free(p); @@ -205,7 +232,7 @@ static int tls_verror_log(REQUEST *request, char const *msg, va_list ap) * Print the error we were given, irrespective * of whether there were any OpenSSL errors. */ - ROPTIONAL(RERROR, ERROR, "%s", p); + ROPTIONAL(RERROR, ERROR, "(TLS) %s", p); talloc_free(p); } @@ -217,9 +244,9 @@ static int tls_verror_log(REQUEST *request, char const *msg, va_list ap) ERR_error_string_n(error, buffer, sizeof(buffer)); /* Extra verbose */ if ((request && RDEBUG_ENABLED3) || DEBUG_ENABLED3) { - ROPTIONAL(REDEBUG, ERROR, "%s[%i]:%s", file, line, buffer); + ROPTIONAL(REDEBUG, ERROR, "(TLS) %s[%i]:%s", file, line, buffer); } else { - ROPTIONAL(REDEBUG, ERROR, "%s", buffer); + ROPTIONAL(REDEBUG, ERROR, "(TLS) %s", buffer); } in_stack++; } while ((error = ERR_get_error_line(&file, &line))); @@ -309,11 +336,11 @@ int tls_error_io_log(REQUEST *request, tls_session_t *session, int ret, char con * being regarded as "dead". */ case SSL_ERROR_SYSCALL: - ROPTIONAL(REDEBUG, ERROR, "System call (I/O) error (%i)", ret); + ROPTIONAL(REDEBUG, ERROR, "(TLS) System call (I/O) error (%i)", ret); return 0; case SSL_ERROR_SSL: - ROPTIONAL(REDEBUG, ERROR, "TLS protocol error (%i)", ret); + ROPTIONAL(REDEBUG, ERROR, "(TLS) Protocol error (%i)", ret); return 0; /* @@ -323,7 +350,7 @@ int tls_error_io_log(REQUEST *request, tls_session_t *session, int ret, char con * the code needs updating here. */ default: - ROPTIONAL(REDEBUG, ERROR, "TLS session error %i (%i)", error, ret); + ROPTIONAL(REDEBUG, ERROR, "(TLS) Session error %i (%i)", error, ret); return 0; } @@ -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..6d954d269f 100644 --- a/src/main/tls_listen.c +++ b/src/main/tls_listen.c @@ -81,54 +81,96 @@ 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) Closing connection"); radius_update_listener(listener); /* - * Do NOT free the listener here. It's in use by + * Do NOT free the listener here. It may be in use by * a request, and will need to hang around until * all of the requests are done. * - * It is instead free'd in remove_from_request_hash() + * It is instead free'd when all of the requests using it + * are done. */ } -static int CC_HINT(nonnull) tls_socket_write(rad_listen_t *listener, REQUEST *request) +static void tls_write_available(fr_event_list_t *el, int sock, void *ctx); + +static int CC_HINT(nonnull) tls_socket_write(rad_listen_t *listener) { - uint8_t *p; ssize_t rcode; listen_socket_t *sock = listener->data; - 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, - (sock->ssn->dirty_out.data + sock->ssn->dirty_out.used) - p); - if (rcode <= 0) { - RDEBUG("Error writing to TLS socket: %s", fr_syserror(errno)); + /* + * It's not writable, so we don't bother writing to it. + */ + if (listener->blocked) return 0; - tls_socket_close(listener); + /* + * Write as much as possible. + */ + rcode = write(listener->fd, sock->ssn->dirty_out.data, sock->ssn->dirty_out.used); + if (rcode <= 0) { +#ifdef EWOULDBLOCK + /* + * Writing to the socket would cause it to block. + * As a result, we just mark it as "don't use" + * until such time as it becomes writable. + */ + if (errno == EWOULDBLOCK) { + proxy_listener_freeze(listener, tls_write_available); return 0; } - p += rcode; +#endif + + + ERROR("(TLS) Error writing to socket: %s", fr_syserror(errno)); + + tls_socket_close(listener); + return -1; } - sock->ssn->dirty_out.used = 0; + /* + * All of the data was written. It's fine. + */ + if ((size_t) rcode == sock->ssn->dirty_out.used) { + sock->ssn->dirty_out.used = 0; + return 0; + } - return 1; + /* + * Move the data to the start of the buffer. + * + * Yes, this is horrible. But doing this means that we + * don't have to modify the rest of the code which mangles dirty_out, and assumes that the write offset is always &data[used]. + */ + memmove(&sock->ssn->dirty_out.data[0], &sock->ssn->dirty_out.data[rcode], sock->ssn->dirty_out.used - rcode); + sock->ssn->dirty_out.used -= rcode; + + return 0; +} + +static void tls_write_available(UNUSED fr_event_list_t *el, UNUSED int fd, void *ctx) +{ + rad_listen_t *listener = ctx; + listen_socket_t *sock = listener->data; + + proxy_listener_thaw(listener); + + PTHREAD_MUTEX_LOCK(&sock->mutex); + (void) tls_socket_write(listener); + PTHREAD_MUTEX_UNLOCK(&sock->mutex); } static int tls_socket_recv(rad_listen_t *listener) { - bool doing_init = false; + bool doing_init = false, already_read = false; ssize_t rcode; RADIUS_PACKET *packet; REQUEST *request; listen_socket_t *sock = listener->data; fr_tls_status_t status; - RADCLIENT *client = sock->client; if (!sock->packet) { sock->packet = rad_alloc(sock, false); @@ -165,7 +207,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; @@ -176,6 +218,8 @@ static int tls_socket_recv(rad_listen_t *listener) 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 +230,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,33 +250,38 @@ 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, - sock->ssn->dirty_in.data, - sizeof(sock->ssn->dirty_in.data)); - if ((rcode < 0) && (errno == ECONNRESET)) { - do_close: - DEBUG("Closing TLS socket from client port %u", sock->other_port); - tls_socket_close(listener); - PTHREAD_MUTEX_UNLOCK(&sock->mutex); - return 0; - } + if (!already_read) { + rcode = read(request->packet->sockfd, + sock->ssn->dirty_in.data, + sizeof(sock->ssn->dirty_in.data)); + if ((rcode < 0) && (errno == ECONNRESET)) { + do_close: + 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)); - goto do_close; - } + if (rcode < 0) { + RDEBUG("(TLS) Error reading socket: %s", fr_syserror(errno)); + goto do_close; + } - /* - * Normal socket close. - */ - if (rcode == 0) goto do_close; + /* + * Normal socket close. + */ + if (rcode == 0) { + RDEBUG("(TLS) Client has closed the TCP connection"); + goto do_close; + } - sock->ssn->dirty_in.used = rcode; + sock->ssn->dirty_in.used = rcode; + } dump_hex("READ FROM SSL", sock->ssn->dirty_in.data, sock->ssn->dirty_in.used); @@ -229,16 +289,17 @@ static int tls_socket_recv(rad_listen_t *listener) * 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; } @@ -246,24 +307,93 @@ static int tls_socket_recv(rad_listen_t *listener) * More ACK data to send. Do so. */ if (sock->ssn->dirty_out.used > 0) { - tls_socket_write(listener, request); + RDEBUG3("(TLS) Writing to socket %d", listener->fd); + tls_socket_write(listener); + PTHREAD_MUTEX_UNLOCK(&sock->mutex); + return 0; + } + + /* + * 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; + request->listener = listener; + sock->state = LISTEN_TLS_CHECKING; + PTHREAD_MUTEX_UNLOCK(&sock->mutex); /* - * FIXME: Run the request through a virtual - * server in order to see if we like the - * certificate presented by the client. + * Don't read from the socket until the request + * returns. */ + listener->status = RAD_LISTEN_STATUS_PAUSE; + radius_update_listener(listener); + + return 1; } /* * Try to get application data. */ get_application_data: + /* + * More data to send. Do so. + */ + if (sock->ssn->dirty_out.used > 0) { + RDEBUG3("(TLS) Writing to socket %d", listener->fd); + rcode = tls_socket_write(listener); + if (rcode < 0) { + PTHREAD_MUTEX_UNLOCK(&sock->mutex); + return rcode; + } + } + status = tls_application_data(sock->ssn, request); - RDEBUG("Application data status %d", status); + RDEBUG3("(TLS) Application data status %d", status); + + /* + * Some kind of failure. Close the socket. + */ + if (status == FR_TLS_FAIL) { + DEBUG("(TLS) Unable to recover from TLS error, closing socket from client port %u", sock->other_port); + tls_socket_close(listener); + PTHREAD_MUTEX_UNLOCK(&sock->mutex); + return 0; + } if (status == FR_TLS_MORE_FRAGMENTS) { PTHREAD_MUTEX_UNLOCK(&sock->mutex); @@ -275,6 +405,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 +426,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; @@ -299,9 +439,11 @@ get_application_data: packet->vps = NULL; PTHREAD_MUTEX_UNLOCK(&sock->mutex); + packet->tls = true; + 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 +457,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 +465,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)), @@ -333,8 +475,6 @@ get_application_data: } } - FR_STATS_INC(auth, total_requests); - return 1; } @@ -359,6 +499,7 @@ redo: rad_assert(client != NULL); packet = talloc_steal(NULL, sock->packet); + sock->request->packet = NULL; sock->packet = NULL; /* @@ -391,8 +532,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 +564,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 +591,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 +614,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. * @@ -507,38 +694,43 @@ int dual_tls_send(rad_listen_t *listener, REQUEST *request) if (sock->ssn->dirty_out.used > 0) { dump_hex("WRITE TO SSL", sock->ssn->dirty_out.data, sock->ssn->dirty_out.used); - tls_socket_write(listener, request); + RDEBUG3("(TLS) Writing to socket %d", listener->fd); + tls_socket_write(listener); } PTHREAD_MUTEX_UNLOCK(&sock->mutex); return 0; } -static int try_connect(tls_session_t *ssn) +static int try_connect(listen_socket_t *sock) { int ret; - ret = SSL_connect(ssn->ssl); - if (ret < 0) { - switch (SSL_get_error(ssn->ssl, ret)) { - default: - break; + time_t now; + now = time(NULL); + if ((sock->opened + sock->connect_timeout) < now) { + tls_error_io_log(NULL, sock->ssn, 0, "Timeout in SSL_connect"); + return -1; + } + ret = SSL_connect(sock->ssn->ssl); + if (ret <= 0) { + switch (SSL_get_error(sock->ssn->ssl, ret)) { + default: + tls_error_io_log(NULL, sock->ssn, ret, "Failed in " STRINGIFY(__FUNCTION__) " (SSL_connect)"); + return -1; case SSL_ERROR_WANT_READ: + DEBUG3("(TLS) SSL_connect() returned WANT_READ"); + return 2; + case SSL_ERROR_WANT_WRITE: - ssn->connected = false; - return 0; + DEBUG3("(TLS) SSL_connect() returned WANT_WRITE"); + return 2; } } - if (ret <= 0) { - tls_error_io_log(NULL, ssn, ret, "Failed in " STRINGIFY(__FUNCTION__) " (SSL_connect)"); - talloc_free(ssn); - - return -1; - } - + sock->ssn->connected = true; return 1; } @@ -564,15 +756,27 @@ static ssize_t proxy_tls_read(rad_listen_t *listener) listen_socket_t *sock = listener->data; if (!sock->ssn->connected) { - rcode = try_connect(sock->ssn); - if (rcode == 0) return 0; + rcode = try_connect(sock); + if (rcode <= 0) return rcode; - if (rcode < 0) { - SSL_shutdown(sock->ssn->ssl); - return -1; - } + if (rcode == 2) return 0; /* more negotiation needed */ + } - sock->ssn->connected = true; + if (sock->ssn->clean_out.used) { + DEBUG3("(TLS) proxy writing %zu to socket", sock->ssn->clean_out.used); + /* + * Write to SSL. + */ + rcode = SSL_write(sock->ssn->ssl, sock->ssn->clean_out.data, sock->ssn->clean_out.used); + if (rcode > 0) { + if ((size_t) rcode < sock->ssn->clean_out.used) { + memmove(sock->ssn->clean_out.data, sock->ssn->clean_out.data + rcode, + sock->ssn->clean_out.used - rcode); + sock->ssn->clean_out.used -= rcode; + } else { + sock->ssn->clean_out.used = 0; + } + } } /* @@ -589,9 +793,14 @@ static ssize_t proxy_tls_read(rad_listen_t *listener) if (rcode <= 0) { int err = SSL_get_error(sock->ssn->ssl, rcode); switch (err) { + case SSL_ERROR_WANT_READ: + DEBUG3("(TLS) OpenSSL returned WANT_READ"); + return 0; + case SSL_ERROR_WANT_WRITE: - return 0; /* do some more work later */ + DEBUG3("(TLS) OpenSSL returned WANT_WRITE"); + return 0; case SSL_ERROR_ZERO_RETURN: /* remote end sent close_notify, send one back */ @@ -602,9 +811,12 @@ static ssize_t proxy_tls_read(rad_listen_t *listener) do_close: return -1; - default: - tls_error_log(NULL, "Failed in proxy receive"); + case SSL_ERROR_SSL: + DEBUG("(TLS) Home server has closed the connection"); + goto do_close; + default: + tls_error_log(NULL, "Failed in proxy receive with OpenSSL error %d", err); goto do_close; } } @@ -641,16 +853,28 @@ static ssize_t proxy_tls_read(rad_listen_t *listener) rcode = SSL_read(sock->ssn->ssl, data + sock->partial, length - sock->partial); if (rcode <= 0) { - switch (SSL_get_error(sock->ssn->ssl, rcode)) { + int err = SSL_get_error(sock->ssn->ssl, rcode); + switch (err) { + case SSL_ERROR_WANT_READ: + DEBUG3("(TLS) OpenSSL returned WANT_READ"); + return 0; + case SSL_ERROR_WANT_WRITE: + DEBUG3("(TLS) OpenSSL returned WANT_WRITE"); return 0; case SSL_ERROR_ZERO_RETURN: /* remote end sent close_notify, send one back */ SSL_shutdown(sock->ssn->ssl); goto do_close; + + case SSL_ERROR_SSL: + DEBUG("(TLS) Home server has closed the connection"); + goto do_close; + default: + DEBUG("(TLS) Unexpected OpenSSL error %d", err); goto do_close; } } @@ -683,18 +907,18 @@ int proxy_tls_recv(rad_listen_t *listener) if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0; + rad_assert(sock->ssn != NULL); + DEBUG3("Proxy SSL socket has data to read"); PTHREAD_MUTEX_LOCK(&sock->mutex); data_len = proxy_tls_read(listener); - PTHREAD_MUTEX_UNLOCK(&sock->mutex); - if (data_len < 0) { - DEBUG("Closing TLS socket to home server"); - PTHREAD_MUTEX_LOCK(&sock->mutex); tls_socket_close(listener); PTHREAD_MUTEX_UNLOCK(&sock->mutex); + DEBUG("Closing TLS socket to home server"); return 0; } + PTHREAD_MUTEX_UNLOCK(&sock->mutex); if (data_len == 0) return 0; /* not done yet */ @@ -713,6 +937,8 @@ int proxy_tls_recv(rad_listen_t *listener) memcpy(packet->data, data, packet->data_len); memcpy(packet->vector, packet->data + 4, 16); + packet->tls = true; + /* * FIXME: Client MIB updates? */ @@ -727,6 +953,14 @@ 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,42 +999,111 @@ 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->reply->tls = true; + request->proxy_listener->proxy_encode(request->proxy_listener, + request); } + rad_assert(sock->ssn != NULL); + if (!sock->ssn->connected) { PTHREAD_MUTEX_LOCK(&sock->mutex); - rcode = try_connect(sock->ssn); + rcode = try_connect(sock); + if (rcode <= 0) { + tls_socket_close(listener); + PTHREAD_MUTEX_UNLOCK(&sock->mutex); + return rcode; + } PTHREAD_MUTEX_UNLOCK(&sock->mutex); - if (rcode == 0) return 0; - if (rcode < 0) { - SSL_shutdown(sock->ssn->ssl); - return -1; - } + /* + * More negotiation is needed, but remember to + * save this packet to an intermediate buffer. + * Once the SSL connection is established, the + * later code writes the packet to the + * connection. + */ + if (rcode == 2) { + PTHREAD_MUTEX_LOCK(&sock->mutex); + if ((sock->ssn->clean_out.used + request->proxy->data_len) > MAX_RECORD_SIZE) { + PTHREAD_MUTEX_UNLOCK(&sock->mutex); + RERROR("(TLS) Too much data buffered during SSL_connect()"); + listener->status = RAD_LISTEN_STATUS_EOL; + radius_update_listener(listener); + return -1; + } - sock->ssn->connected = true; + memcpy(sock->ssn->clean_out.data + sock->ssn->clean_out.used, request->proxy->data, request->proxy->data_len); + sock->ssn->clean_out.used += request->proxy->data_len; + RDEBUG3("(TLS) Writing %zu bytes for later (total %zu)", request->proxy->data_len, sock->ssn->clean_out.used); + + PTHREAD_MUTEX_UNLOCK(&sock->mutex); + return 0; + } } DEBUG3("Proxy is writing %u bytes to SSL", (unsigned int) request->proxy->data_len); PTHREAD_MUTEX_LOCK(&sock->mutex); - rcode = SSL_write(sock->ssn->ssl, request->proxy->data, - request->proxy->data_len); + + /* + * We may have previously cached data on SSL_connect(), which now needs to be written to the home server. + */ + if (sock->ssn->clean_out.used > 0) { + if ((sock->ssn->clean_out.used + request->proxy->data_len) > MAX_RECORD_SIZE) { + PTHREAD_MUTEX_UNLOCK(&sock->mutex); + RERROR("(TLS) Too much data buffered after SSL_connect()"); + listener->status = RAD_LISTEN_STATUS_EOL; + radius_update_listener(listener); + return -1; + } + + /* + * Add in our packet. + */ + memcpy(sock->ssn->clean_out.data + sock->ssn->clean_out.used, request->proxy->data, request->proxy->data_len); + sock->ssn->clean_out.used += request->proxy->data_len; + + /* + * Write to SSL. + */ + DEBUG3("(TLS) proxy writing %zu to socket", sock->ssn->clean_out.used); + + rcode = SSL_write(sock->ssn->ssl, sock->ssn->clean_out.data, sock->ssn->clean_out.used); + if (rcode > 0) { + if ((size_t) rcode < sock->ssn->clean_out.used) { + memmove(sock->ssn->clean_out.data, sock->ssn->clean_out.data + rcode, + sock->ssn->clean_out.used - rcode); + sock->ssn->clean_out.used -= rcode; + } else { + sock->ssn->clean_out.used = 0; + } + PTHREAD_MUTEX_UNLOCK(&sock->mutex); + return 1; + } + } else { + rcode = SSL_write(sock->ssn->ssl, request->proxy->data, + request->proxy->data_len); + } if (rcode < 0) { int err; err = ERR_get_error(); switch (err) { case SSL_ERROR_NONE: + break; + case SSL_ERROR_WANT_READ: + DEBUG3("(TLS) OpenSSL returned WANT_READ"); + break; + case SSL_ERROR_WANT_WRITE: - break; /* let someone else retry */ + DEBUG3("(TLS) OpenSSL returned WANT_WRITE"); + break; default: - tls_error_log(NULL, "Failed in proxy send"); - DEBUG("Closing TLS socket to home server"); + tls_error_log(NULL, "Failed in proxy send with OpenSSL error %d", err); + DEBUG("(TLS) Closing socket to home server"); tls_socket_close(listener); PTHREAD_MUTEX_UNLOCK(&sock->mutex); return 0; diff --git a/src/modules/proto_dhcp/rlm_dhcp.c b/src/modules/proto_dhcp/rlm_dhcp.c index 9fed166e62..1cd73ff246 100644 --- a/src/modules/proto_dhcp/rlm_dhcp.c +++ b/src/modules/proto_dhcp/rlm_dhcp.c @@ -97,7 +97,7 @@ static ssize_t dhcp_options_xlat(UNUSED void *instance, REQUEST *request, decoded++; } - fr_pair_list_move(request->packet, &(request->packet->vps), &head); + fr_pair_list_move(request->packet, &(request->packet->vps), &head, T_OP_ADD); /* Free any unmoved pairs */ fr_pair_list_free(&head); diff --git a/src/modules/rlm_eap/libeap/eap_tls.c b/src/modules/rlm_eap/libeap/eap_tls.c index 83e7252fa8..2f37663df1 100644 --- a/src/modules/rlm_eap/libeap/eap_tls.c +++ b/src/modules/rlm_eap/libeap/eap_tls.c @@ -1,3 +1,4 @@ + /* * eap_tls.c * @@ -61,7 +62,7 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ * * Fragment length is Framed-MTU - 4. */ -tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert) +tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert, bool allow_tls13) { tls_session_t *ssn; REQUEST *request = handler->request; @@ -75,7 +76,7 @@ tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_ * in Opaque. So that we can use these data structures * when we get the response */ - ssn = tls_new_session(handler, tls_conf, request, client_cert); + ssn = tls_new_session(handler, tls_conf, request, client_cert, allow_tls13); if (!ssn) { return NULL; } @@ -139,7 +140,7 @@ int eaptls_start(EAP_DS *eap_ds, int peap_flag) */ int eaptls_success(eap_handler_t *handler, int peap_flag) { - EAPTLS_PACKET reply; + EAPTLS_PACKET reply; REQUEST *request = handler->request; tls_session_t *tls_session = handler->opaque; @@ -160,15 +161,42 @@ int eaptls_success(eap_handler_t *handler, int peap_flag) /* * Automatically generate MPPE keying material. */ - if (tls_session->prf_label) { - eaptls_gen_mppe_keys(handler->request, - tls_session->ssl, tls_session->prf_label); + if (tls_session->label) { + uint8_t const *context = NULL; + size_t context_size = 0; +#ifdef TLS1_3_VERSION + uint8_t const context_tls13[] = { handler->type }; +#endif + + switch (SSL_version(tls_session->ssl)) { +#ifdef TLS1_3_VERSION + case TLS1_3_VERSION: + context = context_tls13; + context_size = sizeof(context_tls13); + tls_session->label = "EXPORTER_EAP_TLS_Key_Material"; + break; +#endif + case TLS1_2_VERSION: + case TLS1_1_VERSION: + case TLS1_VERSION: + break; + case SSL2_VERSION: + case SSL3_VERSION: + default: + /* Should never happen */ + rad_assert(0); + return 0; + break; + } + eaptls_gen_mppe_keys(request, + tls_session->ssl, tls_session->label, + context, context_size); } else if (handler->type != PW_EAP_FAST) { - RWDEBUG("Not adding MPPE keys because there is no PRF label"); + RWDEBUG("(TLS) EAP Not adding MPPE keys because there is no PRF label"); } - eaptls_gen_eap_key(handler->request->reply, tls_session->ssl, - handler->type); + eaptls_gen_eap_key(handler); + return 1; } @@ -291,7 +319,7 @@ static int eaptls_send_ack(eap_handler_t *handler, int peap_flag) EAPTLS_PACKET reply; REQUEST *request = handler->request; - RDEBUG2("ACKing Peer's TLS record fragment"); + RDEBUG2("(TLS) EAP ACKing fragment, the peer should send more data."); reply.code = FR_TLS_ACK; reply.length = TLS_HEADER_LEN + 1/*flags*/; reply.flags = peap_flag; @@ -343,7 +371,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) /* * First output the flags (for debugging) */ - RDEBUG3("Peer sent flags %c%c%c", + RDEBUG3("(TLS) EAP Peer sent flags %c%c%c", TLS_START(eaptls_packet->flags) ? 'S' : '-', TLS_MORE_FRAGMENTS(eaptls_packet->flags) ? 'M' : '-', TLS_LENGTH_INCLUDED(eaptls_packet->flags) ? 'L' : '-'); @@ -364,7 +392,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) if (prev_eap_ds && (prev_eap_ds->request->id == eap_ds->response->id)) { return tls_ack_handler(handler->opaque, request); } else { - REDEBUG("Received Invalid TLS ACK"); + REDEBUG("(TLS) EAP Received Unexpected ACK - rejection the connection"); return FR_TLS_INVALID; } } @@ -373,7 +401,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) * We send TLS_START, but do not receive it. */ if (TLS_START(eaptls_packet->flags)) { - REDEBUG("Peer sent EAP-TLS Start message (only the server is allowed to do this)"); + REDEBUG("(TLS) EAP Peer sent EAP-TLS Start message (only the server is allowed to do this)"); return FR_TLS_INVALID; } @@ -400,11 +428,11 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) size_t total_len = eaptls_packet->data[2] * 256 | eaptls_packet->data[3]; if (frag_len > total_len) { - RWDEBUG("TLS fragment length (%zu bytes) greater than TLS record length (%zu bytes)", frag_len, + RWDEBUG("(TLS) EAP Fragment length (%zu bytes) is greater than TLS record length (%zu bytes)", frag_len, total_len); } - RDEBUG2("Peer indicated complete TLS record size will be %zu bytes", total_len); + RDEBUG2("(TLS) EAP Peer says that the final record size will be %zu bytes", total_len); if (TLS_MORE_FRAGMENTS(eaptls_packet->flags)) { /* * The supplicant is free to send fragments of wildly varying @@ -415,7 +443,7 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) * as they won't contain the length field. */ if (frag_len + 4) { /* check for wrap, else clang scan gets excited */ - RDEBUG2("Expecting %i TLS record fragments", + RDEBUG2("(TLS) EAP Expecting %i fragments", (int)((((total_len - frag_len) + ((frag_len + 4) - 1)) / (frag_len + 4)) + 1)); } @@ -427,24 +455,24 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) */ if (!prev_eap_ds || (!prev_eap_ds->response) || (!eaptls_prev) || !TLS_MORE_FRAGMENTS(eaptls_prev->flags)) { - RDEBUG2("Got first TLS record fragment (%zu bytes). Peer indicated more fragments " - "to follow", frag_len); + RDEBUG2("(TLS) EAP Got first TLS fragment (%zu bytes). Peer says more fragments " + "will follow", frag_len); tls_session->tls_record_in_total_len = total_len; tls_session->tls_record_in_recvd_len = frag_len; return FR_TLS_FIRST_FRAGMENT; } - RDEBUG2("Got additional TLS record fragment with length (%zu bytes). " - "Peer indicated more fragments to follow", frag_len); + RDEBUG2("(TLS) EAP Got additional fragment with length (%zu bytes). " + "Peer says more fragments will follow", frag_len); /* * Check we've not exceeded the originally indicated TLS record size. */ tls_session->tls_record_in_recvd_len += frag_len; if (tls_session->tls_record_in_recvd_len > tls_session->tls_record_in_total_len) { - RWDEBUG("Total received TLS record fragments (%zu bytes), exceeds " - "total TLS record length (%zu bytes)", frag_len, total_len); + RWDEBUG("(TLS) EAP Total received fragments (%zu bytes), exceeds " + "total data length (%zu bytes)", frag_len, total_len); } return FR_TLS_MORE_FRAGMENTS_WITH_LENGTH; @@ -455,13 +483,13 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) * value of the four octet TLS length field. */ if (total_len != frag_len) { - RWDEBUG("Peer indicated no more fragments, but TLS record length (%zu bytes) " - "does not match EAP-TLS data length (%zu bytes)", total_len, frag_len); + RWDEBUG("(TLS) EAP Peer says no more fragments, but expected data length (%zu bytes) " + "does not match expected data length (%zu bytes)", total_len, frag_len); } tls_session->tls_record_in_total_len = total_len; tls_session->tls_record_in_recvd_len = frag_len; - RDEBUG2("Got complete TLS record (%zu bytes)", frag_len); + RDEBUG2("(TLS) EAP Got all data (%zu bytes)", frag_len); return FR_TLS_LENGTH_INCLUDED; } @@ -470,22 +498,22 @@ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) * this must be the final record fragment */ if ((eaptls_prev && TLS_MORE_FRAGMENTS(eaptls_prev->flags)) && !TLS_MORE_FRAGMENTS(eaptls_packet->flags)) { - RDEBUG2("Got final TLS record fragment (%zu bytes)", frag_len); + RDEBUG2("(TLS) EAP Got final fragment (%zu bytes)", frag_len); tls_session->tls_record_in_recvd_len += frag_len; if (tls_session->tls_record_in_recvd_len != tls_session->tls_record_in_total_len) { - RWDEBUG("Total received TLS record fragments (%zu bytes), does not equal indicated " - "TLS record length (%zu bytes)", + RWDEBUG("(TLS) EAP Total received record fragments (%zu bytes), does not equal expected " + "expected data length (%zu bytes)", tls_session->tls_record_in_recvd_len, tls_session->tls_record_in_total_len); } } if (TLS_MORE_FRAGMENTS(eaptls_packet->flags)) { - RDEBUG2("Got additional TLS record fragment (%zu bytes). Peer indicated more fragments to follow", + RDEBUG2("(TLS) EAP Got additional fragment (%zu bytes). Peer says more fragments will follow", frag_len); tls_session->tls_record_in_recvd_len += frag_len; if (tls_session->tls_record_in_recvd_len > tls_session->tls_record_in_total_len) { - RWDEBUG("Total received TLS record fragments (%zu bytes), exceeds " - "indicated TLS record length (%zu bytes)", + RWDEBUG("(TLS) EAP Total received fragments (%zu bytes), exceeds " + "expected length (%zu bytes)", tls_session->tls_record_in_recvd_len, tls_session->tls_record_in_total_len); } return FR_TLS_MORE_FRAGMENTS; @@ -576,7 +604,7 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st */ if (TLS_LENGTH_INCLUDED(tlspacket->flags) && (tlspacket->length < 5)) { /* flags + TLS message length */ - REDEBUG("Invalid EAP-TLS packet received: Length bit is set, " + REDEBUG("(TLS) EAP Invalid packet received: Length bit is set," "but packet too short to contain length field"); talloc_free(tlspacket); return NULL; @@ -593,8 +621,8 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st memcpy(&data_len, &eap_ds->response->type.data[1], 4); data_len = ntohl(data_len); if (data_len > MAX_RECORD_SIZE) { - REDEBUG("Reassembled TLS record will be %u bytes, " - "greater than our maximum record size (" STRINGIFY(MAX_RECORD_SIZE) " bytes)", + REDEBUG("(TLS) EAP Reassembled data will be %u bytes, " + "greater than the size that we can handle (" STRINGIFY(MAX_RECORD_SIZE) " bytes)", data_len); talloc_free(tlspacket); return NULL; @@ -615,7 +643,7 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st case FR_TLS_LENGTH_INCLUDED: case FR_TLS_MORE_FRAGMENTS_WITH_LENGTH: if (tlspacket->length < 5) { /* flags + TLS message length */ - REDEBUG("Invalid EAP-TLS packet received: Expected length, got none"); + REDEBUG("(TLS) EAP Invalid packet received: Expected length, got none"); talloc_free(tlspacket); return NULL; } @@ -648,7 +676,7 @@ static EAPTLS_PACKET *eaptls_extract(REQUEST *request, EAP_DS *eap_ds, fr_tls_st break; default: - REDEBUG("Invalid EAP-TLS packet received"); + REDEBUG("(TLS) EAP Invalid packet received"); talloc_free(tlspacket); return NULL; } @@ -719,11 +747,37 @@ static fr_tls_status_t eaptls_operation(fr_tls_status_t status, eap_handler_t *h * is required then send another request. */ if (!tls_handshake_recv(handler->request, tls_session)) { - REDEBUG("TLS receive handshake failed during operation"); + REDEBUG("(TLS) EAP Receive handshake failed during operation"); tls_fail(tls_session); return FR_TLS_FAIL; } +#ifdef TLS1_3_VERSION + /* + * https://tools.ietf.org/html/draft-ietf-emu-eap-tls13#section-2.5 + * + * We need to signal the other end that TLS negotiation + * is done. We can't send a zero-length application data + * message, so we send application data which is one byte + * of zero. + * + * Note this is only done for when there is no application + * data to be sent. So this is done always for EAP-TLS but + * notibly not for PEAP even on resumption. + */ + if ((SSL_version(tls_session->ssl) == TLS1_3_VERSION) && + (tls_session->client_cert_ok || tls_session->authentication_success || SSL_session_reused(tls_session->ssl))) { + if ((handler->type == PW_EAP_TLS) || SSL_session_reused(tls_session->ssl)) { + tls_session->authentication_success = true; + + RDEBUG("(TLS) EAP Sending final Commitment Message."); + tls_session->record_plus(&tls_session->clean_in, "\0", 1); + } + + tls_handshake_send(request, tls_session); + } +#endif + /* * FIXME: return success/fail. * @@ -737,23 +791,28 @@ static fr_tls_status_t eaptls_operation(fr_tls_status_t status, eap_handler_t *h /* * If there is no data to send i.e * dirty_out.used <=0 and if the SSL - * handshake is finished, then return a - * EPTLS_SUCCESS + * handshake is finished. */ + if (tls_session->is_init_finished) return FR_TLS_SUCCESS; - if (tls_session->is_init_finished) { + /* + * If session is established, skip round-trip and + * try to process any inner tunnel data if present. + * + * This occurs for EAP-TTLS/PAP with TLSv1.3. + */ + if (!tls_session->is_init_finished && SSL_is_init_finished(tls_session->ssl)) { /* - * Init is finished. The rest is - * application data. + * Don't set is_init_finished, as that causes the + * rest of the code to make too many assumptions. */ - tls_session->info.content_type = application_data; - return FR_TLS_SUCCESS; + return FR_TLS_OK; } /* * Who knows what happened... */ - REDEBUG("TLS failed during operation"); + REDEBUG("(TLS) Cannot continue, as the peer is misbehaving."); return FR_TLS_FAIL; } @@ -794,7 +853,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) if (!request) return FR_TLS_FAIL; - RDEBUG2("Continuing EAP-TLS"); + RDEBUG3("(TLS) EAP Continuing ..."); SSL_set_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST, request); @@ -807,9 +866,9 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) */ status = eaptls_verify(handler); if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { - REDEBUG("[eaptls verify] = %s", fr_int2str(fr_tls_status_table, status, "")); + REDEBUG("(TLS) EAP Verification failed with %s", fr_int2str(fr_tls_status_table, status, "")); } else { - RDEBUG2("[eaptls verify] = %s", fr_int2str(fr_tls_status_table, status, "")); + RDEBUG3("(TLS) EAP Verification says %s", fr_int2str(fr_tls_status_table, status, "")); } switch (status) { @@ -840,7 +899,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) * data" phase. */ case FR_TLS_OK: - RDEBUG2("Done initial handshake"); + RDEBUG2("(TLS) EAP Done initial handshake"); /* * Get the rest of the fragments. @@ -856,6 +915,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) * Extract the TLS packet from the buffer. */ if ((tlspacket = eaptls_extract(request, handler->eap_ds, status)) == NULL) { + REDEBUG("(TLS) EAP Failed extracting TLS packet from EAP-Message"); status = FR_TLS_FAIL; goto done; } @@ -873,7 +933,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) if (tlspacket->dlen != (tls_session->record_plus)(&tls_session->dirty_in, tlspacket->data, tlspacket->dlen)) { talloc_free(tlspacket); - REDEBUG("Exceeded maximum record size"); + REDEBUG("(TLS) EAP Exceeded maximum record size"); status = FR_TLS_FAIL; goto done; } @@ -902,7 +962,7 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) * Send the ACK. */ eaptls_send_ack(handler, tls_session->peap_flag); - RDEBUG2("Init is done, but tunneled data is fragmented"); + RDEBUG2("(TLS) EAP Init is done, but tunneled data is fragmented"); status = FR_TLS_HANDLED; goto done; } @@ -928,13 +988,13 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) vps = SSL_SESSION_get_ex_data(tls_session->ssl_session, fr_tls_ex_index_vps); if (!vps) { - RWDEBUG("No information in cached session %s", buffer); + RWDEBUG("(TLS) EAP No information in cached session %s", buffer); } else { vp_cursor_t cursor; VALUE_PAIR *vp; fr_tls_server_conf_t *conf; - RDEBUG("Adding cached attributes from session %s", buffer); + RDEBUG("(TLS) EAP Adding cached attributes from session %s", buffer); conf = (fr_tls_server_conf_t *)SSL_get_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_CONF); rad_assert(conf != NULL); @@ -970,7 +1030,19 @@ fr_tls_status_t eaptls_process(eap_handler_t *handler) rdebug_pair(L_DBG_LVL_2, request, vp, "&request:"); fr_pair_add(&request->packet->vps, fr_pair_copy(request->packet, vp)); } + + } else if ((vp->da->vendor == 0) && + (vp->da->attr == PW_EAP_TYPE)) { + /* + * EAP-Type gets added to + * the control list, so + * that we can sanity check it. + */ + rdebug_pair(L_DBG_LVL_2, request, vp, "&control:"); + fr_pair_add(&request->config, fr_pair_copy(request, vp)); + } else { + rdebug_pair(L_DBG_LVL_2, request, vp, "&reply:"); fr_pair_add(&request->reply->vps, fr_pair_copy(request->reply, vp)); } diff --git a/src/modules/rlm_eap/libeap/eap_tls.h b/src/modules/rlm_eap/libeap/eap_tls.h index 73c7fdd53b..8e5fc773d6 100644 --- a/src/modules/rlm_eap/libeap/eap_tls.h +++ b/src/modules/rlm_eap/libeap/eap_tls.h @@ -62,11 +62,11 @@ int eaptls_fail(eap_handler_t *handler, int peap_flag) CC_HINT(nonnull); int eaptls_request(EAP_DS *eap_ds, tls_session_t *ssn) CC_HINT(nonnull); -void T_PRF(unsigned char const *secret, unsigned int secret_len, char const *prf_label, unsigned char const *seed, unsigned int seed_len, unsigned char *out, unsigned int out_len) CC_HINT(nonnull(1,3,6)); -void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label); +void T_PRF(unsigned char const *secret, unsigned int secret_len, char const *prf_label, unsigned char const *seed, unsigned int seed_len, unsigned char *out, unsigned int out_len) CC_HINT(nonnull(1,3,6)); +void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *label, uint8_t const *context, size_t context_size); void eapttls_gen_challenge(SSL *s, uint8_t *buffer, size_t size); -void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *s, uint32_t header); -void eap_fast_tls_gen_challenge(SSL *ssl, uint8_t *buffer, uint8_t *scratch, size_t size, char const *prf_label) CC_HINT(nonnull); +void eaptls_gen_eap_key(eap_handler_t *handler); +void eap_fast_tls_gen_challenge(SSL *ssl, int version, uint8_t *buffer, size_t size, char const *prf_label) CC_HINT(nonnull); #define BUFFER_SIZE 1024 @@ -100,7 +100,7 @@ typedef struct tls_packet { /* EAP-TLS framework */ EAPTLS_PACKET *eaptls_alloc(void); void eaptls_free(EAPTLS_PACKET **eaptls_packet_ptr); -tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert); +tls_session_t *eaptls_session(eap_handler_t *handler, fr_tls_server_conf_t *tls_conf, bool client_cert, bool allow_tls13); int eaptls_start(EAP_DS *eap_ds, int peap); int eaptls_compose(EAP_DS *eap_ds, EAPTLS_PACKET *reply); diff --git a/src/modules/rlm_eap/libeap/mppe_keys.c b/src/modules/rlm_eap/libeap/mppe_keys.c index 3a9e864104..385441c62f 100644 --- a/src/modules/rlm_eap/libeap/mppe_keys.c +++ b/src/modules/rlm_eap/libeap/mppe_keys.c @@ -26,11 +26,16 @@ RCSID("$Id$") USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ #include "eap_tls.h" +#include #include +#include +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include +#endif /* - * TLS PRF from RFC 2246 + * TLS P_hash from RFC 2246/5246 section 5 */ static void P_hash(EVP_MD const *evp_md, unsigned char const *secret, unsigned int secret_len, @@ -38,23 +43,18 @@ static void P_hash(EVP_MD const *evp_md, unsigned char *out, unsigned int out_len) { HMAC_CTX *ctx_a, *ctx_out; - unsigned char a[HMAC_MAX_MD_CBLOCK]; - unsigned int size; + unsigned char a[EVP_MAX_MD_SIZE]; + unsigned int size = EVP_MAX_MD_SIZE; + unsigned int digest_len; ctx_a = HMAC_CTX_new(); ctx_out = HMAC_CTX_new(); -#ifdef EVP_MD_CTX_FLAG_NON_FIPS_ALLOW - HMAC_CTX_set_flags(ctx_a, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); - HMAC_CTX_set_flags(ctx_out, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); -#endif HMAC_Init_ex(ctx_a, secret, secret_len, evp_md, NULL); HMAC_Init_ex(ctx_out, secret, secret_len, evp_md, NULL); - size = HMAC_size(ctx_out); - /* Calculate A(1) */ HMAC_Update(ctx_a, seed, seed_len); - HMAC_Final(ctx_a, a, NULL); + HMAC_Final(ctx_a, a, &size); while (1) { /* Calculate next part of output */ @@ -63,13 +63,15 @@ static void P_hash(EVP_MD const *evp_md, /* Check if last part */ if (out_len < size) { - HMAC_Final(ctx_out, a, NULL); + digest_len = EVP_MAX_MD_SIZE; + HMAC_Final(ctx_out, a, &digest_len); memcpy(out, a, out_len); break; } /* Place digest in output buffer */ - HMAC_Final(ctx_out, out, NULL); + digest_len = EVP_MAX_MD_SIZE; + HMAC_Final(ctx_out, out, &digest_len); HMAC_Init_ex(ctx_out, NULL, 0, NULL, NULL); out += size; out_len -= size; @@ -77,7 +79,8 @@ static void P_hash(EVP_MD const *evp_md, /* Calculate next A(i) */ HMAC_Init_ex(ctx_a, NULL, 0, NULL, NULL); HMAC_Update(ctx_a, a, size); - HMAC_Final(ctx_a, a, NULL); + digest_len = EVP_MAX_MD_SIZE; + HMAC_Final(ctx_a, a, &digest_len); } HMAC_CTX_free(ctx_a); @@ -85,6 +88,82 @@ static void P_hash(EVP_MD const *evp_md, memset(a, 0, sizeof(a)); } +/* + * TLS PRF from RFC 2246 section 5 + */ +static void PRF(unsigned char const *secret, unsigned int secret_len, + unsigned char const *seed, unsigned int seed_len, + unsigned char *out, unsigned int out_len) +{ + uint8_t buf[out_len + (out_len % SHA_DIGEST_LENGTH)]; + unsigned int i; + + unsigned int len = (secret_len + 1) / 2; + uint8_t const *s1 = secret; + uint8_t const *s2 = secret + (secret_len - len); + + EVP_MD const *md5 = NULL; + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MD *md5_to_free = NULL; + + /* + * If we are using OpenSSL >= 3.0 and FIPS mode is + * enabled, we need to load the default provider in a + * standalone context in order to access MD5. + */ + OSSL_LIB_CTX *libctx = NULL; + OSSL_PROVIDER *default_provider = NULL; + + if (EVP_default_properties_is_fips_enabled(NULL)) { + libctx = OSSL_LIB_CTX_new(); + default_provider = OSSL_PROVIDER_load(libctx, "default"); + + if (!default_provider) { + ERROR("Failed loading OpenSSL default provider."); + return; + } + + md5_to_free = EVP_MD_fetch(libctx, "MD5", NULL); + if (!md5_to_free) { + ERROR("Failed loading OpenSSL MD5 function."); + return; + } + + md5 = md5_to_free; + } else { + md5 = EVP_md5(); + } +#else + md5 = EVP_md5(); +#endif + + P_hash(md5, s1, len, seed, seed_len, out, out_len); + P_hash(EVP_sha1(), s2, len, seed, seed_len, buf, out_len); + + for (i = 0; i < out_len; i++) { + out[i] ^= buf[i]; + } + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + if (libctx) { + OSSL_PROVIDER_unload(default_provider); + OSSL_LIB_CTX_free(libctx); + EVP_MD_free(md5_to_free); + } +#endif +} + +/* + * TLS 1.2 PRF from RFC 5246 section 5 + */ +static void PRFv12(unsigned char const *secret, unsigned int secret_len, + unsigned char const *seed, unsigned int seed_len, + unsigned char *out, unsigned int out_len) +{ + P_hash(EVP_sha256(), secret, secret_len, seed, seed_len, out, out_len); +} + /* EAP-FAST Pseudo-Random Function (T-PRF): RFC 4851, Section 5.5 */ void T_PRF(unsigned char const *secret, unsigned int secret_len, char const *prf_label, @@ -128,60 +207,55 @@ void T_PRF(unsigned char const *secret, unsigned int secret_len, talloc_free(buf); } -static void PRF(unsigned char const *secret, unsigned int secret_len, - unsigned char const *seed, unsigned int seed_len, - unsigned char *out, unsigned char *buf, unsigned int out_len) -{ - unsigned int i; - unsigned int len = (secret_len + 1) / 2; - uint8_t const *s1 = secret; - uint8_t const *s2 = secret + (secret_len - len); - - P_hash(EVP_md5(), s1, len, seed, seed_len, out, out_len); - P_hash(EVP_sha1(), s2, len, seed, seed_len, buf, out_len); - - for (i=0; i < out_len; i++) { - out[i] ^= buf[i]; - } -} - #define EAPTLS_MPPE_KEY_LEN 32 /* * Generate keys according to RFC 2716 and add to reply */ -void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label) +void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *label, uint8_t const *context, UNUSED size_t context_size) { uint8_t out[4 * EAPTLS_MPPE_KEY_LEN]; uint8_t *p; - size_t prf_size; + size_t len; - prf_size = strlen(prf_label); + len = strlen(label); #if OPENSSL_VERSION_NUMBER >= 0x10001000L - if (SSL_export_keying_material(s, out, sizeof(out), prf_label, prf_size, NULL, 0, 0) != 1) { + if (SSL_export_keying_material(s, out, sizeof(out), label, len, context, context_size, context != NULL) != 1) { ERROR("Failed generating keying material"); return; } #else { - uint8_t seed[64 + (2 * SSL3_RANDOM_SIZE)]; + uint8_t seed[64 + (2 * SSL3_RANDOM_SIZE) + (context ? 2 + context_size : 0)]; uint8_t buf[4 * EAPTLS_MPPE_KEY_LEN]; p = seed; - memcpy(p, prf_label, prf_size); - p += prf_size; + memcpy(p, label, len); + p += len; memcpy(p, s->s3->client_random, SSL3_RANDOM_SIZE); p += SSL3_RANDOM_SIZE; - prf_size += SSL3_RANDOM_SIZE; + len += SSL3_RANDOM_SIZE; memcpy(p, s->s3->server_random, SSL3_RANDOM_SIZE); - prf_size += SSL3_RANDOM_SIZE; + p += SSL3_RANDOM_SIZE; + len += SSL3_RANDOM_SIZE; + + if (context) { + /* cloned and reversed FR_PUT_LE16 */ + p[0] = ((uint16_t) (context_size)) >> 8; + p[1] = ((uint16_t) (context_size)) & 0xff; + p += 2; + len += 2; + memcpy(p, context, context_size); + p += context_size; + len += context_size; + } PRF(s->session->master_key, s->session->master_key_length, - seed, prf_size, out, buf, sizeof(out)); + seed, len, out, buf, sizeof(out)); } #endif @@ -195,7 +269,7 @@ void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label) } -#define FR_TLS_PRF_CHALLENGE "ttls challenge" +#define FR_TLS_PRF_CHALLENGE "ttls challenge" /* * Generate the TTLS challenge @@ -206,9 +280,10 @@ void eaptls_gen_mppe_keys(REQUEST *request, SSL *s, char const *prf_label) void eapttls_gen_challenge(SSL *s, uint8_t *buffer, size_t size) { #if OPENSSL_VERSION_NUMBER >= 0x10001000L - SSL_export_keying_material(s, buffer, size, FR_TLS_PRF_CHALLENGE, - sizeof(FR_TLS_PRF_CHALLENGE) - 1, NULL, 0, 0); - + if (SSL_export_keying_material(s, buffer, size, FR_TLS_PRF_CHALLENGE, + sizeof(FR_TLS_PRF_CHALLENGE)-1, NULL, 0, 0) != 1) { + ERROR("Failed generating keying material"); + } #else uint8_t out[32], buf[32]; uint8_t seed[sizeof(FR_TLS_PRF_CHALLENGE)-1 + 2*SSL3_RANDOM_SIZE]; @@ -226,14 +301,20 @@ void eapttls_gen_challenge(SSL *s, uint8_t *buffer, size_t size) #endif } +#define FR_TLS_EXPORTER_METHOD_ID "EXPORTER_EAP_TLS_Method-Id" + /* * Actually generates EAP-Session-Id, which is an internal server * attribute. Not all systems want to send EAP-Key-Name. */ -void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *ssl, uint32_t header) +void eaptls_gen_eap_key(eap_handler_t *handler) { + RADIUS_PACKET *packet = handler->request->reply; + tls_session_t *tls_session = handler->opaque; + SSL *s = tls_session->ssl; VALUE_PAIR *vp; uint8_t *buff, *p; + uint8_t type = handler->type & 0xff; vp = fr_pair_afrom_num(packet, PW_EAP_SESSION_ID, 0); if (!vp) return; @@ -241,11 +322,33 @@ void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *ssl, uint32_t header) vp->vp_length = 1 + 2 * SSL3_RANDOM_SIZE; buff = p = talloc_array(vp, uint8_t, vp->vp_length); - *p++ = header & 0xff; + *p++ = type; - SSL_get_client_random(ssl, p, SSL3_RANDOM_SIZE); - p += SSL3_RANDOM_SIZE; - SSL_get_server_random(ssl, p, SSL3_RANDOM_SIZE); + switch (SSL_version(tls_session->ssl)) { + case TLS1_VERSION: + case TLS1_1_VERSION: + case TLS1_2_VERSION: + SSL_get_client_random(s, p, SSL3_RANDOM_SIZE); + p += SSL3_RANDOM_SIZE; + SSL_get_server_random(s, p, SSL3_RANDOM_SIZE); + break; +#if OPENSSL_VERSION_NUMBER >= 0x10100000L +#ifdef TLS1_3_VERSION + case TLS1_3_VERSION: +#endif + default: + { + uint8_t const context[] = { type }; + + if (SSL_export_keying_material(s, p, 2 * SSL3_RANDOM_SIZE, + FR_TLS_EXPORTER_METHOD_ID, sizeof(FR_TLS_EXPORTER_METHOD_ID)-1, + context, sizeof(context), 1) != 1) { + ERROR("Failed generating keying material"); + return; + } + } +#endif + } vp->vp_octets = buff; fr_pair_add(&packet->vps, vp); @@ -254,7 +357,7 @@ void eaptls_gen_eap_key(RADIUS_PACKET *packet, SSL *ssl, uint32_t header) /* * Same as before, but for EAP-FAST the order of {server,client}_random is flipped */ -void eap_fast_tls_gen_challenge(SSL *s, uint8_t *buffer, uint8_t *scratch, size_t size, char const *prf_label) +void eap_fast_tls_gen_challenge(SSL *s, int version, uint8_t *buffer, size_t size, char const *prf_label) { uint8_t *p; size_t len, master_key_len; @@ -273,7 +376,9 @@ void eap_fast_tls_gen_challenge(SSL *s, uint8_t *buffer, uint8_t *scratch, size_ p += SSL3_RANDOM_SIZE; master_key_len = SSL_SESSION_get_master_key(SSL_get_session(s), master_key, sizeof(master_key)); - PRF(master_key, master_key_len, seed, p - seed, buffer, scratch, size); -} - + if (version == TLS1_2_VERSION) + PRFv12(master_key, master_key_len, seed, p - seed, buffer, size); + else + PRF(master_key, master_key_len, seed, p - seed, buffer, size); +} diff --git a/src/modules/rlm_eap/radeapclient.c b/src/modules/rlm_eap/radeapclient.c index 553a6a6a57..cd504a8363 100644 --- a/src/modules/rlm_eap/radeapclient.c +++ b/src/modules/rlm_eap/radeapclient.c @@ -182,6 +182,14 @@ static void rc_get_port(PW_CODE type, uint16_t *port); static void rc_evprep_packet_timeout(rc_transaction_t *trans); static void rc_deallocate_id(rc_transaction_t *trans); +/* + * For cbtls_cache_*() + */ +rlm_rcode_t process_post_auth(UNUSED int postauth_type, UNUSED REQUEST *request) +{ + return RLM_MODULE_FAIL; +} + static void NEVER_RETURNS usage(void) { diff --git a/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c b/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c index b0953aa1d4..bbb5a03c95 100644 --- a/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c +++ b/src/modules/rlm_eap/types/rlm_eap_fast/eap_fast.c @@ -61,33 +61,51 @@ static int openssl_get_keyblock_size(REQUEST *request, SSL *ssl) return -1; RDEBUG2("OpenSSL: keyblock size: key_len=%d MD_size=%d " - "IV_len=%d", EVP_CIPHER_key_length(c), md_size, - EVP_CIPHER_iv_length(c)); + "IV_len=%d", EVP_CIPHER_key_length(c), md_size, + EVP_CIPHER_iv_length(c)); return 2 * (EVP_CIPHER_key_length(c) + md_size + EVP_CIPHER_iv_length(c)); #else const SSL_CIPHER *ssl_cipher; int cipher, digest; + int mac_key_len, enc_key_len, fixed_iv_len; ssl_cipher = SSL_get_current_cipher(ssl); if (!ssl_cipher) return -1; cipher = SSL_CIPHER_get_cipher_nid(ssl_cipher); digest = SSL_CIPHER_get_digest_nid(ssl_cipher); - RDEBUG2("OpenSSL: cipher nid %d digest nid %d", cipher, digest); + RDEBUG3("OpenSSL: cipher nid %d digest nid %d", + cipher, digest); if (cipher < 0 || digest < 0) return -1; + if (cipher == NID_undef) { + RDEBUG3("OpenSSL: no cipher in use?!"); + return -1; + } c = EVP_get_cipherbynid(cipher); - h = EVP_get_digestbynid(digest); - if (!c || !h) + if (!c) return -1; + enc_key_len = EVP_CIPHER_key_length(c); + if (EVP_CIPHER_mode(c) == EVP_CIPH_GCM_MODE || + EVP_CIPHER_mode(c) == EVP_CIPH_CCM_MODE) + fixed_iv_len = 4; /* only part of IV from PRF */ + else + fixed_iv_len = EVP_CIPHER_iv_length(c); + if (digest == NID_undef) { + RDEBUG3("OpenSSL: no digest in use (e.g., AEAD)"); + mac_key_len = 0; + } else { + h = EVP_get_digestbynid(digest); + if (!h) + return -1; + mac_key_len = EVP_MD_size(h); + } - RDEBUG2("OpenSSL: keyblock size: key_len=%d MD_size=%d IV_len=%d", - EVP_CIPHER_key_length(c), EVP_MD_size(h), - EVP_CIPHER_iv_length(c)); - return 2 * (EVP_CIPHER_key_length(c) + EVP_MD_size(h) + - EVP_CIPHER_iv_length(c)); + RDEBUG2("OpenSSL: keyblock size: mac_key_len=%d enc_key_len=%d fixed_iv_len=%d", + mac_key_len, enc_key_len, fixed_iv_len); + return 2 * (mac_key_len + enc_key_len + fixed_iv_len); #endif } @@ -98,7 +116,6 @@ static void eap_fast_init_keys(REQUEST *request, tls_session_t *tls_session) { eap_fast_tunnel_t *t = tls_session->opaque; uint8_t *buf; - uint8_t *scratch; size_t ksize; RDEBUG2("Deriving EAP-FAST keys"); @@ -108,11 +125,10 @@ static void eap_fast_init_keys(REQUEST *request, tls_session_t *tls_session) ksize = openssl_get_keyblock_size(request, tls_session->ssl); rad_assert(ksize > 0); buf = talloc_size(request, ksize + sizeof(*t->keyblock)); - scratch = talloc_size(request, ksize + sizeof(*t->keyblock)); t->keyblock = talloc(t, eap_fast_keyblock_t); - eap_fast_tls_gen_challenge(tls_session->ssl, buf, scratch, ksize + sizeof(*t->keyblock), "key expansion"); + eap_fast_tls_gen_challenge(tls_session->ssl, SSL_version(tls_session->ssl), buf, ksize + sizeof(*t->keyblock), "key expansion"); memcpy(t->keyblock, &buf[ksize], sizeof(*t->keyblock)); memset(buf, 0, ksize + sizeof(*t->keyblock)); @@ -123,7 +139,6 @@ static void eap_fast_init_keys(REQUEST *request, tls_session_t *tls_session) t->imckc = 0; talloc_free(buf); - talloc_free(scratch); } /** @@ -256,6 +271,7 @@ static void eap_fast_send_pac_tunnel(REQUEST *request, tls_session_t *tls_sessio dlen = eap_fast_encrypt((unsigned const char *)&opaque_plaintext, sizeof(opaque_plaintext), t->a_id, PAC_A_ID_LENGTH, t->pac_opaque_key, pac.opaque.iv, pac.opaque.data, pac.opaque.tag); + if (dlen < 0) return; pac.opaque.hdr.type = htons(EAP_FAST_TLV_MANDATORY | PAC_INFO_PAC_OPAQUE); pac.opaque.hdr.length = htons(sizeof(pac.opaque) - sizeof(pac.opaque.hdr) - sizeof(pac.opaque.data) + dlen); @@ -765,6 +781,7 @@ static rlm_rcode_t CC_HINT(nonnull) process_reply( eap_handler_t *eap_session, switch (reply->code) { case PW_CODE_ACCESS_ACCEPT: RDEBUG("Got tunneled Access-Accept"); + tls_session->authentication_success = true; rcode = RLM_MODULE_OK; for (vp = fr_cursor_init(&cursor, &reply->vps); vp; vp = fr_cursor_next(&cursor)) { @@ -1203,8 +1220,12 @@ PW_CODE eap_fast_process(eap_handler_t *eap_session, tls_session_t *tls_session) t->mode = EAP_FAST_PROVISIONING_AUTH; } - if (!t->pac.expires || t->pac.expired || t->pac.expires - time(NULL) < t->pac_lifetime * 0.6) + /* + * Send a new pac at ~0.6 times the lifetime. + */ + if (!t->pac.expires || t->pac.expired || t->pac.expires < (time(NULL) + (t->pac_lifetime >> 1) + (t->pac_lifetime >> 3))) { t->pac.send = true; + } } eap_fast_init_keys(request, tls_session); diff --git a/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c b/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c index 2ce2dd0c3b..093dc868cd 100644 --- a/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c +++ b/src/modules/rlm_eap/types/rlm_eap_fast/rlm_eap_fast.c @@ -131,6 +131,25 @@ static int mod_instantiate(CONF_SECTION *cs, void **instance) return -1; } +#ifdef TLS1_3_VERSION + if (inst->tls_conf->min_version == TLS1_3_VERSION) { + ERROR("There are no standards for using TLS 1.3 with EAP-FAST."); + ERROR("You MUST enable TLS 1.2 for EAP-FAST to work."); + return -1; + } + + if ((inst->tls_conf->max_version == TLS1_3_VERSION) || + (inst->tls_conf->min_version == TLS1_3_VERSION)) { + WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + WARN("!! There is no standard for using EAP-FAST with TLS 1.3"); + WARN("!! Please set tls_max_version = \"1.2\""); + WARN("!! FreeRADIUS only supports TLS 1.3 for special builds of wpa_supplicant and Windows"); + WARN("!! This limitation is likely to change in late 2021."); + WARN("!! If you are using this version of FreeRADIUS after 2021, you will probably need to upgrade"); + WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + } +#endif + rad_assert(PAC_A_ID_LENGTH == MD5_DIGEST_LENGTH); FR_MD5_CTX ctx; fr_md5_init(&ctx); @@ -241,7 +260,7 @@ static int _session_ticket(SSL *s, uint8_t const *data, int len, void *arg) DICT_ATTR const *fast_da; char const *errmsg; int dlen, plen; - uint16_t length; + int length; eap_fast_attr_pac_opaque_t const *opaque = (eap_fast_attr_pac_opaque_t const *) data; eap_fast_attr_pac_opaque_t opaque_plaintext; @@ -274,7 +293,7 @@ error: * so we have to use the length in the PAC-Opaque header */ length = ntohs(opaque->hdr.length); - if (len - sizeof(opaque->hdr) < length) { + if (len < (int) (length + sizeof(opaque->hdr))) { errmsg = "PAC has bad length in header"; goto error; } @@ -293,7 +312,7 @@ error: plen = eap_fast_decrypt(opaque->data, dlen, opaque->aad, PAC_A_ID_LENGTH, (uint8_t const *) opaque->tag, t->pac_opaque_key, opaque->iv, (uint8_t *)&opaque_plaintext); - if (plen == -1) { + if (plen < 0) { errmsg = "PAC failed to decrypt"; goto error; } @@ -314,8 +333,8 @@ error: break; case PAC_INFO_PAC_LIFETIME: rad_assert(t->pac.expires == 0); - t->pac.expires = vp->vp_integer; - t->pac.expired = (vp->vp_integer <= time(NULL)); + t->pac.expires = vp->vp_integer + time(NULL); + t->pac.expired = false; break; case PAC_INFO_PAC_KEY: rad_assert(t->pac.key == NULL); @@ -392,7 +411,7 @@ static int mod_process(void *arg, eap_handler_t *handler) if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); } else { - RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); + RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); } /* @@ -553,7 +572,13 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) } else { client_cert = inst->req_client_cert; } - handler->opaque = tls_session = eaptls_session(handler, inst->tls_conf, client_cert); + + /* + * Don't allow TLS 1.3 for us, even if it's allowed + * elsewhere. We haven't implemented the necessary + * changes, so we don't allow it. + */ + handler->opaque = tls_session = eaptls_session(handler, inst->tls_conf, client_cert, false); if (!tls_session) return 0; @@ -566,16 +591,20 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) } } -#ifdef SSL_OP_NO_TLSv1_2 - /* - * Forcibly disable TLSv1.2 - * - * @fixme - TLSv1.2 uses a different PRF and - * SSL_export_keying_material("key expansion") is - * forbidden - */ - SSL_set_options(tls_session->ssl, SSL_OP_NO_TLSv1_2); +#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) + { + int i; + for (i = 0; ; i++) { + const char *cipher = SSL_get_cipher_list(tls_session->ssl, i); + if (!cipher) break; + if (!strstr(cipher, "ADH-")) continue; + RDEBUG("Setting security level to 0 to allow anonymous cipher suites"); + SSL_set_security_level(tls_session->ssl, 0); + break; + } + } #endif + #ifdef SSL_OP_NO_TLSv1_3 /* * Forcibly disable TLSv1.3 @@ -599,6 +628,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) rcode = eap_fast_tls_start(handler->eap_ds, tls_session); if (rcode < 0) { + error: talloc_free(tls_session); return 0; } @@ -607,7 +637,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) if (!SSL_set_session_ticket_ext_cb(tls_session->ssl, _session_ticket, tls_session)) { RERROR("Failed setting SSL session ticket callback"); - return 0; + goto error; } handler->stage = PROCESS; diff --git a/src/modules/rlm_eap/types/rlm_eap_peap/peap.c b/src/modules/rlm_eap/types/rlm_eap_peap/peap.c index deaf702d61..5647f613af 100644 --- a/src/modules/rlm_eap/types/rlm_eap_peap/peap.c +++ b/src/modules/rlm_eap/types/rlm_eap_peap/peap.c @@ -442,6 +442,7 @@ static rlm_rcode_t CC_HINT(nonnull) process_reply(eap_handler_t *handler, tls_se switch (reply->code) { case PW_CODE_ACCESS_ACCEPT: RDEBUG2("Tunneled authentication was successful"); + tls_session->authentication_success = true; t->status = PEAP_STATUS_SENT_TLV_SUCCESS; eappeap_success(handler, tls_session); rcode = RLM_MODULE_HANDLED; @@ -790,6 +791,7 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session, /* send an identity request */ t->session_resumption_state = PEAP_RESUMPTION_NO; t->status = PEAP_STATUS_INNER_IDENTITY_REQ_SENT; + tls_session->session_not_resumed = true; eappeap_identity(handler, tls_session); } return RLM_MODULE_HANDLED; @@ -903,15 +905,15 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session, return RLM_MODULE_REJECT; - case PEAP_STATUS_PHASE2_INIT: - RDEBUG("In state machine in phase2 init?"); + case PEAP_STATUS_PHASE2_INIT: + RDEBUG("In state machine in phase2 init?"); - case PEAP_STATUS_PHASE2: - break; + case PEAP_STATUS_PHASE2: + break; - default: - REDEBUG("Unhandled state in peap"); - return RLM_MODULE_REJECT; + default: + REDEBUG("Unhandled state in peap"); + return RLM_MODULE_REJECT; } fake = request_alloc_fake(request); @@ -1040,6 +1042,10 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session, if (vp) { eap_tunnel_data_t *tunnel; + bool proxy_as_eap = t->proxy_tunneled_request_as_eap; + VALUE_PAIR *flag = fr_pair_find_by_num(fake->config, PW_PROXY_TUNNELED_REQUEST_AS_EAP, 0, TAG_ANY); + + if (flag) proxy_as_eap = flag->vp_integer; /* * The tunneled request was NOT handled, @@ -1056,7 +1062,7 @@ rlm_rcode_t eappeap_process(eap_handler_t *handler, tls_session_t *tls_session, * Once the tunneled EAP session is ALMOST * done, THEN we proxy it... */ - if (!t->proxy_tunneled_request_as_eap) { + if (!proxy_as_eap) { fake->options |= RAD_REQUEST_OPTION_PROXY_EAP; /* diff --git a/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c b/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c index 3d23322663..d9f850cef2 100644 --- a/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c +++ b/src/modules/rlm_eap/types/rlm_eap_peap/rlm_eap_peap.c @@ -192,7 +192,10 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) client_cert = inst->req_client_cert; } - ssn = eaptls_session(handler, inst->tls_conf, client_cert); + /* + * Allow TLS 1.3, it works. + */ + ssn = eaptls_session(handler, inst->tls_conf, client_cert, true); if (!ssn) { return 0; } @@ -200,9 +203,13 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) handler->opaque = ((void *)ssn); /* - * Set up type-specific information. + * Set the label to a fixed string. For TLS 1.3, the + * label is the same for all TLS-based EAP methods. If + * the client is using TLS 1.3, then eaptls_success() + * will over-ride this label with the correct label for + * TLS 1.3. */ - ssn->prf_label = "client EAP encryption"; + ssn->label = "client EAP encryption"; /* * As it is a poorly designed protocol, PEAP uses @@ -230,7 +237,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { REDEBUG("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); } else { - RDEBUG2("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); + RDEBUG3("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); } if (status == 0) return 0; @@ -274,7 +281,7 @@ static int mod_process(void *arg, eap_handler_t *handler) if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); } else { - RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); + RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); } /* @@ -311,6 +318,10 @@ static int mod_process(void *arg, eap_handler_t *handler) * data. */ case FR_TLS_OK: + /* + * TLSv1.3 makes application data immediately avaliable + */ + if (tls_session->is_init_finished && (peap->status == PEAP_STATUS_INVALID)) peap->status = PEAP_STATUS_TUNNEL_ESTABLISHED; break; /* diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h b/src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h new file mode 100644 index 0000000000..b717dd51b3 --- /dev/null +++ b/src/modules/rlm_eap/types/rlm_eap_pwd/const_time.h @@ -0,0 +1,190 @@ +/* + * Helper functions for constant time operations + * Copyright (c) 2019, The Linux Foundation + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + * + * These helper functions can be used to implement logic that needs to minimize + * externally visible differences in execution path by avoiding use of branches, + * avoiding early termination or other time differences, and forcing same memory + * access pattern regardless of values. + */ + +#ifndef CONST_TIME_H +#define CONST_TIME_H + + +#if defined(__clang__) +#define NO_UBSAN_UINT_OVERFLOW \ + __attribute__((no_sanitize("unsigned-integer-overflow"))) +#else +#define NO_UBSAN_UINT_OVERFLOW +#endif + +/** + * const_time_fill_msb - Fill all bits with MSB value + * @param val Input value + * @return Value with all the bits set to the MSB of the input val + */ +static inline unsigned int const_time_fill_msb(unsigned int val) +{ + /* Move the MSB to LSB and multiple by -1 to fill in all bits. */ + return (val >> (sizeof(val) * 8 - 1)) * ~0U; +} + + +/* @return -1 if val is zero; 0 if val is not zero */ +static inline unsigned int const_time_is_zero(unsigned int val) + NO_UBSAN_UINT_OVERFLOW +{ + /* Set MSB to 1 for 0 and fill rest of bits with the MSB value */ + return const_time_fill_msb(~val & (val - 1)); +} + + +/* @return -1 if a == b; 0 if a != b */ +static inline unsigned int const_time_eq(unsigned int a, unsigned int b) +{ + return const_time_is_zero(a ^ b); +} + + +/* @return -1 if a == b; 0 if a != b */ +static inline unsigned char const_time_eq_u8(unsigned int a, unsigned int b) +{ + return (unsigned char) const_time_eq(a, b); +} + + +/** + * const_time_eq_bin - Constant time memory comparison + * @param a First buffer to compare + * @param b Second buffer to compare + * @param len Number of octets to compare + * @return -1 if buffers are equal, 0 if not + * + * This function is meant for comparing passwords or hash values where + * difference in execution time or memory access pattern could provide external + * observer information about the location of the difference in the memory + * buffers. The return value does not behave like memcmp(), i.e., + * const_time_eq_bin() cannot be used to sort items into a defined order. Unlike + * memcmp(), the execution time of const_time_eq_bin() does not depend on the + * contents of the compared memory buffers, but only on the total compared + * length. + */ +static inline unsigned int const_time_eq_bin(const void *a, const void *b, + size_t len) +{ + const unsigned char *aa = a; + const unsigned char *bb = b; + size_t i; + unsigned char res = 0; + + for (i = 0; i < len; i++) + res |= aa[i] ^ bb[i]; + + return const_time_is_zero(res); +} + + +/** + * const_time_select - Constant time unsigned int selection + * @param mask 0 (false) or -1 (true) to identify which value to select + * @param true_val Value to select for the true case + * @param false_val Value to select for the false case + * @return true_val if mask == -1, false_val if mask == 0 + */ +static inline unsigned int const_time_select(unsigned int mask, + unsigned int true_val, + unsigned int false_val) +{ + return (mask & true_val) | (~mask & false_val); +} + + +/** + * const_time_select_int - Constant time int selection + * @param mask 0 (false) or -1 (true) to identify which value to select + * @param true_val Value to select for the true case + * @param false_val Value to select for the false case + * @return true_val if mask == -1, false_val if mask == 0 + */ +static inline int const_time_select_int(unsigned int mask, int true_val, + int false_val) +{ + return (int) const_time_select(mask, (unsigned int) true_val, + (unsigned int) false_val); +} + + +/** + * const_time_select_u8 - Constant time u8 selection + * @param mask 0 (false) or -1 (true) to identify which value to select + * @param true_val Value to select for the true case + * @param false_val Value to select for the false case + * @return true_val if mask == -1, false_val if mask == 0 + */ +static inline unsigned char const_time_select_u8(unsigned char mask, unsigned char true_val, unsigned char false_val) +{ + return (unsigned char) const_time_select(mask, true_val, false_val); +} + + +/** + * const_time_select_s8 - Constant time s8 selection + * @param mask 0 (false) or -1 (true) to identify which value to select + * @param true_val Value to select for the true case + * @param false_val Value to select for the false case + * @return true_val if mask == -1, false_val if mask == 0 + */ +static inline char const_time_select_s8(char mask, char true_val, char false_val) +{ + return (char) const_time_select(mask, (unsigned int) true_val, + (unsigned int) false_val); +} + + +/** + * const_time_select_bin - Constant time binary buffer selection copy + * @param mask 0 (false) or -1 (true) to identify which value to copy + * @param true_val Buffer to copy for the true case + * @param false_val Buffer to copy for the false case + * @param len Number of octets to copy + * @param dst Destination buffer for the copy + * + * This function copies the specified buffer into the destination buffer using + * operations with identical memory access pattern regardless of which buffer + * is being copied. + */ +static inline void const_time_select_bin(unsigned char mask, const unsigned char *true_val, + const unsigned char *false_val, size_t len, + unsigned char *dst) +{ + size_t i; + + for (i = 0; i < len; i++) + dst[i] = const_time_select_u8(mask, true_val[i], false_val[i]); +} + + +static inline int const_time_memcmp(const void *a, const void *b, size_t len) +{ + const unsigned char *aa = a; + const unsigned char *bb = b; + int diff, res = 0; + unsigned int mask; + + if (len == 0) + return 0; + do { + len--; + diff = (int) aa[len] - (int) bb[len]; + mask = const_time_is_zero((unsigned int) diff); + res = const_time_select_int(mask, res, diff); + } while (len); + + return res; +} + +#endif /* CONST_TIME_H */ diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c index d94851c3aa..26260527a5 100644 --- a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c +++ b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.c @@ -1,7 +1,5 @@ -/* - * Copyright (c) Dan Harkins, 2012 - * - * Copyright holder grants permission for redistribution and use in source +/** + * copyright holder grants permission for redistribution and use in source * and binary forms, with or without modification, provided that the * following conditions are met: * 1. Redistribution of source code must retain the above copyright @@ -29,100 +27,237 @@ * This license and distribution terms cannot be changed. In other words, * this code cannot simply be copied and put under a different distribution * license (including the GNU public license). + * + * @copyright (c) Dan Harkins, 2012 */ RCSID("$Id: d94851c3aa0fc31db9be2d01a4fb94c1a6c81e00 $") USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ #include "eap_pwd.h" +#include "const_time.h" +#include -#include -#include +static uint8_t allzero[SHA256_DIGEST_LENGTH] = { 0x00 }; /* The random function H(x) = HMAC-SHA256(0^32, x) */ -static void H_Init(HMAC_CTX *ctx) +static void pwd_hmac_final(HMAC_CTX *hmac_ctx, uint8_t *digest) { - uint8_t allzero[SHA256_DIGEST_LENGTH]; + unsigned int mdlen = SHA256_DIGEST_LENGTH; + HMAC_Final(hmac_ctx, digest, &mdlen); +// HMAC_CTX_reset(hmac_ctx); +} - memset(allzero, 0, SHA256_DIGEST_LENGTH); +/* a counter-based KDF based on NIST SP800-108 */ +static void eap_pwd_kdf(uint8_t *key, int keylen, char const *label, + int label_len, uint8_t *result, int result_bit_len) +{ + HMAC_CTX *hmac_ctx; + uint8_t digest[SHA256_DIGEST_LENGTH]; + uint16_t i, ctr, L; + int result_byte_len, len = 0; + unsigned int mdlen = SHA256_DIGEST_LENGTH; + uint8_t mask = 0xff; + + MEM(hmac_ctx = HMAC_CTX_new()); + result_byte_len = (result_bit_len + 7) / 8; + + ctr = 0; + L = htons(result_bit_len); + while (len < result_byte_len) { + ctr++; i = htons(ctr); - HMAC_Init_ex(ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL); + HMAC_Init_ex(hmac_ctx, key, keylen, EVP_sha256(), NULL); + if (ctr > 1) HMAC_Update(hmac_ctx, digest, mdlen); + HMAC_Update(hmac_ctx, (uint8_t *) &i, sizeof(uint16_t)); + HMAC_Update(hmac_ctx, (uint8_t const *)label, label_len); + HMAC_Update(hmac_ctx, (uint8_t *) &L, sizeof(uint16_t)); + HMAC_Final(hmac_ctx, digest, &mdlen); + if ((len + (int) mdlen) > result_byte_len) { + memcpy(result + len, digest, result_byte_len - len); + } else { + memcpy(result + len, digest, mdlen); + } + len += mdlen; +// HMAC_CTX_reset(hmac_ctx); + } + + /* since we're expanding to a bit length, mask off the excess */ + if (result_bit_len % 8) { + mask <<= (8 - (result_bit_len % 8)); + result[result_byte_len - 1] &= mask; + } + + HMAC_CTX_free(hmac_ctx); } -static void H_Update(HMAC_CTX *ctx, uint8_t const *data, int len) +static BIGNUM *consttime_BN (void) { - HMAC_Update(ctx, data, len); + BIGNUM *bn; + + bn = BN_new(); + if (bn) BN_set_flags(bn, BN_FLG_CONSTTIME); + return bn; } -static void H_Final(HMAC_CTX *ctx, uint8_t *digest) +/* + * compute the legendre symbol in constant time + */ +static int legendre(BIGNUM *a, BIGNUM *p, BN_CTX *bnctx) { - unsigned int mdlen = SHA256_DIGEST_LENGTH; + int symbol; + unsigned int mask; + BIGNUM *res, *pm1over2; + + pm1over2 = consttime_BN(); + res = consttime_BN(); + + if (!BN_sub(pm1over2, p, BN_value_one()) || + !BN_rshift1(pm1over2, pm1over2) || + !BN_mod_exp_mont_consttime(res, a, pm1over2, p, bnctx, NULL)) { + BN_free(pm1over2); + BN_free(res); + return -2; + } + + symbol = -1; + mask = const_time_eq(BN_is_word(res, 1), 1); + symbol = const_time_select_int(mask, 1, symbol); + mask = const_time_eq(BN_is_zero(res), 1); + symbol = const_time_select_int(mask, -1, symbol); + + BN_free(pm1over2); + BN_free(res); - HMAC_Final(ctx, digest, &mdlen); + return symbol; } -/* a counter-based KDF based on NIST SP800-108 */ -static int eap_pwd_kdf(uint8_t *key, int keylen, char const *label, int labellen, uint8_t *result, int resultbitlen) +static void do_equation(EC_GROUP *group, BIGNUM *y2, BIGNUM *x, BN_CTX *bnctx) { - HMAC_CTX *hctx = NULL; - uint8_t digest[SHA256_DIGEST_LENGTH]; - uint16_t i, ctr, L; - int resultbytelen, len = 0; - unsigned int mdlen = SHA256_DIGEST_LENGTH; - uint8_t mask = 0xff; + BIGNUM *p, *a, *b, *tmp1, *pm1; - hctx = HMAC_CTX_new(); - if (hctx == NULL) { - DEBUG("failed allocating HMAC context"); - return -1; + tmp1 = BN_new(); + pm1 = BN_new(); + p = BN_new(); + a = BN_new(); + b = BN_new(); + EC_GROUP_get_curve(group, p, a, b, bnctx); + + BN_sub(pm1, p, BN_value_one()); + + /* + * y2 = x^3 + ax + b + */ + BN_mod_sqr(tmp1, x, p, bnctx); + BN_mod_mul(y2, tmp1, x, p, bnctx); + BN_mod_mul(tmp1, a, x, p, bnctx); + BN_mod_add_quick(y2, y2, tmp1, p); + BN_mod_add_quick(y2, y2, b, p); + + BN_free(tmp1); + BN_free(pm1); + BN_free(p); + BN_free(a); + BN_free(b); + + return; +} + +static int is_quadratic_residue(BIGNUM *val, BIGNUM *p, BIGNUM *qr, BIGNUM *qnr, BN_CTX *bnctx) +{ + int offset, check, ret = 0; + BIGNUM *r = NULL, *pm1 = NULL, *res = NULL, *qr_or_qnr = NULL; + unsigned int mask; + unsigned char *qr_bin = NULL, *qnr_bin = NULL, *qr_or_qnr_bin = NULL; + + if (((r = consttime_BN()) == NULL) || + ((res = consttime_BN()) == NULL) || + ((qr_or_qnr = consttime_BN()) == NULL) || + ((pm1 = consttime_BN()) == NULL)) { + ret = -2; + goto fail; } - resultbytelen = (resultbitlen + 7)/8; - ctr = 0; - L = htons(resultbitlen); - while (len < resultbytelen) { - ctr++; i = htons(ctr); - HMAC_Init_ex(hctx, key, keylen, EVP_sha256(), NULL); - if (ctr > 1) { - HMAC_Update(hctx, digest, mdlen); - } - HMAC_Update(hctx, (uint8_t *) &i, sizeof(uint16_t)); - HMAC_Update(hctx, (uint8_t const *)label, labellen); - HMAC_Update(hctx, (uint8_t *) &L, sizeof(uint16_t)); - HMAC_Final(hctx, digest, &mdlen); - if ((len + (int) mdlen) > resultbytelen) { - memcpy(result + len, digest, resultbytelen - len); - } else { - memcpy(result + len, digest, mdlen); - } - len += mdlen; + + if (((qr_bin = (unsigned char *)malloc(BN_num_bytes(p))) == NULL) || + ((qnr_bin = (unsigned char *)malloc(BN_num_bytes(p))) == NULL) || + ((qr_or_qnr_bin = (unsigned char *)malloc(BN_num_bytes(p))) == NULL)) { + ret = -2; + goto fail; } - HMAC_CTX_free(hctx); - /* since we're expanding to a bit length, mask off the excess */ - if (resultbitlen % 8) { - mask <<= (8 - (resultbitlen % 8)); - result[resultbytelen - 1] &= mask; + /* + * we select binary in constant time so make them binary + */ + memset(qr_bin, 0, BN_num_bytes(p)); + memset(qnr_bin, 0, BN_num_bytes(p)); + memset(qr_or_qnr_bin, 0, BN_num_bytes(p)); + + offset = BN_num_bytes(p) - BN_num_bytes(qr); + BN_bn2bin(qr, qr_bin + offset); + + offset = BN_num_bytes(p) - BN_num_bytes(qnr); + BN_bn2bin(qnr, qnr_bin + offset); + + /* + * r = (random() mod p-1) + 1 + */ + BN_sub(pm1, p, BN_value_one()); + BN_rand_range(r, pm1); + BN_add(r, r, BN_value_one()); + + BN_copy(res, val); + + /* + * res = val * r * r which ensures res != val but has same quadratic residocity + */ + BN_mod_mul(res, res, r, p, bnctx); + BN_mod_mul(res, res, r, p, bnctx); + + /* + * if r is even (mask is -1) then multiply by qnr and our check is qnr + * otherwise multiply by qr and our check is qr + */ + mask = const_time_is_zero(BN_is_odd(r)); + const_time_select_bin(mask, qnr_bin, qr_bin, BN_num_bytes(p), qr_or_qnr_bin); + BN_bin2bn(qr_or_qnr_bin, BN_num_bytes(p), qr_or_qnr); + BN_mod_mul(res, res, qr_or_qnr, p, bnctx); + check = const_time_select_int(mask, -1, 1); + + if ((ret = legendre(res, p, bnctx)) == -2) { + ret = -1; /* just say no it's not */ + goto fail; } + mask = const_time_eq(ret, check); + ret = const_time_select_int(mask, 1, 0); - return 0; +fail: + if (qr_bin != NULL) free(qr_bin); + if (qnr_bin != NULL) free(qnr_bin); + if (qr_or_qnr_bin != NULL) free(qr_or_qnr_bin); + BN_free(r); + BN_free(res); + BN_free(qr_or_qnr); + BN_free(pm1); + + return ret; } -int compute_password_element (pwd_session_t *session, uint16_t grp_num, +int compute_password_element (REQUEST *request, pwd_session_t *session, uint16_t grp_num, char const *password, int password_len, char const *id_server, int id_server_len, char const *id_peer, int id_peer_len, uint32_t *token) { - BIGNUM *x_candidate = NULL, *rnd = NULL, *cofactor = NULL; - HMAC_CTX *ctx = NULL; - uint8_t pwe_digest[SHA256_DIGEST_LENGTH], *prfbuf = NULL, ctr; - int nid, is_odd, primebitlen, primebytelen, ret = 0; - - ctx = HMAC_CTX_new(); - if (ctx == NULL) { - DEBUG("failed allocating HMAC context"); - goto fail; - } + BIGNUM *x_candidate = NULL, *rnd = NULL, *y_sqrd = NULL, *qr = NULL, *qnr = NULL, *y1 = NULL, *y2 = NULL, *y = NULL, *exp = NULL; + EVP_MD_CTX *hmac_ctx; + EVP_PKEY *hmac_pkey; + uint8_t pwe_digest[SHA256_DIGEST_LENGTH], *prfbuf = NULL, *xbuf = NULL, *pm1buf = NULL, *y1buf = NULL, *y2buf = NULL, *ybuf = NULL, ctr; + int nid, is_odd, primebitlen, primebytelen, ret = 0, found = 0, mask; + int save, i, rbits, qr_or_qnr, save_is_odd = 0, cmp; + unsigned int skip; + + MEM(hmac_ctx = EVP_MD_CTX_new()); + MEM(hmac_pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, allzero, sizeof(allzero))); switch (grp_num) { /* from IANA registry for IKE D-H groups */ case 19: @@ -159,17 +294,23 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num, goto fail; } - if (((rnd = BN_new()) == NULL) || - ((cofactor = BN_new()) == NULL) || + if (((rnd = consttime_BN()) == NULL) || ((session->pwe = EC_POINT_new(session->group)) == NULL) || - ((session->order = BN_new()) == NULL) || - ((session->prime = BN_new()) == NULL) || - ((x_candidate = BN_new()) == NULL)) { + ((session->order = consttime_BN()) == NULL) || + ((session->prime = consttime_BN()) == NULL) || + ((qr = consttime_BN()) == NULL) || + ((qnr = consttime_BN()) == NULL) || + ((x_candidate = consttime_BN()) == NULL) || + ((y_sqrd = consttime_BN()) == NULL) || + ((y1 = consttime_BN()) == NULL) || + ((y2 = consttime_BN()) == NULL) || + ((y = consttime_BN()) == NULL) || + ((exp = consttime_BN()) == NULL)) { DEBUG("unable to create bignums"); goto fail; } - if (!EC_GROUP_get_curve_GFp(session->group, session->prime, NULL, NULL, NULL)) { + if (!EC_GROUP_get_curve(session->group, session->prime, NULL, NULL, NULL)) { DEBUG("unable to get prime for GFp curve"); goto fail; } @@ -179,46 +320,80 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num, goto fail; } - if (!EC_GROUP_get_cofactor(session->group, cofactor, NULL)) { - DEBUG("unable to get cofactor for curve"); - goto fail; - } - primebitlen = BN_num_bits(session->prime); primebytelen = BN_num_bytes(session->prime); if ((prfbuf = talloc_zero_array(session, uint8_t, primebytelen)) == NULL) { DEBUG("unable to alloc space for prf buffer"); goto fail; } + if ((xbuf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) { + DEBUG("unable to alloc space for x buffer"); + goto fail; + } + if ((pm1buf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) { + DEBUG("unable to alloc space for pm1 buffer"); + goto fail; + } + if ((y1buf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) { + DEBUG("unable to alloc space for y1 buffer"); + goto fail; + } + if ((y2buf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) { + DEBUG("unable to alloc space for y2 buffer"); + goto fail; + } + if ((ybuf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) { + DEBUG("unable to alloc space for y buffer"); + goto fail; + } + + + /* + * derive random quadradic residue and quadratic non-residue + */ + do { + BN_rand_range(qr, session->prime); + } while (legendre(qr, session->prime, session->bnctx) != 1); + + do { + BN_rand_range(qnr, session->prime); + } while (legendre(qnr, session->prime, session->bnctx) != -1); + + if (!BN_sub(rnd, session->prime, BN_value_one())) { + goto fail; + } + BN_bn2bin(rnd, pm1buf); + + save_is_odd = 0; + found = 0; + memset(xbuf, 0, primebytelen); ctr = 0; - while (1) { - if (ctr > 100) { - DEBUG("unable to find random point on curve for group %d, something's fishy", grp_num); - goto fail; - } + while (ctr < 40) { ctr++; /* * compute counter-mode password value and stretch to prime - * pwd-seed = H(token | peer-id | server-id | password | - * counter) + * pwd-seed = H(token | peer-id | server-id | password | + * counter) */ - H_Init(ctx); - H_Update(ctx, (uint8_t *)token, sizeof(*token)); - H_Update(ctx, (uint8_t const *)id_peer, id_peer_len); - H_Update(ctx, (uint8_t const *)id_server, id_server_len); - H_Update(ctx, (uint8_t const *)password, password_len); - H_Update(ctx, (uint8_t *)&ctr, sizeof(ctr)); - H_Final(ctx, pwe_digest); + EVP_DigestSignInit(hmac_ctx, NULL, EVP_sha256(), NULL, hmac_pkey); + EVP_DigestSignUpdate(hmac_ctx, (uint8_t *)token, sizeof(*token)); + EVP_DigestSignUpdate(hmac_ctx, (uint8_t const *)id_peer, id_peer_len); + EVP_DigestSignUpdate(hmac_ctx, (uint8_t const *)id_server, id_server_len); + EVP_DigestSignUpdate(hmac_ctx, (uint8_t const *)password, password_len); + EVP_DigestSignUpdate(hmac_ctx, (uint8_t *)&ctr, sizeof(ctr)); + + { + size_t mdlen = SHA256_DIGEST_LENGTH; + + EVP_DigestSignFinal(hmac_ctx, pwe_digest, &mdlen); + EVP_MD_CTX_reset(hmac_ctx); + } BN_bin2bn(pwe_digest, SHA256_DIGEST_LENGTH, rnd); - if (eap_pwd_kdf(pwe_digest, SHA256_DIGEST_LENGTH, "EAP-pwd Hunting And Pecking", - strlen("EAP-pwd Hunting And Pecking"), prfbuf, primebitlen) != 0) { - DEBUG("key derivation function failed"); - goto fail; - } + eap_pwd_kdf(pwe_digest, SHA256_DIGEST_LENGTH, "EAP-pwd Hunting And Pecking", + strlen("EAP-pwd Hunting And Pecking"), prfbuf, primebitlen); - BN_bin2bn(prfbuf, primebytelen, x_candidate); /* * eap_pwd_kdf() returns a string of bits 0..primebitlen but * BN_bin2bn will treat that string of bits as a big endian @@ -226,49 +401,86 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num, * then excessive bits-- those _after_ primebitlen-- so now * we have to shift right the amount we masked off. */ - if (primebitlen % 8) BN_rshift(x_candidate, x_candidate, (8 - (primebitlen % 8))); - if (BN_ucmp(x_candidate, session->prime) >= 0) continue; + if (primebitlen % 8) { + rbits = 8 - (primebitlen % 8); + for (i = primebytelen - 1; i > 0; i--) { + prfbuf[i] = (prfbuf[i - 1] << (8 - rbits)) | (prfbuf[i] >> rbits); + } + prfbuf[0] >>= rbits; + } + BN_bin2bn(prfbuf, primebytelen, x_candidate); /* - * need to unambiguously identify the solution, if there is - * one... - */ - is_odd = BN_is_odd(rnd) ? 1 : 0; + * it would've been better if the spec reduced the candidate + * modulo the prime but it didn't. So if the candidate >= prime + * we need to skip it but still run through the operations below + */ + cmp = const_time_memcmp(pm1buf, prfbuf, primebytelen); + skip = const_time_fill_msb((unsigned int)cmp); /* - * solve the quadratic equation, if it's not solvable then we - * don't have a point - */ - if (!EC_POINT_set_compressed_coordinates_GFp(session->group, session->pwe, x_candidate, is_odd, NULL)) { - continue; - } + * need to unambiguously identify the solution, if there is + * one.. + */ + is_odd = BN_is_odd(rnd); /* - * If there's a solution to the equation then the point must be - * on the curve so why check again explicitly? OpenSSL code - * says this is required by X9.62. We're not X9.62 but it can't - * hurt just to be sure. - */ - if (!EC_POINT_is_on_curve(session->group, session->pwe, NULL)) { - DEBUG("EAP-pwd: point is not on curve"); - continue; - } + * check whether x^3 + a*x + b is a quadratic residue + * + * save the first quadratic residue we find in the loop but do + * it in constant time. + */ + do_equation(session->group, y_sqrd, x_candidate, session->bnctx); + qr_or_qnr = is_quadratic_residue(y_sqrd, session->prime, qr, qnr, session->bnctx); - if (BN_cmp(cofactor, BN_value_one())) { - /* make sure the point is not in a small sub-group */ - if (!EC_POINT_mul(session->group, session->pwe, NULL, session->pwe, - cofactor, NULL)) { - DEBUG("EAP-pwd: cannot multiply generator by order"); - continue; - } + /* + * if the candidate >= prime then we want to skip it + */ + qr_or_qnr = const_time_select(skip, 0, qr_or_qnr); - if (EC_POINT_is_at_infinity(session->group, session->pwe)) { - DEBUG("EAP-pwd: point is at infinity"); - continue; - } - } - /* if we got here then we have a new generator. */ - break; + /* + * if we haven't found PWE yet (found = 0) then mask will be true, + * if we have found PWE then mask will be false + */ + mask = const_time_select(found, 0, -1); + + /* + * save will be 1 if we want to save this value-- i.e. we haven't + * found PWE yet and this is a quadratic residue-- and 0 otherwise + */ + save = const_time_select(mask, qr_or_qnr, 0); + + /* + * mask will be true (-1) if we want to save this and false (0) + * otherwise + */ + mask = const_time_eq(save, 1); + + const_time_select_bin(mask, prfbuf, xbuf, primebytelen, xbuf); + save_is_odd = const_time_select(mask, is_odd, save_is_odd); + found = const_time_select(mask, -1, found); + } + + /* + * now we can savely construct PWE + */ + BN_bin2bn(xbuf, primebytelen, x_candidate); + do_equation(session->group, y_sqrd, x_candidate, session->bnctx); + if ( !BN_add(exp, session->prime, BN_value_one()) || + !BN_rshift(exp, exp, 2) || + !BN_mod_exp_mont_consttime(y1, y_sqrd, exp, session->prime, session->bnctx, NULL) || + !BN_sub(y2, session->prime, y1) || + !BN_bn2bin(y1, y1buf) || + !BN_bn2bin(y2, y2buf)) { + DEBUG("unable to compute y"); + goto fail; + } + mask = const_time_eq(save_is_odd, BN_is_odd(y1)); + const_time_select_bin(mask, y1buf, y2buf, primebytelen, ybuf); + if (BN_bin2bn(ybuf, primebytelen, y) == NULL || + !EC_POINT_set_affine_coordinates(session->group, session->pwe, x_candidate, y, session->bnctx)) { + DEBUG("unable to set point coordinate"); + goto fail; } session->group_num = grp_num; @@ -278,78 +490,89 @@ int compute_password_element (pwd_session_t *session, uint16_t grp_num, } /* cleanliness and order.... */ - BN_clear_free(cofactor); BN_clear_free(x_candidate); + BN_clear_free(y_sqrd); + BN_clear_free(qr); + BN_clear_free(qnr); BN_clear_free(rnd); - talloc_free(prfbuf); - HMAC_CTX_free(ctx); + BN_clear_free(y1); + BN_clear_free(y2); + BN_clear_free(y); + BN_clear_free(exp); + + if (prfbuf) talloc_free(prfbuf); + if (xbuf) talloc_free(xbuf); + if (pm1buf) talloc_free(pm1buf); + if (y1buf) talloc_free(y1buf); + if (y2buf) talloc_free(y2buf); + if (ybuf) talloc_free(ybuf); + + EVP_MD_CTX_free(hmac_ctx); + EVP_PKEY_free(hmac_pkey); return ret; } -int compute_scalar_element (pwd_session_t *session, BN_CTX *bnctx) { +int compute_scalar_element(REQUEST *request, pwd_session_t *session, BN_CTX *bn_ctx) +{ BIGNUM *mask = NULL; int ret = -1; - if (((session->private_value = BN_new()) == NULL) || - ((session->my_element = EC_POINT_new(session->group)) == NULL) || - ((session->my_scalar = BN_new()) == NULL) || - ((mask = BN_new()) == NULL)) { - DEBUG2("server scalar allocation failed"); - goto fail; - } + MEM(session->private_value = BN_new()); + MEM(session->my_element = EC_POINT_new(session->group)); + MEM(session->my_scalar = BN_new()); + + MEM(mask = BN_new()); if (BN_rand_range(session->private_value, session->order) != 1) { - DEBUG2("Unable to get randomness for private_value"); - goto fail; + REDEBUG("Unable to get randomness for private_value"); + goto error; } if (BN_rand_range(mask, session->order) != 1) { - DEBUG2("Unable to get randomness for mask"); - goto fail; + REDEBUG("Unable to get randomness for mask"); + goto error; } BN_add(session->my_scalar, session->private_value, mask); - BN_mod(session->my_scalar, session->my_scalar, session->order, bnctx); + BN_mod(session->my_scalar, session->my_scalar, session->order, bn_ctx); - if (!EC_POINT_mul(session->group, session->my_element, NULL, session->pwe, mask, bnctx)) { - DEBUG2("server element allocation failed"); - goto fail; + if (!EC_POINT_mul(session->group, session->my_element, NULL, session->pwe, mask, bn_ctx)) { + REDEBUG("Server element allocation failed"); + goto error; } - if (!EC_POINT_invert(session->group, session->my_element, bnctx)) { - DEBUG2("server element inversion failed"); - goto fail; + if (!EC_POINT_invert(session->group, session->my_element, bn_ctx)) { + REDEBUG("Server element inversion failed"); + goto error; } ret = 0; -fail: +error: BN_clear_free(mask); return ret; } -int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_CTX *bnctx) +int process_peer_commit(REQUEST *request, pwd_session_t *session, uint8_t *in, size_t in_len, BN_CTX *bn_ctx) { - uint8_t *ptr; - size_t data_len; - BIGNUM *x = NULL, *y = NULL, *cofactor = NULL; - EC_POINT *K = NULL, *point = NULL; - int res = 1; - - if (((session->peer_scalar = BN_new()) == NULL) || - ((session->k = BN_new()) == NULL) || - ((cofactor = BN_new()) == NULL) || - ((x = BN_new()) == NULL) || - ((y = BN_new()) == NULL) || - ((point = EC_POINT_new(session->group)) == NULL) || - ((K = EC_POINT_new(session->group)) == NULL) || - ((session->peer_element = EC_POINT_new(session->group)) == NULL)) { - DEBUG2("pwd: failed to allocate room to process peer's commit"); - goto finish; - } + uint8_t *ptr; + size_t data_len; + BIGNUM *x = NULL, *y = NULL, *cofactor = NULL; + EC_POINT *K = NULL, *point = NULL; + int ret = 1; + + MEM(session->peer_scalar = BN_new()); + MEM(session->k = BN_new()); + MEM(session->peer_element = EC_POINT_new(session->group)); + MEM(point = EC_POINT_new(session->group)); + MEM(K = EC_POINT_new(session->group)); + + MEM(cofactor = BN_new()); + MEM(x = BN_new()); + MEM(y = BN_new()); if (!EC_GROUP_get_cofactor(session->group, cofactor, NULL)) { - DEBUG2("pwd: unable to get group co-factor"); + REDEBUG("Unable to get group co-factor"); goto finish; } @@ -361,7 +584,7 @@ int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_ * Did the peer send enough data? */ if (in_len < (2 * data_len + BN_num_bytes(session->order))) { - DEBUG("pwd: Invalid commit packet"); + REDEBUG("Invalid commit packet"); goto finish; } @@ -377,54 +600,54 @@ int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_ if (BN_is_zero(session->peer_scalar) || BN_is_one(session->peer_scalar) || BN_cmp(session->peer_scalar, session->order) >= 0) { - ERROR("Peer's scalar is not within the allowed range"); + REDEBUG("Peer's scalar is not within the allowed range"); goto finish; } - if (!EC_POINT_set_affine_coordinates_GFp(session->group, session->peer_element, x, y, bnctx)) { - DEBUG2("pwd: unable to get coordinates of peer's element"); + if (!EC_POINT_set_affine_coordinates(session->group, session->peer_element, x, y, bn_ctx)) { + REDEBUG("Unable to get coordinates of peer's element"); goto finish; } /* validate received element */ - if (!EC_POINT_is_on_curve(session->group, session->peer_element, bnctx) || + if (!EC_POINT_is_on_curve(session->group, session->peer_element, bn_ctx) || EC_POINT_is_at_infinity(session->group, session->peer_element)) { - ERROR("Peer's element is not a point on the elliptic curve"); + REDEBUG("Peer's element is not a point on the elliptic curve"); goto finish; } /* check to ensure peer's element is not in a small sub-group */ if (BN_cmp(cofactor, BN_value_one())) { if (!EC_POINT_mul(session->group, point, NULL, session->peer_element, cofactor, NULL)) { - DEBUG2("pwd: unable to multiply element by co-factor"); + REDEBUG("Unable to multiply element by co-factor"); goto finish; } if (EC_POINT_is_at_infinity(session->group, point)) { - DEBUG2("pwd: peer's element is in small sub-group"); + REDEBUG("Peer's element is in small sub-group"); goto finish; } } /* detect reflection attacks */ if (BN_cmp(session->peer_scalar, session->my_scalar) == 0 || - EC_POINT_cmp(session->group, session->peer_element, session->my_element, bnctx) == 0) { - ERROR("Reflection attack detected"); + EC_POINT_cmp(session->group, session->peer_element, session->my_element, bn_ctx) == 0) { + REDEBUG("Reflection attack detected"); goto finish; } /* compute the shared key, k */ - if ((!EC_POINT_mul(session->group, K, NULL, session->pwe, session->peer_scalar, bnctx)) || - (!EC_POINT_add(session->group, K, K, session->peer_element, bnctx)) || - (!EC_POINT_mul(session->group, K, NULL, K, session->private_value, bnctx))) { - DEBUG2("pwd: unable to compute shared key, k"); + if ((!EC_POINT_mul(session->group, K, NULL, session->pwe, session->peer_scalar, bn_ctx)) || + (!EC_POINT_add(session->group, K, K, session->peer_element, bn_ctx)) || + (!EC_POINT_mul(session->group, K, NULL, K, session->private_value, bn_ctx))) { + REDEBUG("Unable to compute shared key, k"); goto finish; } /* ensure that the shared key isn't in a small sub-group */ if (BN_cmp(cofactor, BN_value_one())) { if (!EC_POINT_mul(session->group, K, NULL, K, cofactor, NULL)) { - DEBUG2("pwd: unable to multiply k by co-factor"); + REDEBUG("Unable to multiply k by co-factor"); goto finish; } } @@ -436,15 +659,15 @@ int process_peer_commit (pwd_session_t *session, uint8_t *in, size_t in_len, BN_ * sure" so let's be safe. */ if (EC_POINT_is_at_infinity(session->group, K)) { - DEBUG2("pwd: k is point-at-infinity!"); + REDEBUG("K is point-at-infinity"); goto finish; } - if (!EC_POINT_get_affine_coordinates_GFp(session->group, K, session->k, NULL, bnctx)) { - DEBUG2("pwd: unable to get shared secret from K"); + if (!EC_POINT_get_affine_coordinates(session->group, K, session->k, NULL, bn_ctx)) { + REDEBUG("Unable to get shared secret from K"); goto finish; } - res = 0; + ret = 0; finish: EC_POINT_clear_free(K); @@ -453,36 +676,29 @@ finish: BN_clear_free(x); BN_clear_free(y); - return res; + return ret; } -int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) +int compute_server_confirm(REQUEST *request, pwd_session_t *session, uint8_t *out, BN_CTX *bn_ctx) { - BIGNUM *x = NULL, *y = NULL; - HMAC_CTX *ctx = NULL; - uint8_t *cruft = NULL; - int offset, req = -1; - - ctx = HMAC_CTX_new(); - if (ctx == NULL) { - DEBUG2("pwd: unable to allocate HMAC context!"); - goto finish; - } + BIGNUM *x = NULL, *y = NULL; + HMAC_CTX *hmac_ctx = NULL; + uint8_t *cruft = NULL; + int offset, req = -1; /* * Each component of the cruft will be at most as big as the prime */ - if (((cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime))) == NULL) || - ((x = BN_new()) == NULL) || ((y = BN_new()) == NULL)) { - DEBUG2("pwd: unable to allocate space to compute confirm!"); - goto finish; - } + MEM(cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime))); + MEM(x = BN_new()); + MEM(y = BN_new()); /* * commit is H(k | server_element | server_scalar | peer_element | * peer_scalar | ciphersuite) */ - H_Init(ctx); + MEM(hmac_ctx = HMAC_CTX_new()); + HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL); /* * Zero the memory each time because this is mod prime math and some @@ -492,24 +708,24 @@ int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) */ offset = BN_num_bytes(session->prime) - BN_num_bytes(session->k); BN_bn2bin(session->k, cruft + offset); - H_Update(ctx, cruft, BN_num_bytes(session->prime)); + HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); /* * next is server element: x, y */ - if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->my_element, x, y, bnctx)) { - DEBUG2("pwd: unable to get coordinates of server element"); + if (!EC_POINT_get_affine_coordinates(session->group, session->my_element, x, y, bn_ctx)) { + REDEBUG("Unable to get coordinates of server element"); goto finish; } memset(cruft, 0, BN_num_bytes(session->prime)); offset = BN_num_bytes(session->prime) - BN_num_bytes(x); BN_bn2bin(x, cruft + offset); - H_Update(ctx, cruft, BN_num_bytes(session->prime)); + HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); memset(cruft, 0, BN_num_bytes(session->prime)); offset = BN_num_bytes(session->prime) - BN_num_bytes(y); BN_bn2bin(y, cruft + offset); - H_Update(ctx, cruft, BN_num_bytes(session->prime)); + HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); /* * and server scalar @@ -517,25 +733,25 @@ int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) memset(cruft, 0, BN_num_bytes(session->prime)); offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar); BN_bn2bin(session->my_scalar, cruft + offset); - H_Update(ctx, cruft, BN_num_bytes(session->order)); + HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); /* * next is peer element: x, y */ - if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->peer_element, x, y, bnctx)) { - DEBUG2("pwd: unable to get coordinates of peer's element"); + if (!EC_POINT_get_affine_coordinates(session->group, session->peer_element, x, y, bn_ctx)) { + REDEBUG("Unable to get coordinates of peer's element"); goto finish; } memset(cruft, 0, BN_num_bytes(session->prime)); offset = BN_num_bytes(session->prime) - BN_num_bytes(x); BN_bn2bin(x, cruft + offset); - H_Update(ctx, cruft, BN_num_bytes(session->prime)); + HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); memset(cruft, 0, BN_num_bytes(session->prime)); offset = BN_num_bytes(session->prime) - BN_num_bytes(y); BN_bn2bin(y, cruft + offset); - H_Update(ctx, cruft, BN_num_bytes(session->prime)); + HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); /* * and peer scalar @@ -543,52 +759,46 @@ int compute_server_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) memset(cruft, 0, BN_num_bytes(session->prime)); offset = BN_num_bytes(session->order) - BN_num_bytes(session->peer_scalar); BN_bn2bin(session->peer_scalar, cruft + offset); - H_Update(ctx, cruft, BN_num_bytes(session->order)); + HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); /* * finally, ciphersuite */ - H_Update(ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); + HMAC_Update(hmac_ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); - H_Final(ctx, out); + pwd_hmac_final(hmac_ctx, out); req = 0; + finish: + HMAC_CTX_free(hmac_ctx); talloc_free(cruft); BN_free(x); BN_free(y); - HMAC_CTX_free(ctx); return req; } -int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) +int compute_peer_confirm(REQUEST *request, pwd_session_t *session, uint8_t *out, BN_CTX *bn_ctx) { - BIGNUM *x = NULL, *y = NULL; - HMAC_CTX *ctx = NULL; - uint8_t *cruft = NULL; - int offset, req = -1; - - ctx = HMAC_CTX_new(); - if (ctx == NULL) { - DEBUG2("pwd: unable to allocate HMAC context!"); - goto finish; - } + BIGNUM *x = NULL, *y = NULL; + HMAC_CTX *hmac_ctx = NULL; + uint8_t *cruft = NULL; + int offset, req = -1; /* * Each component of the cruft will be at most as big as the prime */ - if (((cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime))) == NULL) || - ((x = BN_new()) == NULL) || ((y = BN_new()) == NULL)) { - DEBUG2("pwd: unable to allocate space to compute confirm!"); - goto finish; - } + MEM(cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime))); + MEM(x = BN_new()); + MEM(y = BN_new()); /* * commit is H(k | server_element | server_scalar | peer_element | * peer_scalar | ciphersuite) */ - H_Init(ctx); + MEM(hmac_ctx = HMAC_CTX_new()); + HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL); /* * Zero the memory each time because this is mod prime math and some @@ -598,25 +808,25 @@ int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) */ offset = BN_num_bytes(session->prime) - BN_num_bytes(session->k); BN_bn2bin(session->k, cruft + offset); - H_Update(ctx, cruft, BN_num_bytes(session->prime)); + HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); /* * then peer element: x, y */ - if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->peer_element, x, y, bnctx)) { - DEBUG2("pwd: unable to get coordinates of peer's element"); + if (!EC_POINT_get_affine_coordinates(session->group, session->peer_element, x, y, bn_ctx)) { + REDEBUG("Unable to get coordinates of peer's element"); goto finish; } memset(cruft, 0, BN_num_bytes(session->prime)); offset = BN_num_bytes(session->prime) - BN_num_bytes(x); BN_bn2bin(x, cruft + offset); - H_Update(ctx, cruft, BN_num_bytes(session->prime)); + HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); memset(cruft, 0, BN_num_bytes(session->prime)); offset = BN_num_bytes(session->prime) - BN_num_bytes(y); BN_bn2bin(y, cruft + offset); - H_Update(ctx, cruft, BN_num_bytes(session->prime)); + HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); /* * and peer scalar @@ -624,24 +834,24 @@ int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) memset(cruft, 0, BN_num_bytes(session->prime)); offset = BN_num_bytes(session->order) - BN_num_bytes(session->peer_scalar); BN_bn2bin(session->peer_scalar, cruft + offset); - H_Update(ctx, cruft, BN_num_bytes(session->order)); + HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); /* * then server element: x, y */ - if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->my_element, x, y, bnctx)) { - DEBUG2("pwd: unable to get coordinates of server element"); + if (!EC_POINT_get_affine_coordinates(session->group, session->my_element, x, y, bn_ctx)) { + REDEBUG("Unable to get coordinates of server element"); goto finish; } memset(cruft, 0, BN_num_bytes(session->prime)); offset = BN_num_bytes(session->prime) - BN_num_bytes(x); BN_bn2bin(x, cruft + offset); - H_Update(ctx, cruft, BN_num_bytes(session->prime)); + HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); memset(cruft, 0, BN_num_bytes(session->prime)); offset = BN_num_bytes(session->prime) - BN_num_bytes(y); BN_bn2bin(y, cruft + offset); - H_Update(ctx, cruft, BN_num_bytes(session->prime)); + HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); /* * and server scalar @@ -649,94 +859,75 @@ int compute_peer_confirm (pwd_session_t *session, uint8_t *out, BN_CTX *bnctx) memset(cruft, 0, BN_num_bytes(session->prime)); offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar); BN_bn2bin(session->my_scalar, cruft + offset); - H_Update(ctx, cruft, BN_num_bytes(session->order)); + HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); /* * finally, ciphersuite */ - H_Update(ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); + HMAC_Update(hmac_ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); - H_Final(ctx, out); + pwd_hmac_final(hmac_ctx, out); req = 0; finish: + HMAC_CTX_free(hmac_ctx); talloc_free(cruft); BN_free(x); BN_free(y); - HMAC_CTX_free(ctx); return req; } -int compute_keys (pwd_session_t *session, uint8_t *peer_confirm, uint8_t *msk, uint8_t *emsk) +int compute_keys(UNUSED REQUEST *request, pwd_session_t *session, uint8_t *peer_confirm, uint8_t *msk, uint8_t *emsk) { - HMAC_CTX *ctx = NULL; - uint8_t mk[SHA256_DIGEST_LENGTH], *cruft = NULL; - uint8_t session_id[SHA256_DIGEST_LENGTH + 1]; - uint8_t msk_emsk[128]; /* 64 each */ - int offset, ret = -1; - - ctx = HMAC_CTX_new(); - if (ctx == NULL) { - DEBUG2("pwd: unable to allocate HMAC context!"); - goto finish; - } + HMAC_CTX *hmac_ctx; + uint8_t mk[SHA256_DIGEST_LENGTH], *cruft; + uint8_t session_id[SHA256_DIGEST_LENGTH + 1]; + uint8_t msk_emsk[128]; /* 64 each */ + int offset; - if ((cruft = talloc_array(session, uint8_t, BN_num_bytes(session->prime))) == NULL) { - DEBUG2("pwd: unable to allocate space to compute keys"); - goto finish; - } + MEM(cruft = talloc_array(session, uint8_t, BN_num_bytes(session->prime))); + MEM(hmac_ctx = HMAC_CTX_new()); /* * first compute the session-id = TypeCode | H(ciphersuite | scal_p | * scal_s) */ session_id[0] = PW_EAP_PWD; - H_Init(ctx); - H_Update(ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); + HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL); + HMAC_Update(hmac_ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite)); offset = BN_num_bytes(session->order) - BN_num_bytes(session->peer_scalar); memset(cruft, 0, BN_num_bytes(session->prime)); BN_bn2bin(session->peer_scalar, cruft + offset); - H_Update(ctx, cruft, BN_num_bytes(session->order)); + HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar); memset(cruft, 0, BN_num_bytes(session->prime)); BN_bn2bin(session->my_scalar, cruft + offset); - H_Update(ctx, cruft, BN_num_bytes(session->order)); - H_Final(ctx, (uint8_t *)&session_id[1]); + HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order)); + pwd_hmac_final(hmac_ctx, (uint8_t *)&session_id[1]); /* then compute MK = H(k | commit-peer | commit-server) */ - H_Init(ctx); + HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL); memset(cruft, 0, BN_num_bytes(session->prime)); offset = BN_num_bytes(session->prime) - BN_num_bytes(session->k); BN_bn2bin(session->k, cruft + offset); - H_Update(ctx, cruft, BN_num_bytes(session->prime)); + HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime)); - H_Update(ctx, peer_confirm, SHA256_DIGEST_LENGTH); + HMAC_Update(hmac_ctx, peer_confirm, SHA256_DIGEST_LENGTH); - H_Update(ctx, session->my_confirm, SHA256_DIGEST_LENGTH); + HMAC_Update(hmac_ctx, session->my_confirm, SHA256_DIGEST_LENGTH); - H_Final(ctx, mk); + pwd_hmac_final(hmac_ctx, mk); /* stretch the mk with the session-id to get MSK | EMSK */ - if (eap_pwd_kdf(mk, SHA256_DIGEST_LENGTH, (char const *)session_id, - SHA256_DIGEST_LENGTH + 1, msk_emsk, - /* it's bits, ((64 + 64) * 8) */ - 1024) != 0) { - DEBUG("key derivation function failed"); - goto finish; - } + eap_pwd_kdf(mk, SHA256_DIGEST_LENGTH, (char const *)session_id, + SHA256_DIGEST_LENGTH + 1, msk_emsk, 1024); /* it's bits, ((64 + 64) * 8) */ memcpy(msk, msk_emsk, 64); memcpy(emsk, msk_emsk + 64, 64); - ret = 0; -finish: + HMAC_CTX_free(hmac_ctx); talloc_free(cruft); - HMAC_CTX_free(ctx); - return ret; + return 0; } - - - - diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h index ca12778f61..a40a346069 100644 --- a/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h +++ b/src/modules/rlm_eap/types/rlm_eap_pwd/eap_pwd.h @@ -102,18 +102,22 @@ typedef struct _pwd_session_t { EC_POINT *my_element; EC_POINT *peer_element; uint8_t my_confirm[SHA256_DIGEST_LENGTH]; + uint8_t prep; + uint8_t salt_present; + uint8_t salt_len; + uint8_t salt[255]; } pwd_session_t; -int compute_password_element(pwd_session_t *sess, uint16_t grp_num, +int compute_password_element(REQUEST *request, pwd_session_t *sess, uint16_t grp_num, char const *password, int password_len, char const *id_server, int id_server_len, char const *id_peer, int id_peer_len, uint32_t *token); -int compute_scalar_element(pwd_session_t *sess, BN_CTX *bnctx); -int process_peer_commit (pwd_session_t *sess, uint8_t *in, size_t in_len, BN_CTX *bnctx); -int compute_server_confirm(pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx); -int compute_peer_confirm(pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx); -int compute_keys(pwd_session_t *sess, uint8_t *peer_confirm, +int compute_scalar_element(REQUEST *request, pwd_session_t *sess, BN_CTX *bnctx); +int process_peer_commit(REQUEST *request, pwd_session_t *sess, uint8_t *in, size_t in_len, BN_CTX *bnctx); +int compute_server_confirm(REQUEST *request, pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx); +int compute_peer_confirm(REQUEST *request, pwd_session_t *sess, uint8_t *out, BN_CTX *bnctx); +int compute_keys(REQUEST *request, pwd_session_t *sess, uint8_t *peer_confirm, uint8_t *msk, uint8_t *emsk); #ifdef PRINTBUF void print_buf(char *str, uint8_t *buf, int len); diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c index 18ab97f148..4992a2aeef 100644 --- a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c +++ b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.c @@ -41,11 +41,93 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ #define MPPE_KEY_LEN 32 #define MSK_EMSK_LEN (2*MPPE_KEY_LEN) +/* EAP-PWD can use different preprocessing (prep) modes to mangle the password + * before proving to both parties that they both know the same (mangled) password. + * + * The server advertises a preprocessing mode to the client. Only "none" is + * mandatory to implement. + * + * What is a good selection on the preprocessing mode? + * + * a) the server uses a hashed password + * b) the client uses a hashed password + * + * a | b | result + * --+---+--------------------------------------- + * n | n | none + * n | y | hint needed (cannot know automatically) + * y | n | select by hash given + * y | y | only works if both have the same hash; select by hash given + * + * Which hash functions does the server or client need to implement? + * + * a | b | server | client + * --+---+------------------------+---------------------- + * n | n | none | none + * n | y | as configured | none + * y | n | none | as selected by server + * y | y | none | none + * + * RFC 5931 defines 3 and RFC 8146 another 8 hash functions to implement. + * Can we avoid implementing them all? Only if they are provided as hash by some + * other module, e.g. in SQL or statically in password database. + * + * Therefore we select the preprocessing mode by the type of password given if + * in automatic mode: + * a) Cleartext-Password or User-Password: None. + * If the client only supports a hash (e.g. on Windows it might only have an + * NT-Password), do not provide a Cleartext-Password attribute but instead + * preprocess the password externally (e.g. hash the Cleartext-Password + * into an NT-Password and drop the Cleartext-Password). + * b) NT-Password: rfc2759 (prep=MS). + * The NT-Password Hash is hashed into a HashNTPasswordHash hash. + * c) EAP-Pwd-Password-Hash - provides hash as binary + * EAP-Pwd-Password-Salt - (optional) salt to be transmitted to client + * (RFC 8146) + * EAP-Pwd-Password-Prep - constant to transmit to client in prep field + * + * Though, there is one issue left. The method needs to be selected in + * EAP-PWD-ID/Request, that is the first message from server and thus before + * the client sent its peer-id. This is feasable using the EAP-Identity frame + * (outer identity); EAP-PWD does transmit its peer-id in plaintext anyway. + * So we need a toggle for this, in case anybody needs rlm_eap_pwd to use + * only the peer_id (inner identity). This toogle is an integer to also support + * setting currently unknown nor not implemented preprocessing methods. + * + * The toogle is named "prep", is a module configuration item, and accepts the + * following values: + * prep | meaning + * -------+-------------------------------------------------------------------- + * -1 | [automatic] discover using method described above from EAP-Identity + * | as User-Name before EAP-PWD-Id/Request + * 0..255 | [static] Fixed password preprocessing method. Expects virtual + * | server to provide matching password given EAP-PWD + * | peer-id as User-Name. The virtual server is provided + * | with EAP-Pwd-Password-Prep containing the configured + * | prep value. + * else | reserved/invalid + * + * Attributes to provide Password/Password-Hash and possibly salt. + * prep | accepted attributes + * -------+-------------------------------------------------------------------- + * -1 | see above for automatic discovery + * 0 | Use Cleartext-Password or give cleartext in EAP-Pwd-Password-Hash + * 1 | Use NT-Password, Cleartext-Password, User-Password or + * | give hashed NT-Password hash in EAP-Pwd-Password-Hash + * 2..255 | Use EAP-Pwd-Password-Hash and possibly EAP-Pwd-Pasword-Salt. + * + * To be able to pass EAP-Pwd-Password-Hash and EAP-Pwd-Password-Salt als hex + * string, they are decoded as hex if module config option unhex=1 (default). + * Set it to zero if you provide binary input. + */ + static CONF_PARSER pwd_module_config[] = { { "group", FR_CONF_OFFSET(PW_TYPE_INTEGER, eap_pwd_t, group), "19" }, { "fragment_size", FR_CONF_OFFSET(PW_TYPE_INTEGER, eap_pwd_t, fragment_size), "1020" }, { "server_id", FR_CONF_OFFSET(PW_TYPE_STRING, eap_pwd_t, server_id), NULL }, { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING, eap_pwd_t, virtual_server), NULL }, + { "prep", FR_CONF_OFFSET(PW_TYPE_SIGNED, eap_pwd_t, prep), "0" }, + { "unhex", FR_CONF_OFFSET(PW_TYPE_SIGNED, eap_pwd_t, unhex), "1" }, CONF_PARSER_TERMINATOR }; @@ -65,6 +147,11 @@ static int mod_instantiate (CONF_SECTION *cs, void **instance) return -1; } + if (inst->prep < -1 || inst->prep > 255) { + cf_log_err_cs(cs, "Invalid value for password preparation method: %d", inst->prep); + return -1; + } + return 0; } @@ -153,18 +240,282 @@ static int send_pwd_request (pwd_session_t *session, EAP_DS *eap_ds) return 1; } +static void normify(REQUEST *request, VALUE_PAIR *vp) +{ + size_t decoded; + size_t expected_len; + uint8_t *buffer; + + rad_assert((vp->da->type == PW_TYPE_OCTETS) || (vp->da->type == PW_TYPE_STRING)); + + if (vp->vp_length % 2 != 0 || vp->vp_length == 0) return; + + expected_len = vp->vp_length / 2; + buffer = talloc_zero_array(request, uint8_t, expected_len); + rad_assert(buffer); + + decoded = fr_hex2bin(buffer, expected_len, vp->vp_strvalue, vp->vp_length); + if (decoded == expected_len) { + RDEBUG2("Normalizing %s from hex encoding, %zu bytes -> %zu bytes", + vp->da->name, vp->vp_length, decoded); + fr_pair_value_memcpy(vp, buffer, decoded); + } else { + RDEBUG2("Normalizing %s from hex encoding, %zu bytes -> %zu bytes failed, got %zu bytes", + vp->da->name, vp->vp_length, expected_len, decoded); + } + + talloc_free(buffer); +} + +static int fetch_and_process_password(pwd_session_t *session, REQUEST *request, eap_pwd_t *inst) { + REQUEST *fake; + VALUE_PAIR *vp, *pw; + const char *pwbuf; + int pw_len; + uint8_t nthash[MD4_DIGEST_LENGTH]; + uint8_t nthashash[MD4_DIGEST_LENGTH]; + int ret = -1; + eap_type_t old_eap_type = 0; + + if ((fake = request_alloc_fake(request)) == NULL) { + RDEBUG("pwd unable to create fake request!"); + return ret; + } + fake->username = fr_pair_afrom_num(fake->packet, PW_USER_NAME, 0); + if (!fake->username) { + RDEBUG("Failed creating pair for peer id"); + goto out; + } + fr_pair_value_bstrncpy(fake->username, session->peer_id, session->peer_id_len); + fr_pair_add(&fake->packet->vps, fake->username); + + if (inst->prep >= 0) { + vp = fr_pair_afrom_num(fake->packet, PW_EAP_PWD_PASSWORD_PREP, 0); + rad_assert(vp != NULL); + vp->vp_byte = inst->prep; + fr_pair_add(&fake->packet->vps, vp); + } + + if ((vp = fr_pair_find_by_num(request->config, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) { + fake->server = vp->vp_strvalue; + } else if (inst->virtual_server) { + fake->server = inst->virtual_server; + } /* else fake->server == request->server */ + + if ((vp = fr_pair_find_by_num(request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY)) != NULL) { + /* EAP-Type = NAK here if inst->prep == -1. + * But this does not help the virtual server to differentiate + * based on which EAP method was selected, that is to properly + * prepare session-state: for PWD. + * So fake EAP-Type = PWD here for the time of the inner request. + */ + old_eap_type = vp->vp_integer; + vp->vp_integer = PW_EAP_PWD; + } + RDEBUG("Sending tunneled request"); + rdebug_pair_list(L_DBG_LVL_1, request, fake->packet->vps, NULL); + + if (fake->server) { + RDEBUG("server %s {", fake->server); + } else { + RDEBUG("server {"); + } + + /* + * Call authorization recursively, which will + * get the password. + */ + RINDENT(); + process_authorize(0, fake); + REXDENT(); + + /* + * Note that we don't do *anything* with the reply + * attributes. + */ + if (fake->server) { + RDEBUG("} # server %s", fake->server); + } else { + RDEBUG("}"); + } + + RDEBUG("Got tunneled reply code %d", fake->reply->code); + rdebug_pair_list(L_DBG_LVL_1, request, fake->reply->vps, NULL); + + if (old_eap_type && (vp = fr_pair_find_by_num(request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY)) != NULL) { + vp->vp_integer = old_eap_type; + } + + pw = fr_pair_find_by_num(fake->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY); + if (!pw) { + pw = fr_pair_find_by_num(fake->config, PW_USER_PASSWORD, 0, TAG_ANY); + } + + if (pw && (inst->prep < 0 || inst->prep == EAP_PWD_PREP_NONE)) { + VERIFY_VP(pw); + session->prep = EAP_PWD_PREP_NONE; + + RDEBUG("Use Cleartext-Password or User-Password for %s to do pwd authentication", + session->peer_id); + + pwbuf = pw->vp_strvalue; + pw_len = pw->vp_length; + + goto success; + } + + pw = fr_pair_find_by_num(fake->config, PW_NT_PASSWORD, 0, TAG_ANY); + + if (pw && (inst->prep < 0 || inst->prep == EAP_PWD_PREP_MS)) { + VERIFY_VP(pw); + session->prep = EAP_PWD_PREP_MS; + + RDEBUG("Use NT-Password for %s to do pwd authentication", + session->peer_id); + + if (pw->vp_length != MD4_DIGEST_LENGTH) { + RDEBUG("NT-Password invalid length"); + goto out; + } + + fr_md4_calc(nthashash, pw->vp_octets, pw->vp_length); + pwbuf = (const char*) nthashash; + pw_len = MD4_DIGEST_LENGTH; + + goto success; + } + + pw = fr_pair_find_by_num(fake->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY); + if (!pw) { + pw = fr_pair_find_by_num(fake->config, PW_USER_PASSWORD, 0, TAG_ANY); + } + + if (pw && inst->prep == EAP_PWD_PREP_MS) { + VERIFY_VP(pw); + session->prep = EAP_PWD_PREP_NONE; + + RDEBUG("Use Cleartext-Password or User-Password as NT-Password for %s to do pwd authentication", + session->peer_id); + + // compute NT-Hash from Cleartext-Password + ssize_t len; + uint8_t ucs2_password[512]; + len = fr_utf8_to_ucs2(ucs2_password, sizeof(ucs2_password), pw->vp_strvalue, pw->vp_length); + if (len < 0) { + ERROR("rlm_eap_pwd: Error converting password to UCS2"); + goto out; + } + fr_md4_calc(nthash, ucs2_password, len); + + fr_md4_calc(nthashash, nthash, MD4_DIGEST_LENGTH); + pwbuf = (const char*) nthashash; + pw_len = MD4_DIGEST_LENGTH; + + goto success; + } + + vp = fr_pair_find_by_num(fake->config, PW_EAP_PWD_PASSWORD_PREP, 0, TAG_ANY); + if (vp) { + VERIFY_VP(vp); + } + if (vp && inst->prep < 0) { + RDEBUG("Use EAP-Pwd-Password-Prep %u for %s to do pwd authentication", + vp->vp_byte, session->peer_id); + session->prep = vp->vp_byte; + } else if (vp && inst->prep != vp->vp_byte) { + RDEBUG2("Mismatch of configured password preparation method and provided EAP-Pwd-Password-Prep attribute type for %s", + session->peer_id); + goto out; + } else if (inst->prep < 0) { + RDEBUG2("Missing EAP-Pwd-Password-Prep for %s", + session->peer_id); + goto out; + } + + pw = fr_pair_find_by_num(fake->config, PW_EAP_PWD_PASSWORD_SALT, 0, TAG_ANY); + if (pw) { + VERIFY_VP(pw); + + RDEBUG("Use EAP-Pwd-Password-Salt for %s to do pwd authentication", + session->peer_id); + + if (inst->unhex) normify(request, pw); + + if (pw->vp_length > 255) { + /* salt len is 1 byte */ + RDEBUG("EAP-Pwd-Password-Salt too long (more than 255 octets)"); + goto out; + } + rad_assert(pw->vp_length <= sizeof(session->salt)); + + session->salt_present = 1; + session->salt_len = pw->vp_length; + memcpy(session->salt, pw->vp_octets, pw->vp_length); + } + + pw = fr_pair_find_by_num(fake->config, PW_EAP_PWD_PASSWORD_HASH, 0, TAG_ANY); + if (pw) { + VERIFY_VP(pw); + + RDEBUG("Use EAP-Pwd-Password-Hash for %s to do pwd authentication", + session->peer_id); + + if (inst->unhex) normify(request, pw); + + pwbuf = (const char*) pw->vp_octets; + pw_len = pw->vp_length; + + goto success; + } + + RDEBUG2("Mismatch of password preparation method and provided password attribute type for %s", + session->peer_id); + goto out; + +success: + if (RDEBUG_ENABLED4) { + char outbuf[1024]; + char *p = outbuf; + for (int i = 0; i < pw_len && p < outbuf + sizeof(outbuf) - 3; i++) { + p += sprintf(p, "%02hhX", pwbuf[i]); + } + RDEBUG4("hex pw data: %s (%d)", outbuf, pw_len); + } + + if (compute_password_element(request, session, session->group_num, + pwbuf, pw_len, + inst->server_id, strlen(inst->server_id), + session->peer_id, strlen(session->peer_id), + &session->token)) { + RDEBUG("failed to obtain password element"); + goto out; + } + + ret = 0; +out: + talloc_free(fake); + return ret; +} + static int mod_session_init (void *instance, eap_handler_t *handler) { pwd_session_t *session; eap_pwd_t *inst = (eap_pwd_t *)instance; VALUE_PAIR *vp; pwd_id_packet_t *packet; + REQUEST *request; if (!inst || !handler) { ERROR("rlm_eap_pwd: Initiate, NULL data provided"); return 0; } + request = handler->request; + if (!request) { + ERROR("rlm_eap_pwd: NULL request provided"); + return 0; + } + /* * make sure the server's been configured properly */ @@ -232,6 +583,30 @@ static int mod_session_init (void *instance, eap_handler_t *handler) session->out_pos = 0; handler->opaque = session; + session->token = fr_rand(); + if (inst->prep < 0) { + RDEBUG2("using outer identity %s to configure EAP-PWD", handler->identity); + session->peer_id_len = strlen(handler->identity); + if (session->peer_id_len >= sizeof(session->peer_id)) { + RDEBUG("identity is malformed"); + return 0; + } + memcpy(session->peer_id, handler->identity, session->peer_id_len); + session->peer_id[session->peer_id_len] = '\0'; + + /* + * make fake request to get the password for the usable ID + * in order to identity prep + */ + if (fetch_and_process_password(session, handler->request, inst) < 0) { + RDEBUG("failed to find password for %s to do pwd authentication (init)", + session->peer_id); + return 0; + } + } else { + session->prep = inst->prep; + } + /* * construct an EAP-pwd-ID/Request */ @@ -244,9 +619,8 @@ static int mod_session_init (void *instance, eap_handler_t *handler) packet->group_num = htons(session->group_num); packet->random_function = EAP_PWD_DEF_RAND_FUN; packet->prf = EAP_PWD_DEF_PRF; - session->token = fr_rand(); memcpy(packet->token, (char *)&session->token, 4); - packet->prep = EAP_PWD_PREP_NONE; + packet->prep = session->prep; memcpy(packet->identity, inst->server_id, session->out_len - sizeof(pwd_id_packet_t) ); handler->stage = PROCESS; @@ -259,16 +633,16 @@ static int mod_process(void *arg, eap_handler_t *handler) pwd_session_t *session; pwd_hdr *hdr; pwd_id_packet_t *packet; + REQUEST *request; eap_packet_t *response; - REQUEST *request, *fake; - VALUE_PAIR *pw, *vp; EAP_DS *eap_ds; - size_t in_len; + size_t in_len, peer_id_len; int ret = 0; eap_pwd_t *inst = (eap_pwd_t *)arg; uint16_t offset; uint8_t exch, *in, *ptr, msk[MSK_EMSK_LEN], emsk[MSK_EMSK_LEN]; uint8_t peer_confirm[SHA256_DIGEST_LENGTH]; + char *peer_id; if (((eap_ds = handler->eap_ds) == NULL) || !inst) return 0; @@ -389,7 +763,7 @@ static int mod_process(void *arg, eap_handler_t *handler) if ((packet->prf != EAP_PWD_DEF_PRF) || (packet->random_function != EAP_PWD_DEF_RAND_FUN) || - (packet->prep != EAP_PWD_PREP_NONE) || + (packet->prep != session->prep) || (CRYPTO_memcmp(packet->token, &session->token, 4)) || (packet->group_num != ntohs(session->group_num))) { RDEBUG2("pwd id response is invalid"); @@ -405,89 +779,46 @@ static int mod_process(void *arg, eap_handler_t *handler) ptr += sizeof(uint8_t); *ptr = EAP_PWD_DEF_PRF; - session->peer_id_len = in_len - sizeof(pwd_id_packet_t); - if (session->peer_id_len >= sizeof(session->peer_id)) { + peer_id_len = in_len - sizeof(pwd_id_packet_t); + if (peer_id_len >= sizeof(session->peer_id)) { RDEBUG2("pwd id response is malformed"); return 0; } + peer_id = packet->identity; - memcpy(session->peer_id, packet->identity, session->peer_id_len); - session->peer_id[session->peer_id_len] = '\0'; - - /* - * make fake request to get the password for the usable ID - */ - if ((fake = request_alloc_fake(handler->request)) == NULL) { - RDEBUG("pwd unable to create fake request!"); - return 0; - } - fake->username = fr_pair_afrom_num(fake->packet, PW_USER_NAME, 0); - if (!fake->username) { - RDEBUG("Failed creating pair for peer id"); - talloc_free(fake); - return 0; - } - fr_pair_value_bstrncpy(fake->username, session->peer_id, session->peer_id_len); - fr_pair_add(&fake->packet->vps, fake->username); - - if ((vp = fr_pair_find_by_num(request->config, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) { - fake->server = vp->vp_strvalue; - } else if (inst->virtual_server) { - fake->server = inst->virtual_server; - } /* else fake->server == request->server */ - - RDEBUG("Sending tunneled request"); - rdebug_pair_list(L_DBG_LVL_1, request, fake->packet->vps, NULL); - - if (fake->server) { - RDEBUG("server %s {", fake->server); - } else { - RDEBUG("server {"); - } + if (inst->prep >= 0) { + /* + * make fake request to get the password for the usable ID + */ - /* - * Call authorization recursively, which will - * get the password. - */ - RINDENT(); - process_authorize(0, fake); - REXDENT(); + session->peer_id_len = peer_id_len; + memcpy(session->peer_id, peer_id, peer_id_len); + session->peer_id[peer_id_len] = '\0'; - /* - * Note that we don't do *anything* with the reply - * attributes. - */ - if (fake->server) { - RDEBUG("} # server %s", fake->server); + if (fetch_and_process_password(session, request, inst) < 0) { + RDEBUG2("failed to find password for %s to do pwd authentication", + session->peer_id); + return 0; + } } else { - RDEBUG("}"); + /* verify inner identity == outer identity */ + if (session->peer_id_len != peer_id_len || + memcmp(session->peer_id, peer_id, peer_id_len) != 0) { + char buf[sizeof(session->peer_id)]; + memcpy(buf, peer_id, peer_id_len); + buf[peer_id_len] = '\0'; + + RDEBUG2("inner identity(peer_id) %s does not match outer identity %s", + buf, session->peer_id); + return 0; + } + RDEBUG2("inner identity matched for %s", session->peer_id); } - RDEBUG("Got tunneled reply code %d", fake->reply->code); - rdebug_pair_list(L_DBG_LVL_1, request, fake->reply->vps, NULL); - - if ((pw = fr_pair_find_by_num(fake->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) == NULL) { - DEBUG2("failed to find password for %s to do pwd authentication", - session->peer_id); - talloc_free(fake); - return 0; - } - - if (compute_password_element(session, session->group_num, - pw->data.strvalue, strlen(pw->data.strvalue), - inst->server_id, strlen(inst->server_id), - session->peer_id, strlen(session->peer_id), - &session->token)) { - DEBUG2("failed to obtain password element"); - talloc_free(fake); - return 0; - } - TALLOC_FREE(fake); - /* * compute our scalar and element */ - if (compute_scalar_element(session, session->bnctx)) { + if (compute_scalar_element(request, session, session->bnctx)) { DEBUG2("failed to compute server's scalar and element"); return 0; } @@ -498,7 +829,7 @@ static int mod_process(void *arg, eap_handler_t *handler) /* * element is a point, get both coordinates: x and y */ - if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->my_element, x, y, + if (!EC_POINT_get_affine_coordinates(session->group, session->my_element, x, y, session->bnctx)) { DEBUG2("server point assignment failed"); BN_clear_free(x); @@ -510,12 +841,23 @@ static int mod_process(void *arg, eap_handler_t *handler) * construct request */ session->out_len = BN_num_bytes(session->order) + (2 * BN_num_bytes(session->prime)); + if (session->salt_present) + session->out_len += 1 + session->salt_len; + if ((session->out = talloc_array(session, uint8_t, session->out_len)) == NULL) { return 0; } memset(session->out, 0, session->out_len); ptr = session->out; + if (session->salt_present) { + *ptr = session->salt_len; + ptr++; + + memcpy(ptr, session->salt, session->salt_len); + ptr += session->salt_len; + } + offset = BN_num_bytes(session->prime) - BN_num_bytes(x); BN_bn2bin(x, ptr + offset); BN_clear_free(x); @@ -534,7 +876,7 @@ static int mod_process(void *arg, eap_handler_t *handler) } break; - case PWD_STATE_COMMIT: + case PWD_STATE_COMMIT: if (EAP_PWD_GET_EXCHANGE(hdr) != EAP_PWD_EXCH_COMMIT) { RDEBUG2("pwd exchange is incorrect: not commit!"); return 0; @@ -543,7 +885,7 @@ static int mod_process(void *arg, eap_handler_t *handler) /* * process the peer's commit and generate the shared key, k */ - if (process_peer_commit(session, in, in_len, session->bnctx)) { + if (process_peer_commit(request, session, in, in_len, session->bnctx)) { RDEBUG2("failed to process peer's commit"); return 0; } @@ -551,7 +893,7 @@ static int mod_process(void *arg, eap_handler_t *handler) /* * compute our confirm blob */ - if (compute_server_confirm(session, session->my_confirm, session->bnctx)) { + if (compute_server_confirm(request, session, session->my_confirm, session->bnctx)) { ERROR("rlm_eap_pwd: failed to compute confirm!"); return 0; } @@ -582,7 +924,7 @@ static int mod_process(void *arg, eap_handler_t *handler) RDEBUG2("pwd exchange is incorrect: not commit!"); return 0; } - if (compute_peer_confirm(session, peer_confirm, session->bnctx)) { + if (compute_peer_confirm(request, session, peer_confirm, session->bnctx)) { RDEBUG2("pwd exchange cannot compute peer's confirm"); return 0; } @@ -590,7 +932,7 @@ static int mod_process(void *arg, eap_handler_t *handler) RDEBUG2("pwd exchange fails: peer confirm is incorrect!"); return 0; } - if (compute_keys(session, peer_confirm, msk, emsk)) { + if (compute_keys(request, session, peer_confirm, msk, emsk)) { RDEBUG2("pwd exchange cannot generate (E)MSK!"); return 0; } diff --git a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h index 2264566bb6..966646c360 100644 --- a/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h +++ b/src/modules/rlm_eap/types/rlm_eap_pwd/rlm_eap_pwd.h @@ -44,6 +44,8 @@ typedef struct _eap_pwd_t { uint32_t fragment_size; char const *server_id; char const *virtual_server; + int32_t prep; + int32_t unhex; } eap_pwd_t; #endif /* _RLM_EAP_PWD_H */ diff --git a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c index 4d41cd42e6..d327c575fc 100644 --- a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c +++ b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.c @@ -43,6 +43,7 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ static CONF_PARSER module_config[] = { { "tls", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_eap_tls_t, tls_conf_name), NULL }, { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_eap_tls_t, virtual_server), NULL }, + { "configurable_client_cert", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_eap_tls_t, configurable_client_cert), NULL }, CONF_PARSER_TERMINATOR }; @@ -71,6 +72,19 @@ static int mod_instantiate(CONF_SECTION *cs, void **instance) return -1; } +#ifdef TLS1_3_VERSION + if ((inst->tls_conf->max_version == TLS1_3_VERSION) || + (inst->tls_conf->min_version == TLS1_3_VERSION)) { + WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + WARN("!! Most supplicants do not support EAP-TLS with TLS 1.3"); + WARN("!! Please set tls_max_version = \"1.2\""); + WARN("!! FreeRADIUS only supports TLS 1.3 for special builds of wpa_supplicant and Windows"); + WARN("!! This limitation is likely to change in late 2021."); + WARN("!! If you are using this version of FreeRADIUS after 2021, you will probably need to upgrade"); + WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + } +#endif + return 0; } @@ -84,25 +98,38 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) tls_session_t *ssn; rlm_eap_tls_t *inst; REQUEST *request = handler->request; + bool require_client_cert = true; inst = type_arg; handler->tls = true; /* - * EAP-TLS always requires a client certificate. + * Respect EAP-TLS-Require-Client-Cert, but only if + * enabled in the module configuration. + * + * We can't change behavior of existing systems, so this + * change has to be enabled via a new configuration + * option. */ - ssn = eaptls_session(handler, inst->tls_conf, true); + if (inst->configurable_client_cert) { + VALUE_PAIR *vp; + + vp = fr_pair_find_by_num(handler->request->config, PW_EAP_TLS_REQUIRE_CLIENT_CERT, 0, TAG_ANY); + if (vp && !vp->vp_integer) require_client_cert = false; + } + + /* + * EAP-TLS always requires a client certificate, and + * allows for TLS 1.3 if permitted. + */ + ssn = eaptls_session(handler, inst->tls_conf, require_client_cert, true); if (!ssn) { return 0; } handler->opaque = ((void *)ssn); - - /* - * Set up type-specific information. - */ - ssn->prf_label = "client EAP encryption"; + ssn->quick_session_tickets = true; /* send as soon as we've seen the client cert */ /* * TLS session initialization is over. Now handle TLS @@ -112,7 +139,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { REDEBUG("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); } else { - RDEBUG2("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); + RDEBUG3("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); } if (status == 0) return 0; @@ -141,7 +168,7 @@ static int CC_HINT(nonnull) mod_process(void *type_arg, eap_handler_t *handler) if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); } else { - RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); + RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); } @@ -195,6 +222,13 @@ static int CC_HINT(nonnull) mod_process(void *type_arg, eap_handler_t *handler) /* success */ } + /* + * Set the label to a fixed string. For TLS 1.3, + * the label is the same for all TLS-based EAP + * methods. + */ + tls_session->label = "client EAP encryption"; + /* * Success: Automatically return MPPE keys. */ diff --git a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h index cebcb92f57..550cbbdce3 100644 --- a/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h +++ b/src/modules/rlm_eap/types/rlm_eap_tls/rlm_eap_tls.h @@ -42,6 +42,11 @@ typedef struct rlm_eap_tls_t { * Virtual server for checking certificates */ char const *virtual_server; + + /* + * Configurable EAP-TLS-Require-Client-Cert + */ + bool configurable_client_cert; } rlm_eap_tls_t; #endif /* _RLM_EAP_TLS_H */ diff --git a/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c b/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c index a3c575bceb..4e53c92244 100644 --- a/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c +++ b/src/modules/rlm_eap/types/rlm_eap_ttls/rlm_eap_ttls.c @@ -181,7 +181,10 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) client_cert = inst->req_client_cert; } - ssn = eaptls_session(handler, inst->tls_conf, client_cert); + /* + * Allow TLS 1.3, it works. + */ + ssn = eaptls_session(handler, inst->tls_conf, client_cert, true); if (!ssn) { return 0; } @@ -189,9 +192,13 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) handler->opaque = ((void *)ssn); /* - * Set up type-specific information. + * Set the label to a fixed string. For TLS 1.3, the + * label is the same for all TLS-based EAP methods. If + * the client is using TLS 1.3, then eaptls_success() + * will over-ride this label with the correct label for + * TLS 1.3. */ - ssn->prf_label = "ttls keying material"; + ssn->label = "ttls keying material"; /* * TLS session initialization is over. Now handle TLS @@ -201,7 +208,7 @@ static int mod_session_init(void *type_arg, eap_handler_t *handler) if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { REDEBUG("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); } else { - RDEBUG2("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); + RDEBUG3("[eaptls start] = %s", fr_int2str(fr_tls_status_table, status, "")); } if (status == 0) return 0; @@ -238,7 +245,7 @@ static int mod_process(void *arg, eap_handler_t *handler) if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { REDEBUG("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); } else { - RDEBUG2("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); + RDEBUG3("[eaptls process] = %s", fr_int2str(fr_tls_status_table, status, "")); } /* @@ -342,8 +349,7 @@ static int mod_process(void *arg, eap_handler_t *handler) * Success: Automatically return MPPE keys. */ case PW_CODE_ACCESS_ACCEPT: - ret = eaptls_success(handler, 0); - goto done; + goto do_keys; /* * No response packet, MUST be proxying it. diff --git a/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c b/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c index 627d722ed7..cbe423951a 100644 --- a/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c +++ b/src/modules/rlm_eap/types/rlm_eap_ttls/ttls.c @@ -145,9 +145,10 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, size_t size; size_t data_left = data_len; VALUE_PAIR *first = NULL; - VALUE_PAIR *vp; + VALUE_PAIR *vp = NULL; RADIUS_PACKET *packet = fake->packet; /* FIXME: api issues */ vp_cursor_t out; + DICT_ATTR const *da; fr_cursor_init(&out, &first); @@ -258,13 +259,13 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, if (decoded < 0) { REDEBUG2("diameter2vp failed decoding attr: %s", fr_strerror()); - goto do_octets; + goto raw; } if ((size_t) decoded != size + 2) { REDEBUG2("diameter2vp failed to entirely decode VSA"); fr_pair_list_free(&vp); - goto do_octets; + goto raw; } fr_cursor_merge(&out, vp); @@ -275,8 +276,10 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, /* * Create it. If this fails, it's because we're OOM. */ - do_octets: - vp = fr_pair_afrom_num(packet, attr, vendor); + da = dict_attrbyvalue(attr, vendor); + if (!da) goto raw; + + vp = fr_pair_afrom_da(packet, da); if (!vp) { RDEBUG2("Failure in creating VP"); fr_pair_list_free(&first); @@ -293,8 +296,6 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, case PW_TYPE_INTEGER: case PW_TYPE_DATE: if (size != vp->vp_length) { - DICT_ATTR const *da; - /* * Bad format. Create a "raw" * attribute. @@ -405,7 +406,7 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, */ if (((vp->da->vendor == 0) && (vp->da->attr == PW_CHAP_CHALLENGE)) || ((vp->da->vendor == VENDORPEC_MICROSOFT) && (vp->da->attr == PW_MSCHAP_CHALLENGE))) { - uint8_t challenge[16]; + uint8_t challenge[17]; if ((vp->vp_length < 8) || (vp->vp_length > 16)) { @@ -415,8 +416,11 @@ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, return NULL; } - eapttls_gen_challenge(ssl, challenge, - sizeof(challenge)); + /* + * TLSv1.3 exports a different key depending on the length + * requested so ask for *exactly* what the spec requires + */ + eapttls_gen_challenge(ssl, challenge, vp->vp_length + 1); if (memcmp(challenge, vp->vp_octets, vp->vp_length) != 0) { @@ -644,6 +648,7 @@ static rlm_rcode_t CC_HINT(nonnull) process_reply(eap_handler_t *handler, tls_se */ switch (reply->code) { case PW_CODE_ACCESS_ACCEPT: + tls_session->authentication_success = true; RDEBUG("Got tunneled Access-Accept"); rcode = RLM_MODULE_OK; diff --git a/src/modules/rlm_exec/rlm_exec.c b/src/modules/rlm_exec/rlm_exec.c index e7dbfa685e..f7e23628e6 100644 --- a/src/modules/rlm_exec/rlm_exec.c +++ b/src/modules/rlm_exec/rlm_exec.c @@ -353,7 +353,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_exec_dispatch(void *instance, REQUEST *r * If we're not waiting, then there are no output pairs. */ if (inst->output) { - fr_pair_list_move(ctx, output_pairs, &answer); + fr_pair_list_move(ctx, output_pairs, &answer, T_OP_ADD); } fr_pair_list_free(&answer); @@ -399,7 +399,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque /* * Always add the value-pairs to the reply. */ - fr_pair_list_move(request->reply, &request->reply->vps, &tmp); + fr_pair_list_move(request->reply, &request->reply->vps, &tmp, T_OP_ADD); fr_pair_list_free(&tmp); finish: diff --git a/src/modules/rlm_expr/rlm_expr.c b/src/modules/rlm_expr/rlm_expr.c index c449d7776b..f835800376 100644 --- a/src/modules/rlm_expr/rlm_expr.c +++ b/src/modules/rlm_expr/rlm_expr.c @@ -970,6 +970,49 @@ static ssize_t md5_xlat(UNUSED void *instance, REQUEST *request, return strlen(out); } +/** Calculate the MD4 hash of a string or attribute. + * + * Example: "%{md4:foo}" == "0ac6700c491d70fb8650940b1ca1e4b2" + */ +static ssize_t md4_xlat(UNUSED void *instance, REQUEST *request, + char const *fmt, char *out, size_t outlen) +{ + uint8_t digest[16]; + ssize_t i, len, inlen; + uint8_t const *p; + FR_MD4_CTX ctx; + + /* + * We need room for at least one octet of output. + */ + if (outlen < 3) { + *out = '\0'; + return 0; + } + + inlen = xlat_fmt_to_ref(&p, request, fmt); + if (inlen < 0) { + return -1; + } + + fr_md4_init(&ctx); + fr_md4_update(&ctx, p, inlen); + fr_md4_final(digest, &ctx); + + /* + * Each digest octet takes two hex digits, plus one for + * the terminating NUL. + */ + len = (outlen / 2) - 1; + if (len > 16) len = 16; + + for (i = 0; i < len; i++) { + snprintf(out + i * 2, 3, "%02x", digest[i]); + } + + return strlen(out); +} + /** Calculate the SHA1 hash of a string or attribute. * * Example: "%{sha1:foo}" == "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33" @@ -1570,6 +1613,76 @@ static ssize_t next_time_xlat(UNUSED void *instance, REQUEST *request, return snprintf(out, outlen, "%" PRIu64, (uint64_t)(mktime(local) - now)); } +/** Calculate number of seconds until the previous n hour(s), day(s), week(s), year(s). + * + * For example, if it were 16:18 %{lasttime:1h} would expand to -2520. + */ +static ssize_t last_time_xlat(UNUSED void *instance, REQUEST *request, + char const *fmt, char *out, size_t outlen) +{ + long num; + + char const *p; + char *q; + time_t now; + struct tm *local, local_buff; + + now = time(NULL); + local = localtime_r(&now, &local_buff); + + p = fmt; + + num = strtoul(p, &q, 10); + if (!q || *q == '\0') { + REDEBUG("nexttime: must be followed by period specifier (h|d|w|m|y)"); + return -1; + } + + if (p == q) { + num = 1; + } else { + p += q - p; + } + + local->tm_sec = 0; + local->tm_min = 0; + + switch (*p) { + case 'h': + local->tm_hour -= num; + break; + + case 'd': + local->tm_hour = 0; + local->tm_mday -= num; + break; + + case 'w': + local->tm_hour = 0; + local->tm_mday -= (7 - local->tm_wday) + (7 * (num-1)); + break; + + case 'm': + local->tm_hour = 0; + local->tm_mday = 1; + local->tm_mon -= num; + break; + + case 'y': + local->tm_hour = 0; + local->tm_mday = 1; + local->tm_mon = 0; + local->tm_year -= num; + break; + + default: + REDEBUG("lasttime: Invalid period specifier '%c', must be h|d|w|m|y", *p); + return -1; + } + + return snprintf(out, outlen, "%" PRIu64, (uint64_t)(now - mktime(local))); +} + /* * Parse the 3 arguments to lpad / rpad. @@ -1761,6 +1874,7 @@ static int mod_bootstrap(CONF_SECTION *conf, void *instance) xlat_register("unescape", unescape_xlat, NULL, inst); xlat_register("tolower", tolower_xlat, NULL, inst); xlat_register("toupper", toupper_xlat, NULL, inst); + xlat_register("md4", md4_xlat, NULL, inst); xlat_register("md5", md5_xlat, NULL, inst); xlat_register("sha1", sha1_xlat, NULL, inst); #ifdef HAVE_OPENSSL_EVP_H @@ -1778,6 +1892,7 @@ static int mod_bootstrap(CONF_SECTION *conf, void *instance) xlat_register("explode", explode_xlat, NULL, inst); xlat_register("nexttime", next_time_xlat, NULL, inst); + xlat_register("lasttime", last_time_xlat, NULL, inst); xlat_register("lpad", lpad_xlat, NULL, inst); xlat_register("rpad", rpad_xlat, NULL, inst); diff --git a/src/modules/rlm_files/rlm_files.c b/src/modules/rlm_files/rlm_files.c index c825a9230b..9e77cd7ff1 100644 --- a/src/modules/rlm_files/rlm_files.c +++ b/src/modules/rlm_files/rlm_files.c @@ -422,7 +422,7 @@ static rlm_rcode_t file_common(rlm_files_t *inst, REQUEST *request, char const * /* ctx may be reply or proxy */ reply_tmp = fr_pair_list_copy(reply_packet, pl->reply); radius_pairmove(request, &reply_packet->vps, reply_tmp, true); - fr_pair_list_move(request, &request->config, &check_tmp); + fr_pair_list_move(request, &request->config, &check_tmp, T_OP_ADD); fr_pair_list_free(&check_tmp); /* diff --git a/src/modules/rlm_ldap/ldap.h b/src/modules/rlm_ldap/ldap.h index 7dc874d35e..29e7cfd757 100644 --- a/src/modules/rlm_ldap/ldap.h +++ b/src/modules/rlm_ldap/ldap.h @@ -20,6 +20,7 @@ * always need to support that. */ #define LDAP_DEPRECATED 1 +USES_APPLE_DEPRECATED_API /* Apple wants us to use OpenDirectory Framework, we don't want that */ #include #include #include "config.h" @@ -265,6 +266,9 @@ typedef struct ldap_instance { int tls_require_cert; //!< OpenLDAP constant representing the require cert string. + char const *tls_min_version_str; //!< Minimum TLS version + int tls_min_version; + /* * Options */ diff --git a/src/modules/rlm_mschap/rlm_mschap.c b/src/modules/rlm_mschap/rlm_mschap.c index 83c84a63e2..25bcd9c44e 100644 --- a/src/modules/rlm_mschap/rlm_mschap.c +++ b/src/modules/rlm_mschap/rlm_mschap.c @@ -322,6 +322,56 @@ static ssize_t mschap_xlat(void *instance, REQUEST *request, data = response->vp_octets + 2; data_len = 24; + /* + * Pull the domain name out of the User-Name, if it exists. + * + * This is the full domain name, not just the name after host/ + */ + } else if (strncasecmp(fmt, "Domain-Name", 11) == 0) { + char *p; + + user_name = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY); + if (!user_name) { + REDEBUG("No User-Name was found in the request"); + return -1; + } + + /* + * First check to see if this is a host/ style User-Name + * (a la Kerberos host principal) + */ + if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) { + /* + * If we're getting a User-Name formatted in this way, + * it's likely due to PEAP. The Windows Domain will be + * the first domain component following the hostname, + * or the machine name itself if only a hostname is supplied + */ + p = strchr(user_name->vp_strvalue, '.'); + if (!p) { + RDEBUG2("setting NT-Domain to same as machine name"); + strlcpy(out, user_name->vp_strvalue + 5, outlen); + } else { + p++; /* skip the period */ + strlcpy(out, p, outlen); + } + } else { + p = strchr(user_name->vp_strvalue, '\\'); + if (!p) { + REDEBUG("No NT-Domain was found in the User-Name"); + return -1; + } + + /* + * Hack. This is simpler than the alternatives. + */ + *p = '\0'; + strlcpy(out, user_name->vp_strvalue, outlen); + *p = '\\'; + } + + return strlen(out); + /* * Pull the NT-Domain out of the User-Name, if it exists. */ @@ -616,7 +666,7 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance) return -1; } #else - cf_log_err_cs(conf, "'winbind' auth not enabled at compiled time"); + cf_log_err_cs(conf, "'winbind' is not enabled in this build."); return -1; #endif } @@ -942,7 +992,6 @@ ntlm_auth_err: ssize_t result_len; char result[253]; uint8_t nt_pass_decrypted[516], old_nt_hash_expected[NT_DIGEST_LENGTH]; - RC4_KEY key; if (!nt_password) { RDEBUG("Local MS-CHAPv2 password change requires NT-Password attribute"); @@ -951,11 +1000,47 @@ ntlm_auth_err: RDEBUG("Doing MS-CHAPv2 password change locally"); } - /* - * Decrypt the blob - */ - RC4_set_key(&key, nt_password->vp_length, nt_password->vp_octets); - RC4(&key, 516, new_nt_password, nt_pass_decrypted); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + { + EVP_CIPHER_CTX *ctx; + int ntlen = sizeof(nt_pass_decrypted); + + ctx = EVP_CIPHER_CTX_new(); + if (!ctx) { + REDEBUG("Failed getting RC4 from OpenSSL"); + error: + if (ctx) EVP_CIPHER_CTX_free(ctx); + return -1; + } + + if (!EVP_CIPHER_CTX_set_key_length(ctx, nt_password->vp_length)) { + REDEBUG("Failed setting key length"); + goto error; + } + + if (!EVP_EncryptInit_ex(ctx, EVP_rc4(), NULL, nt_password->vp_octets, NULL)) { + REDEBUG("Failed setting key value"); + goto error;; + } + + if (!EVP_EncryptUpdate(ctx, nt_pass_decrypted, &ntlen, new_nt_password, ntlen)) { + REDEBUG("Failed getting output"); + goto error; + } + + EVP_CIPHER_CTX_free(ctx); + } +#else + { + RC4_KEY key; + + /* + * Decrypt the blob + */ + RC4_set_key(&key, nt_password->vp_length, nt_password->vp_octets); + RC4(&key, 516, new_nt_password, nt_pass_decrypted); + } +#endif /* * pwblock is diff --git a/src/modules/rlm_otp/otp_mppe.c b/src/modules/rlm_otp/otp_mppe.c index 399689abf3..932e44abe9 100644 --- a/src/modules/rlm_otp/otp_mppe.c +++ b/src/modules/rlm_otp/otp_mppe.c @@ -39,10 +39,16 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ #include +#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER < 0x30000000L) +#define UNUSED3 +#else +#define UNUSED3 UNUSED +#endif + /* * Add MPPE attributes to a request, if required. */ -void otp_mppe(REQUEST *request, otp_pwe_t pwe, rlm_otp_t const *opt, char const *passcode) +void otp_mppe(REQUEST *request, otp_pwe_t pwe, UNUSED3 rlm_otp_t const *opt, UNUSED3 char const *passcode) { VALUE_PAIR *cvp, *rvp; @@ -58,6 +64,7 @@ void otp_mppe(REQUEST *request, otp_pwe_t pwe, rlm_otp_t const *opt, char const case PWE_CHAP: return; +#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER < 0x30000000L) case PWE_MSCHAP: /* First, set some related attributes. */ pair_make_reply("MS-MPPE-Encryption-Policy", otp_mppe_policy[opt->mschap_mppe_policy], T_OP_EQ); @@ -368,7 +375,12 @@ void otp_mppe(REQUEST *request, otp_pwe_t pwe, rlm_otp_t const *opt, char const break; /* PWE_MSCHAP2 */ } /* PWE_MSCHAP2 */ - +#else + case PWE_MSCHAP: + case PWE_MSCHAP2: + REDEBUG("MS-CHAP is unsupported for OpenSSL 3."); + break; +#endif } /* switch (pwe) */ return; diff --git a/src/modules/rlm_otp/otp_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, ""); - len = snprintf(p, freespace + 1, "\"%s\":{\"type\":\"%s\",\"value\":[", vp->da->name, type); + if (ctx->section->attr_num) { + len = snprintf(p, freespace + 1, "\"%s\":{\"attr_num\":%d,\"type\":\"%s\",\"value\":[", + vp->da->name, vp->da->attr, type); + } else { + len = snprintf(p, freespace + 1, "\"%s\":{\"type\":\"%s\",\"value\":[", vp->da->name, type); + } + if (len >= freespace) goto no_space; p += len; freespace -= len; @@ -783,7 +843,7 @@ static size_t rest_encode_json(void *out, size_t size, size_t nmemb, void *userd * write that out. */ attr_space = fr_cursor_next_peek(&ctx->cursor) ? freespace - 1 : freespace; - len = vp_prints_value_json(p, attr_space + 1, vp); + len = vp_prints_value_json(p, attr_space + 1, vp, ctx->section->raw_value); if (is_truncated(len, attr_space + 1)) goto no_space; /* @@ -1575,8 +1635,9 @@ static size_t rest_response_header(void *in, size_t size, size_t nmemb, void *us * HTTP/ [ ]\r\n * * "HTTP/1.1 " (8) + "100 " (4) + "\r\n" (2) = 14 + * "HTTP/2 " (8) + "100 " (4) + "\r\n" (2) = 12 */ - if (s < 14) { + if (s < 12) { REDEBUG("Malformed HTTP header: Status line too short"); goto malformed; } @@ -1614,8 +1675,10 @@ static size_t rest_response_header(void *in, size_t size, size_t nmemb, void *us p++; s--; - /* Char after reason code must be a space, or \r */ - if (!((p[3] == ' ') || (p[3] == '\r'))) goto malformed; + /* + * "xxx( |\r)" status code and terminator. + */ + if (!isdigit(p[0]) || !isdigit(p[1]) || !isdigit(p[2]) || !((p[3] == ' ') || (p[3] == '\r'))) goto malformed; ctx->code = atoi(p); @@ -1996,11 +2059,24 @@ int rest_request_config(rlm_rest_t *instance, rlm_rest_section_t *section, SET_OPTION(CURLOPT_NOSIGNAL, 1); SET_OPTION(CURLOPT_USERAGENT, "FreeRADIUS " RADIUSD_VERSION_STRING); + /* + * As described in https://curl.se/libcurl/c/CURLOPT_HTTP_VERSION.html, + * The libcurl decides which http version should be + * used by default accoring by library version. + */ + if (instance->http_negotiation != CURL_HTTP_VERSION_NONE) { + RDEBUG3("Set HTTP negotiation for %s", instance->http_negotiation_str); + SET_OPTION(CURLOPT_HTTP_VERSION, instance->http_negotiation); + } + content_type = fr_int2str(http_content_type_table, type, section->body_str); snprintf(buffer, sizeof(buffer), "Content-Type: %s", content_type); ctx->headers = curl_slist_append(ctx->headers, buffer); if (!ctx->headers) goto error_header; + // Pass configuration to the request + ctx->request.section = section; + SET_OPTION(CURLOPT_CONNECTTIMEOUT_MS, instance->connect_timeout); SET_OPTION(CURLOPT_TIMEOUT_MS, section->timeout); @@ -2322,6 +2398,7 @@ int rest_request_perform(UNUSED rlm_rest_t *instance, UNUSED rlm_rest_section_t rlm_rest_handle_t *randle = handle; CURL *candle = randle->handle; CURLcode ret; + VALUE_PAIR *vp; ret = curl_easy_perform(candle); if (ret != CURLE_OK) { @@ -2330,6 +2407,14 @@ int rest_request_perform(UNUSED rlm_rest_t *instance, UNUSED rlm_rest_section_t return -1; } + /* + * Save the HTTP return status code. + */ + vp = pair_make_reply("REST-HTTP-Status-Code", NULL, T_OP_SET); + vp->vp_integer = rest_get_handle_code(handle); + + RDEBUG2("Adding reply:REST-HTTP-Status-Code = \"%d\"", vp->vp_integer); + return 0; } diff --git a/src/modules/rlm_rest/rest.h b/src/modules/rlm_rest/rest.h index 9e278d16c4..4c41cf46c2 100644 --- a/src/modules/rlm_rest/rest.h +++ b/src/modules/rlm_rest/rest.h @@ -31,6 +31,10 @@ RCSIDH(other_h, "$Id$") #define CURL_NO_OLDIES 1 #include +#ifdef HAVE_WDOCUMENTATION +DIAG_OFF(documentation) +#endif + #ifdef HAVE_JSON # if defined(HAVE_JSONMC_JSON_H) # include @@ -39,6 +43,10 @@ RCSIDH(other_h, "$Id$") # endif #endif +#ifdef HAVE_WDOCUMENTATION +DIAG_ON(documentation) +#endif + #define REST_URI_MAX_LEN 2048 #define REST_BODY_MAX_LEN 8192 #define REST_BODY_INIT 1024 @@ -102,6 +110,8 @@ extern const FR_NAME_NUMBER http_body_type_table[]; extern const FR_NAME_NUMBER http_content_type_table[]; +extern const FR_NAME_NUMBER http_negotiation_table[]; + /* * Structure for section configuration */ @@ -115,6 +125,9 @@ typedef struct rlm_rest_section_t { char const *body_str; //!< The string version of the encoding/content type. http_body_type_t body; //!< What encoding type should be used. + bool attr_num; //!< If true, the the attribute number is supplied for each attribute. + bool raw_value; //!< If true, enumerated attributes are provided as a numeric value + char const *force_to_str; //!< Force decoding with this decoder. http_body_type_t force_to; //!< Override the Content-Type header in the response //!< to force decoding as a particular type. @@ -154,6 +167,9 @@ typedef struct rlm_rest_t { struct timeval connect_timeout_tv; //!< Connection timeout timeval. long connect_timeout; //!< Connection timeout ms. + char const *http_negotiation_str; //!< The string version of the http_negotiation + long http_negotiation; //!< The HTTP protocol version to use + fr_connection_pool_t *pool; //!< Pointer to the connection pool. rlm_rest_section_t authorize; //!< Configuration specific to authorisation. @@ -205,6 +221,8 @@ typedef struct rlm_rest_request_t { size_t chunk; //!< Chunk size + rlm_rest_section_t *section; //!< Configuration data + void *encoder; //!< Encoder specific data. } rlm_rest_request_t; diff --git a/src/modules/rlm_rest/rlm_rest.c b/src/modules/rlm_rest/rlm_rest.c index 6fa1345614..1337ae5425 100644 --- a/src/modules/rlm_rest/rlm_rest.c +++ b/src/modules/rlm_rest/rlm_rest.c @@ -60,6 +60,8 @@ static const CONF_PARSER section_config[] = { { "uri", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_rest_section_t, uri), "" }, { "method", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, method_str), "GET" }, { "body", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, body_str), "none" }, + { "attr_num", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_rest_section_t, attr_num), "no" }, + { "raw_value", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_rest_section_t, raw_value), "no" }, { "data", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_rest_section_t, data), NULL }, { "force_to", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, force_to_str), NULL }, @@ -81,6 +83,8 @@ static const CONF_PARSER section_config[] = { static const CONF_PARSER module_config[] = { { "connect_uri", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_t, connect_uri), NULL }, { "connect_timeout", FR_CONF_OFFSET(PW_TYPE_TIMEVAL, rlm_rest_t, connect_timeout_tv), "4.0" }, + { "http_negotiation", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_t, http_negotiation_str), "default" }, + CONF_PARSER_TERMINATOR }; @@ -223,6 +227,8 @@ static ssize_t rest_xlat(void *instance, REQUEST *request, .name = "xlat", .method = HTTP_METHOD_GET, .body = HTTP_BODY_NONE, + .attr_num = false, + .raw_value = false, .body_str = "application/x-www-form-urlencoded", .require_auth = false, .force_to = HTTP_BODY_PLAIN @@ -926,6 +932,12 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance) return -1; } + inst->http_negotiation = fr_str2int(http_negotiation_table, inst->http_negotiation_str, -1); + if (inst->http_negotiation == -1) { + cf_log_err_cs(conf, "Unsupported HTTP version \"%s\".", inst->http_negotiation_str); + return -1; + } + /* * Initialise REST libraries. */ diff --git a/src/modules/rlm_wimax/milenage.c b/src/modules/rlm_wimax/milenage.c new file mode 100644 index 0000000000..e14086e78c --- /dev/null +++ b/src/modules/rlm_wimax/milenage.c @@ -0,0 +1,642 @@ +/** + * @file src/modules/rlm_wimax/milenage.c + * @brief 3GPP AKA - Milenage algorithm (3GPP TS 35.205, .206, .207, .208) + * + * This file implements an example authentication algorithm defined for 3GPP + * AKA. This can be used to implement a simple HLR/AuC into hlr_auc_gw to allow + * EAP-AKA to be tested properly with real USIM cards. + * + * This implementations assumes that the r1..r5 and c1..c5 constants defined in + * TS 35.206 are used, i.e., r1=64, r2=0, r3=32, r4=64, r5=96, c1=00..00, + * c2=00..01, c3=00..02, c4=00..04, c5=00..08. The block cipher is assumed to + * be AES (Rijndael). + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + * + * @copyright 2017 The FreeRADIUS server project + * @copyright 2006-2007 (j@w1.fi) + */ +#include +#include + +#include +#include +#include +#include "milenage.h" + +#define MILENAGE_MAC_A_SIZE 8 +#define MILENAGE_MAC_S_SIZE 8 + +static inline int aes_128_encrypt_block(EVP_CIPHER_CTX *evp_ctx, + uint8_t const key[16], uint8_t const in[16], uint8_t out[16]) +{ + size_t len; + + if (unlikely(EVP_EncryptInit_ex(evp_ctx, EVP_aes_128_ecb(), NULL, key, NULL) != 1)) { + fr_strerror_printf("Failed initialising AES-128-ECB context"); + return -1; + } + + /* + * By default OpenSSL will try and pad out a 16 byte + * plaintext to 32 bytes so that it's detectable that + * there was padding. + * + * In this case we know the length of the plaintext + * we're trying to recover, so we explicitly tell + * OpenSSL not to pad here, and not to expected padding + * when decrypting. + */ + EVP_CIPHER_CTX_set_padding(evp_ctx, 0); + if (unlikely(EVP_EncryptUpdate(evp_ctx, out, (int *)&len, in, 16) != 1) || + unlikely(EVP_EncryptFinal_ex(evp_ctx, out + len, (int *)&len) != 1)) { + fr_strerror_printf("Failed encrypting data"); + return -1; + } + + return 0; +} + +/** milenage_f1 - Milenage f1 and f1* algorithms + * + * @param[in] opc 128-bit value derived from OP and K. + * @param[in] k 128-bit subscriber key. + * @param[in] rand 128-bit random challenge. + * @param[in] sqn 48-bit sequence number. + * @param[in] amf 16-bit authentication management field. + * @param[out] mac_a Buffer for MAC-A = 64-bit network authentication code, or NULL + * @param[out] mac_s Buffer for MAC-S = 64-bit resync authentication code, or NULL + * @return + * - 0 on success. + * - -1 on failure. + */ +static int milenage_f1(uint8_t mac_a[MILENAGE_MAC_A_SIZE], + uint8_t mac_s[MILENAGE_MAC_S_SIZE], + uint8_t const opc[MILENAGE_OPC_SIZE], + uint8_t const k[MILENAGE_KI_SIZE], + uint8_t const rand[MILENAGE_RAND_SIZE], + uint8_t const sqn[MILENAGE_SQN_SIZE], + uint8_t const amf[MILENAGE_AMF_SIZE]) +{ + uint8_t tmp1[16], tmp2[16], tmp3[16]; + int i; + EVP_CIPHER_CTX *evp_ctx; + + /* tmp1 = TEMP = E_K(RAND XOR OP_C) */ + for (i = 0; i < 16; i++) tmp1[i] = rand[i] ^ opc[i]; + + evp_ctx = EVP_CIPHER_CTX_new(); + if (!evp_ctx) { + //tls_strerror_printf("Failed allocating EVP context"); + return -1; + } + + if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp1) < 0) { + error: + EVP_CIPHER_CTX_free(evp_ctx); + return -1; + } + + /* tmp2 = IN1 = SQN || AMF || SQN || AMF */ + memcpy(tmp2, sqn, 6); + memcpy(tmp2 + 6, amf, 2); + memcpy(tmp2 + 8, tmp2, 8); + + /* OUT1 = E_K(TEMP XOR rot(IN1 XOR OP_C, r1) XOR c1) XOR OP_C */ + + /* + * rotate (tmp2 XOR OP_C) by r1 (= 0x40 = 8 bytes) + */ + for (i = 0; i < 16; i++) tmp3[(i + 8) % 16] = tmp2[i] ^ opc[i]; + + /* + * XOR with TEMP = E_K(RAND XOR OP_C) + */ + for (i = 0; i < 16; i++) tmp3[i] ^= tmp1[i]; + /* XOR with c1 (= ..00, i.e., NOP) */ + + /* + * f1 || f1* = E_K(tmp3) XOR OP_c + */ + if (aes_128_encrypt_block(evp_ctx, k, tmp3, tmp1) < 0) goto error; /* Reuses existing key */ + + for (i = 0; i < 16; i++) tmp1[i] ^= opc[i]; + + if (mac_a) memcpy(mac_a, tmp1, 8); /* f1 */ + if (mac_s) memcpy(mac_s, tmp1 + 8, 8); /* f1* */ + + EVP_CIPHER_CTX_free(evp_ctx); + + return 0; +} + +/** milenage_f2345 - Milenage f2, f3, f4, f5, f5* algorithms + * + * @param[out] res Buffer for RES = 64-bit signed response (f2), or NULL + * @param[out] ck Buffer for CK = 128-bit confidentiality key (f3), or NULL + * @param[out] ik Buffer for IK = 128-bit integrity key (f4), or NULL + * @param[out] ak Buffer for AK = 48-bit anonymity key (f5), or NULL + * @param[out] ak_resync Buffer for AK = 48-bit anonymity key (f5*), or NULL + * @param[in] opc 128-bit value derived from OP and K. + * @param[in] k 128-bit subscriber key + * @param[in] rand 128-bit random challenge + * @return + * - 0 on success. + * - -1 on failure. + */ +static int milenage_f2345(uint8_t res[MILENAGE_RES_SIZE], + uint8_t ik[MILENAGE_IK_SIZE], + uint8_t ck[MILENAGE_CK_SIZE], + uint8_t ak[MILENAGE_AK_SIZE], + uint8_t ak_resync[MILENAGE_AK_SIZE], + uint8_t const opc[MILENAGE_OPC_SIZE], + uint8_t const k[MILENAGE_KI_SIZE], + uint8_t const rand[MILENAGE_RAND_SIZE]) +{ + uint8_t tmp1[16], tmp2[16], tmp3[16]; + int i; + EVP_CIPHER_CTX *evp_ctx; + + /* tmp2 = TEMP = E_K(RAND XOR OP_C) */ + for (i = 0; i < 16; i++) tmp1[i] = rand[i] ^ opc[i]; + + evp_ctx = EVP_CIPHER_CTX_new(); + if (!evp_ctx) { + fr_strerror_printf("Failed allocating EVP context"); + return -1; + } + + if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp2) < 0) { + error: + EVP_CIPHER_CTX_free(evp_ctx); + return -1; + } + + /* OUT2 = E_K(rot(TEMP XOR OP_C, r2) XOR c2) XOR OP_C */ + /* OUT3 = E_K(rot(TEMP XOR OP_C, r3) XOR c3) XOR OP_C */ + /* OUT4 = E_K(rot(TEMP XOR OP_C, r4) XOR c4) XOR OP_C */ + /* OUT5 = E_K(rot(TEMP XOR OP_C, r5) XOR c5) XOR OP_C */ + + /* f2 and f5 */ + /* rotate by r2 (= 0, i.e., NOP) */ + for (i = 0; i < 16; i++) tmp1[i] = tmp2[i] ^ opc[i]; + tmp1[15] ^= 1; /* XOR c2 (= ..01) */ + /* f5 || f2 = E_K(tmp1) XOR OP_c */ + + if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp3) < 0) goto error; + + for (i = 0; i < 16; i++) tmp3[i] ^= opc[i]; + if (res) memcpy(res, tmp3 + 8, 8); /* f2 */ + if (ak) memcpy(ak, tmp3, 6); /* f5 */ + + /* f3 */ + if (ck) { + /* rotate by r3 = 0x20 = 4 bytes */ + for (i = 0; i < 16; i++) tmp1[(i + 12) % 16] = tmp2[i] ^ opc[i]; + tmp1[15] ^= 2; /* XOR c3 (= ..02) */ + + if (aes_128_encrypt_block(evp_ctx, k, tmp1, ck) < 0) goto error; + + for (i = 0; i < 16; i++) ck[i] ^= opc[i]; + } + + /* f4 */ + if (ik) { + /* rotate by r4 = 0x40 = 8 bytes */ + for (i = 0; i < 16; i++) tmp1[(i + 8) % 16] = tmp2[i] ^ opc[i]; + tmp1[15] ^= 4; /* XOR c4 (= ..04) */ + + if (aes_128_encrypt_block(evp_ctx, k, tmp1, ik) < 0) goto error; + + for (i = 0; i < 16; i++) ik[i] ^= opc[i]; + } + + /* f5* */ + if (ak_resync) { + /* rotate by r5 = 0x60 = 12 bytes */ + for (i = 0; i < 16; i++) tmp1[(i + 4) % 16] = tmp2[i] ^ opc[i]; + tmp1[15] ^= 8; /* XOR c5 (= ..08) */ + + if (aes_128_encrypt_block(evp_ctx, k, tmp1, tmp1) < 0) goto error; + + for (i = 0; i < 6; i++) ak_resync[i] = tmp1[i] ^ opc[i]; + } + EVP_CIPHER_CTX_free(evp_ctx); + + return 0; +} + +/** Derive OPc from OP and Ki + * + * @param[out] opc The derived Operator Code used as an input to other Milenage + * functions. + * @param[in] op Operator Code. + * @param[in] ki Subscriber key. + * @return + * - 0 on success. + * - -1 on failure. + */ +int milenage_opc_generate(uint8_t opc[MILENAGE_OPC_SIZE], + uint8_t const op[MILENAGE_OP_SIZE], + uint8_t const ki[MILENAGE_KI_SIZE]) +{ + int ret; + uint8_t tmp[MILENAGE_OPC_SIZE]; + EVP_CIPHER_CTX *evp_ctx; + size_t i; + + evp_ctx = EVP_CIPHER_CTX_new(); + if (!evp_ctx) { + fr_strerror_printf("Failed allocating EVP context"); + return -1; + } + ret = aes_128_encrypt_block(evp_ctx, ki, op, tmp); + EVP_CIPHER_CTX_free(evp_ctx); + if (ret < 0) return ret; + + for (i = 0; i < sizeof(tmp); i++) opc[i] = op[i] ^ tmp[i]; + + return 0; +} + +/** Generate AKA AUTN, IK, CK, RES + * + * @param[out] autn Buffer for AUTN = 128-bit authentication token. + * @param[out] ik Buffer for IK = 128-bit integrity key (f4), or NULL. + * @param[out] ck Buffer for CK = 128-bit confidentiality key (f3), or NULL. + * @param[out] ak Buffer for AK = 48-bit anonymity key (f5), or NULL + * @param[out] res Buffer for RES = 64-bit signed response (f2), or NULL. + * @param[in] opc 128-bit operator variant algorithm configuration field (encr.). + * @param[in] amf 16-bit authentication management field. + * @param[in] ki 128-bit subscriber key. + * @param[in] sqn 48-bit sequence number (host byte order). + * @param[in] rand 128-bit random challenge. + * @return + * - 0 on success. + * - -1 on failure. + */ +int milenage_umts_generate(uint8_t autn[MILENAGE_AUTN_SIZE], + uint8_t ik[MILENAGE_IK_SIZE], + uint8_t ck[MILENAGE_CK_SIZE], + uint8_t ak[MILENAGE_AK_SIZE], + uint8_t res[MILENAGE_RES_SIZE], + uint8_t const opc[MILENAGE_OPC_SIZE], + uint8_t const amf[MILENAGE_AMF_SIZE], + uint8_t const ki[MILENAGE_KI_SIZE], + uint64_t sqn, + uint8_t const rand[MILENAGE_RAND_SIZE]) +{ + uint8_t mac_a[8], ak_buff[MILENAGE_AK_SIZE]; + uint8_t sqn_buff[MILENAGE_SQN_SIZE]; + uint8_t *p = autn; + size_t i; + + if ((milenage_f1(mac_a, NULL, opc, ki, rand, + uint48_to_buff(sqn_buff, sqn), amf) < 0) || + (milenage_f2345(res, ik, ck, ak_buff, NULL, opc, ki, rand) < 0)) return -1; + + /* + * AUTN = (SQN ^ AK) || AMF || MAC_A + */ + for (i = 0; i < sizeof(sqn_buff); i++) *p++ = sqn_buff[i] ^ ak_buff[i]; + memcpy(p, amf, MILENAGE_AMF_SIZE); + p += MILENAGE_AMF_SIZE; + memcpy(p, mac_a, sizeof(mac_a)); + + /* + * Output the anonymity key if required + */ + if (ak) memcpy(ak, ak_buff, sizeof(ak_buff)); + + return 0; +} + +/** Milenage AUTS validation + * + * @param[out] sqn SQN = 48-bit sequence number (host byte order). + * @param[in] opc 128-bit operator variant algorithm configuration field (encr.). + * @param[in] ki 128-bit subscriber key. + * @param[in] rand 128-bit random challenge. + * @param[in] auts 112-bit authentication token from client. + * @return + * - 0 on success with sqn filled. + * - -1 on failure. + */ +int milenage_auts(uint64_t *sqn, + uint8_t const opc[MILENAGE_OPC_SIZE], + uint8_t const ki[MILENAGE_KI_SIZE], + uint8_t const rand[MILENAGE_RAND_SIZE], + uint8_t const auts[MILENAGE_AUTS_SIZE]) +{ + uint8_t amf[MILENAGE_AMF_SIZE] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */ + uint8_t ak[MILENAGE_AK_SIZE], mac_s[MILENAGE_MAC_S_SIZE]; + uint8_t sqn_buff[MILENAGE_SQN_SIZE]; + size_t i; + + if (milenage_f2345(NULL, NULL, NULL, NULL, ak, opc, ki, rand)) return -1; + for (i = 0; i < sizeof(sqn_buff); i++) sqn_buff[i] = auts[i] ^ ak[i]; + + if (milenage_f1(NULL, mac_s, opc, ki, rand, sqn_buff, amf) || CRYPTO_memcmp(mac_s, auts + 6, 8) != 0) return -1; + + *sqn = uint48_from_buff(sqn_buff); + + return 0; +} + +/** Generate GSM-Milenage (3GPP TS 55.205) authentication triplet from a quintuplet + * + * @param[out] sres Buffer for SRES = 32-bit SRES. + * @param[out] kc 64-bit Kc. + * @param[in] ik 128-bit integrity. + * @param[in] ck Confidentiality key. + * @param[in] res 64-bit signed response. + */ +void milenage_gsm_from_umts(uint8_t sres[MILENAGE_SRES_SIZE], + uint8_t kc[MILENAGE_KC_SIZE], + uint8_t const ik[MILENAGE_IK_SIZE], + uint8_t const ck[MILENAGE_CK_SIZE], + uint8_t const res[MILENAGE_RES_SIZE]) +{ + int i; + + for (i = 0; i < 8; i++) kc[i] = ck[i] ^ ck[i + 8] ^ ik[i] ^ ik[i + 8]; + +#ifdef GSM_MILENAGE_ALT_SRES + memcpy(sres, res, 4); +#else /* GSM_MILENAGE_ALT_SRES */ + for (i = 0; i < 4; i++) sres[i] = res[i] ^ res[i + 4]; +#endif /* GSM_MILENAGE_ALT_SRES */ +} + +/** Generate GSM-Milenage (3GPP TS 55.205) authentication triplet + * + * @param[out] sres Buffer for SRES = 32-bit SRES. + * @param[out] kc 64-bit Kc. + * @param[in] opc 128-bit operator variant algorithm configuration field (encr.). + * @param[in] ki 128-bit subscriber key. + * @param[in] rand 128-bit random challenge. + * @return + * - 0 on success. + * - -1 on failure. + */ +int milenage_gsm_generate(uint8_t sres[MILENAGE_SRES_SIZE], + uint8_t kc[MILENAGE_KC_SIZE], + uint8_t const opc[MILENAGE_OPC_SIZE], + uint8_t const ki[MILENAGE_KI_SIZE], + uint8_t const rand[MILENAGE_RAND_SIZE]) +{ + uint8_t res[MILENAGE_RES_SIZE], ck[MILENAGE_CK_SIZE], ik[MILENAGE_IK_SIZE]; + + if (milenage_f2345(res, ik, ck, NULL, NULL, opc, ki, rand)) return -1; + + milenage_gsm_from_umts(sres, kc, ik, ck, res); + + return 0; +} + +/** Milenage check + * + * @param[out] ik Buffer for IK = 128-bit integrity key (f4), or NULL. + * @param[out] ck Buffer for CK = 128-bit confidentiality key (f3), or NULL. + * @param[out] res Buffer for RES = 64-bit signed response (f2), or NULL. + * @param[in] auts 112-bit buffer for AUTS. + * @param[in] opc 128-bit operator variant algorithm configuration field (encr.). + * @param[in] ki 128-bit subscriber key. + * @param[in] sqn 48-bit sequence number. + * @param[in] rand 128-bit random challenge. + * @param[in] autn 128-bit authentication token. + * @return + * - 0 on success. + * - -1 on failure. + * - -2 on synchronization failure + */ +int milenage_check(uint8_t ik[MILENAGE_IK_SIZE], + uint8_t ck[MILENAGE_CK_SIZE], + uint8_t res[MILENAGE_RES_SIZE], + uint8_t auts[MILENAGE_AUTS_SIZE], + uint8_t const opc[MILENAGE_OPC_SIZE], + uint8_t const ki[MILENAGE_KI_SIZE], + uint64_t sqn, + uint8_t const rand[MILENAGE_RAND_SIZE], + uint8_t const autn[MILENAGE_AUTN_SIZE]) +{ + + uint8_t mac_a[MILENAGE_MAC_A_SIZE], ak[MILENAGE_AK_SIZE], rx_sqn[MILENAGE_SQN_SIZE]; + uint8_t sqn_buff[MILENAGE_SQN_SIZE]; + const uint8_t *amf; + size_t i; + + uint48_to_buff(sqn_buff, sqn); + + //FR_PROTO_HEX_DUMP(autn, MILENAGE_AUTN_SIZE, "AUTN"); + //FR_PROTO_HEX_DUMP(rand, MILENAGE_RAND_SIZE, "RAND"); + + if (milenage_f2345(res, ck, ik, ak, NULL, opc, ki, rand)) return -1; + + //FR_PROTO_HEX_DUMP(res, MILENAGE_RES_SIZE, "RES"); + //FR_PROTO_HEX_DUMP(ck, MILENAGE_CK_SIZE, "CK"); + //FR_PROTO_HEX_DUMP(ik, MILENAGE_IK_SIZE, "IK"); + //FR_PROTO_HEX_DUMP(ak, MILENAGE_AK_SIZE, "AK"); + + /* AUTN = (SQN ^ AK) || AMF || MAC */ + for (i = 0; i < 6; i++) rx_sqn[i] = autn[i] ^ ak[i]; + //FR_PROTO_HEX_DUMP(rx_sqn, MILENAGE_SQN_SIZE, "SQN"); + + if (CRYPTO_memcmp(rx_sqn, sqn_buff, sizeof(rx_sqn)) <= 0) { + uint8_t auts_amf[MILENAGE_AMF_SIZE] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */ + + if (milenage_f2345(NULL, NULL, NULL, NULL, ak, opc, ki, rand)) return -1; + + //FR_PROTO_HEX_DUMP(ak, sizeof(ak), "AK*"); + for (i = 0; i < 6; i++) auts[i] = sqn_buff[i] ^ ak[i]; + + if (milenage_f1(NULL, auts + 6, opc, ki, rand, sqn_buff, auts_amf) < 0) return -1; + //FR_PROTO_HEX_DUMP(auts, 14, "AUTS"); + return -2; + } + + amf = autn + 6; + //FR_PROTO_HEX_DUMP(amf, MILENAGE_AMF_SIZE, "AMF"); + if (milenage_f1(mac_a, NULL, opc, ki, rand, rx_sqn, amf) < 0) return -1; + + //FR_PROTO_HEX_DUMP(mac_a, MILENAGE_MAC_A_SIZE, "MAC_A"); + + if (CRYPTO_memcmp(mac_a, autn + 8, 8) != 0) { + //FR_PROTO_HEX_DUMP(autn + 8, 8, "Received MAC_A"); + fr_strerror_printf("MAC mismatch"); + return -1; + } + + return 0; +} + +#ifdef TESTING_MILENAGE +/* + * cc milenage.c -g3 -Wall -DHAVE_DLFCN_H -DTESTING_MILENAGE -DWITH_TLS -I../../../../ -I../../../ -I ../base/ -I /usr/local/opt/openssl/include/ -include ../include/build.h -L /usr/local/opt/openssl/lib/ -l ssl -l crypto -l talloc -L ../../../../../build/lib/local/.libs/ -lfreeradius-server -lfreeradius-tls -lfreeradius-util -o test_milenage && ./test_milenage + */ +#include + +void test_set_1(void) +{ + /* + * Inputs + */ + uint8_t ki[] = { 0x46, 0x5b, 0x5c, 0xe8, 0xb1, 0x99, 0xb4, 0x9f, + 0xaa, 0x5f, 0x0a, 0x2e, 0xe2, 0x38, 0xa6, 0xbc }; + uint8_t rand[] = { 0x23, 0x55, 0x3c, 0xbe, 0x96, 0x37, 0xa8, 0x9d, + 0x21, 0x8a, 0xe6, 0x4d, 0xae, 0x47, 0xbf, 0x35 }; + uint8_t sqn[] = { 0xff, 0x9b, 0xb4, 0xd0, 0xb6, 0x07 }; + uint8_t amf[] = { 0xb9, 0xb9 }; + uint8_t op[] = { 0xcd, 0xc2, 0x02, 0xd5, 0x12, 0x3e, 0x20, 0xf6, + 0x2b, 0x6d, 0x67, 0x6a, 0xc7, 0x2c, 0xb3, 0x18 }; + uint8_t opc[] = { 0xcd, 0x63, 0xcb, 0x71, 0x95, 0x4a, 0x9f, 0x4e, + 0x48, 0xa5, 0x99, 0x4e, 0x37, 0xa0, 0x2b, 0xaf }; + + /* + * Outputs + */ + uint8_t opc_out[MILENAGE_OPC_SIZE]; + uint8_t mac_a_out[MILENAGE_MAC_A_SIZE]; + uint8_t mac_s_out[MILENAGE_MAC_S_SIZE]; + uint8_t res_out[MILENAGE_RES_SIZE]; + uint8_t ck_out[MILENAGE_CK_SIZE]; + uint8_t ik_out[MILENAGE_IK_SIZE]; + uint8_t ak_out[MILENAGE_AK_SIZE]; + uint8_t ak_resync_out[MILENAGE_AK_SIZE]; + + /* function 1 */ + uint8_t mac_a[] = { 0x4a, 0x9f, 0xfa, 0xc3, 0x54, 0xdf, 0xaf, 0xb3 }; + /* function 1* */ + uint8_t mac_s[] = { 0x01, 0xcf, 0xaf, 0x9e, 0xc4, 0xe8, 0x71, 0xe9 }; + /* function 2 */ + uint8_t res[] = { 0xa5, 0x42, 0x11, 0xd5, 0xe3, 0xba, 0x50, 0xbf }; + /* function 3 */ + uint8_t ck[] = { 0xb4, 0x0b, 0xa9, 0xa3, 0xc5, 0x8b, 0x2a, 0x05, + 0xbb, 0xf0, 0xd9, 0x87, 0xb2, 0x1b, 0xf8, 0xcb }; + /* function 4 */ + uint8_t ik[] = { 0xf7, 0x69, 0xbc, 0xd7, 0x51, 0x04, 0x46, 0x04, + 0x12, 0x76, 0x72, 0x71, 0x1c, 0x6d, 0x34, 0x41 }; + /* function 5 */ + uint8_t ak[] = { 0xaa, 0x68, 0x9c, 0x64, 0x83, 0x70 }; + /* function 5* */ + uint8_t ak_resync[] = { 0x45, 0x1e, 0x8b, 0xec, 0xa4, 0x3b }; + + int ret = 0; + +/* + fr_debug_lvl = 4; +*/ + ret = milenage_opc_generate(opc_out, op, ki); + TEST_CHECK(ret == 0); + + //FR_PROTO_HEX_DUMP(opc_out, sizeof(opc_out), "opc"); + + TEST_CHECK(memcmp(opc_out, opc, sizeof(opc_out)) == 0); + + if ((milenage_f1(mac_a_out, mac_s_out, opc, ki, rand, sqn, amf) < 0) || + (milenage_f2345(res_out, ik_out, ck_out, ak_out, ak_resync_out, opc, ki, rand) < 0)) ret = -1; + + //FR_PROTO_HEX_DUMP(mac_a, sizeof(mac_a_out), "mac_a"); + //FR_PROTO_HEX_DUMP(mac_s, sizeof(mac_s_out), "mac_s"); + //FR_PROTO_HEX_DUMP(ik_out, sizeof(ik_out), "ik"); + //FR_PROTO_HEX_DUMP(ck_out, sizeof(ck_out), "ck"); + //FR_PROTO_HEX_DUMP(res_out, sizeof(res_out), "res"); + //FR_PROTO_HEX_DUMP(ak_out, sizeof(ak_out), "ak"); + //FR_PROTO_HEX_DUMP(ak_resync_out, sizeof(ak_resync_out), "ak_resync"); + + TEST_CHECK(ret == 0); + TEST_CHECK(memcmp(mac_a_out, mac_a, sizeof(mac_a_out)) == 0); + TEST_CHECK(memcmp(mac_s_out, mac_s, sizeof(mac_s_out)) == 0); + TEST_CHECK(memcmp(res_out, res, sizeof(res_out)) == 0); + TEST_CHECK(memcmp(ck_out, ck, sizeof(ck_out)) == 0); + TEST_CHECK(memcmp(ik_out, ik, sizeof(ik_out)) == 0); + TEST_CHECK(memcmp(ak_out, ak, sizeof(ak_out)) == 0); + TEST_CHECK(memcmp(ak_resync, ak_resync, sizeof(ak_resync_out)) == 0); +} + +void test_set_19(void) +{ + /* + * Inputs + */ + uint8_t ki[] = { 0x51, 0x22, 0x25, 0x02, 0x14, 0xc3, 0x3e, 0x72, + 0x3a, 0x5d, 0xd5, 0x23, 0xfc, 0x14, 0x5f, 0xc0 }; + uint8_t rand[] = { 0x81, 0xe9, 0x2b, 0x6c, 0x0e, 0xe0, 0xe1, 0x2e, + 0xbc, 0xeb, 0xa8, 0xd9, 0x2a, 0x99, 0xdf, 0xa5 }; + uint8_t sqn[] = { 0x16, 0xf3, 0xb3, 0xf7, 0x0f, 0xc2 }; + uint8_t amf[] = { 0xc3, 0xab }; + uint8_t op[] = { 0xc9, 0xe8, 0x76, 0x32, 0x86, 0xb5, 0xb9, 0xff, + 0xbd, 0xf5, 0x6e, 0x12, 0x97, 0xd0, 0x88, 0x7b }; + uint8_t opc[] = { 0x98, 0x1d, 0x46, 0x4c, 0x7c, 0x52, 0xeb, 0x6e, + 0x50, 0x36, 0x23, 0x49, 0x84, 0xad, 0x0b, 0xcf }; + + /* + * Outputs + */ + uint8_t opc_out[MILENAGE_OPC_SIZE]; + uint8_t mac_a_out[MILENAGE_MAC_A_SIZE]; + uint8_t mac_s_out[MILENAGE_MAC_S_SIZE]; + uint8_t res_out[MILENAGE_RES_SIZE]; + uint8_t ck_out[MILENAGE_CK_SIZE]; + uint8_t ik_out[MILENAGE_IK_SIZE]; + uint8_t ak_out[MILENAGE_AK_SIZE]; + uint8_t ak_resync_out[MILENAGE_AK_SIZE]; + + /* function 1 */ + uint8_t mac_a[] = { 0x2a, 0x5c, 0x23, 0xd1, 0x5e, 0xe3, 0x51, 0xd5 }; + /* function 1* */ + uint8_t mac_s[] = { 0x62, 0xda, 0xe3, 0x85, 0x3f, 0x3a, 0xf9, 0xd2 }; + /* function 2 */ + uint8_t res[] = { 0x28, 0xd7, 0xb0, 0xf2, 0xa2, 0xec, 0x3d, 0xe5 }; + /* function 3 */ + uint8_t ck[] = { 0x53, 0x49, 0xfb, 0xe0, 0x98, 0x64, 0x9f, 0x94, + 0x8f, 0x5d, 0x2e, 0x97, 0x3a, 0x81, 0xc0, 0x0f }; + /* function 4 */ + uint8_t ik[] = { 0x97, 0x44, 0x87, 0x1a, 0xd3, 0x2b, 0xf9, 0xbb, + 0xd1, 0xdd, 0x5c, 0xe5, 0x4e, 0x3e, 0x2e, 0x5a }; + /* function 5 */ + uint8_t ak[] = { 0xad, 0xa1, 0x5a, 0xeb, 0x7b, 0xb8 }; + /* function 5* */ + uint8_t ak_resync[] = { 0xd4, 0x61, 0xbc, 0x15, 0x47, 0x5d }; + + int ret = 0; + +/* + fr_debug_lvl = 4; +*/ + + ret = milenage_opc_generate(opc_out, op, ki); + TEST_CHECK(ret == 0); + + //FR_PROTO_HEX_DUMP(opc_out, sizeof(opc_out), "opc"); + + TEST_CHECK(memcmp(opc_out, opc, sizeof(opc_out)) == 0); + + if ((milenage_f1(mac_a_out, mac_s_out, opc, ki, rand, sqn, amf) < 0) || + (milenage_f2345(res_out, ik_out, ck_out, ak_out, ak_resync_out, opc, ki, rand) < 0)) ret = -1; + + //FR_PROTO_HEX_DUMP(mac_a, sizeof(mac_a_out), "mac_a"); + //FR_PROTO_HEX_DUMP(mac_s, sizeof(mac_s_out), "mac_s"); + //FR_PROTO_HEX_DUMP(ik_out, sizeof(ik_out), "ik"); + //FR_PROTO_HEX_DUMP(ck_out, sizeof(ck_out), "ck"); + //FR_PROTO_HEX_DUMP(res_out, sizeof(res_out), "res"); + //FR_PROTO_HEX_DUMP(ak_out, sizeof(ak_out), "ak"); + //FR_PROTO_HEX_DUMP(ak_resync_out, sizeof(ak_resync_out), "ak_resync"); + + TEST_CHECK(ret == 0); + TEST_CHECK(memcmp(mac_a_out, mac_a, sizeof(mac_a_out)) == 0); + TEST_CHECK(memcmp(mac_s_out, mac_s, sizeof(mac_s_out)) == 0); + TEST_CHECK(memcmp(res_out, res, sizeof(res_out)) == 0); + TEST_CHECK(memcmp(ck_out, ck, sizeof(ck_out)) == 0); + TEST_CHECK(memcmp(ik_out, ik, sizeof(ik_out)) == 0); + TEST_CHECK(memcmp(ak_out, ak, sizeof(ak_out)) == 0); + TEST_CHECK(memcmp(ak_resync, ak_resync, sizeof(ak_resync_out)) == 0); +} + +TEST_LIST = { + { "test_set_1", test_set_1 }, + { "test_set_19", test_set_19 }, + { NULL } +}; +#endif diff --git a/src/modules/rlm_wimax/milenage.h b/src/modules/rlm_wimax/milenage.h new file mode 100644 index 0000000000..758383aba2 --- /dev/null +++ b/src/modules/rlm_wimax/milenage.h @@ -0,0 +1,128 @@ +#pragma once +/** + * @file src/modules/rlm_wimax/milenage.h + * @brief 3GPP AKA - Milenage algorithm (3GPP TS 35.205, .206, .207, .208) + * + * This file implements an example authentication algorithm defined for 3GPP + * AKA. This can be used to implement a simple HLR/AuC into hlr_auc_gw to allow + * EAP-AKA to be tested properly with real USIM cards. + * + * This implementations assumes that the r1..r5 and c1..c5 constants defined in + * TS 35.206 are used, i.e., r1=64, r2=0, r3=32, r4=64, r5=96, c1=00..00, + * c2=00..01, c3=00..02, c4=00..04, c5=00..08. The block cipher is assumed to + * be AES (Rijndael). + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + * + * @copyright 2017 The FreeRADIUS server project + * @copyright 2006-2007 (j@w1.fi) + */ +#include + +/* + * Inputs + */ +#define MILENAGE_KI_SIZE 16 //!< Subscriber key. +#define MILENAGE_OP_SIZE 16 //!< Operator code (unique to the operator) +#define MILENAGE_OPC_SIZE 16 //!< Derived operator code (unique to the operator and subscriber). +#define MILENAGE_AMF_SIZE 2 //!< Authentication management field. +#define MILENAGE_SQN_SIZE 6 //!< Sequence number. +#define MILENAGE_RAND_SIZE 16 //!< Random challenge. + +/* + * UMTS Outputs + */ +#define MILENAGE_AK_SIZE 6 //!< Anonymisation key. +#define MILENAGE_AUTN_SIZE 16 //!< Network authentication key. +#define MILENAGE_IK_SIZE 16 //!< Integrity key. +#define MILENAGE_CK_SIZE 16 //!< Ciphering key. +#define MILENAGE_RES_SIZE 8 +#define MILENAGE_AUTS_SIZE 14 + +/* + * GSM (COMP128-4) outputs + */ +#define MILENAGE_SRES_SIZE 4 +#define MILENAGE_KC_SIZE 8 + +/** Copy a 48bit value from a 64bit integer into a uint8_t buff in big endian byte order + * + * There may be fast ways of doing this, but this is the *correct* + * way, and does not make assumptions about how integers are laid + * out in memory. + * + * @param[out] out 6 byte butter to store value. + * @param[in] i integer value. + * @return pointer to out. + */ +static inline uint8_t *uint48_to_buff(uint8_t out[6], uint64_t i) +{ + out[0] = (i & 0xff0000000000) >> 40; + out[1] = (i & 0x00ff00000000) >> 32; + out[2] = (i & 0x0000ff000000) >> 24; + out[3] = (i & 0x000000ff0000) >> 16; + out[4] = (i & 0x00000000ff00) >> 8; + out[5] = (i & 0x0000000000ff); + + return out; +} + +/** Convert a 48bit big endian value into a unsigned 64bit integer + * + */ +static inline uint64_t uint48_from_buff(uint8_t const in[6]) +{ + uint64_t i = 0; + + i |= ((uint64_t)in[0]) << 40; + i |= ((uint64_t)in[1]) << 32; + i |= ((uint32_t)in[2]) << 24; + i |= ((uint32_t)in[3]) << 16; + i |= ((uint16_t)in[4]) << 8; + i |= in[5]; + + return i; +} + +int milenage_opc_generate(uint8_t opc[MILENAGE_OPC_SIZE], + uint8_t const op[MILENAGE_OP_SIZE], + uint8_t const ki[MILENAGE_KI_SIZE]); + +int milenage_umts_generate(uint8_t autn[MILENAGE_AUTN_SIZE], + uint8_t ik[MILENAGE_IK_SIZE], + uint8_t ck[MILENAGE_CK_SIZE], + uint8_t ak[MILENAGE_AK_SIZE], + uint8_t res[MILENAGE_RES_SIZE], + uint8_t const opc[MILENAGE_OPC_SIZE], + uint8_t const amf[MILENAGE_AMF_SIZE], + uint8_t const ki[MILENAGE_KI_SIZE], + uint64_t sqn, + uint8_t const rand[MILENAGE_RAND_SIZE]); + +int milenage_auts(uint64_t *sqn, + uint8_t const opc[MILENAGE_OPC_SIZE], + uint8_t const ki[MILENAGE_KI_SIZE], + uint8_t const rand[MILENAGE_RAND_SIZE], + uint8_t const auts[MILENAGE_AUTS_SIZE]); + +void milenage_gsm_from_umts(uint8_t sres[MILENAGE_SRES_SIZE], + uint8_t kc[MILENAGE_KC_SIZE], + uint8_t const ik[MILENAGE_IK_SIZE], + uint8_t const ck[MILENAGE_CK_SIZE], + uint8_t const res[MILENAGE_RES_SIZE]); + +int milenage_gsm_generate(uint8_t sres[MILENAGE_SRES_SIZE], uint8_t kc[MILENAGE_KC_SIZE], + uint8_t const opc[MILENAGE_OPC_SIZE], + uint8_t const ki[MILENAGE_KI_SIZE], + uint8_t const rand[MILENAGE_RAND_SIZE]); + +int milenage_check(uint8_t ik[MILENAGE_IK_SIZE], + uint8_t ck[MILENAGE_CK_SIZE], + uint8_t res[MILENAGE_RES_SIZE], + uint8_t auts[MILENAGE_AUTS_SIZE], + uint8_t const opc[MILENAGE_OPC_SIZE], + uint8_t const ki[MILENAGE_KI_SIZE], + uint64_t sqn, + uint8_t const rand[MILENAGE_RAND_SIZE], + uint8_t const autn[MILENAGE_AUTN_SIZE]); diff --git a/src/modules/rlm_wimax/rlm_wimax.c b/src/modules/rlm_wimax/rlm_wimax.c index f0fb394fcd..d2125eb3a5 100644 --- a/src/modules/rlm_wimax/rlm_wimax.c +++ b/src/modules/rlm_wimax/rlm_wimax.c @@ -26,16 +26,43 @@ USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ #include #include +#include "milenage.h" #ifdef HAVE_OPENSSL_HMAC_H #include #endif +#include + +#define WIMAX_EPSAKA_RAND_SIZE 16 +#define WIMAX_EPSAKA_KI_SIZE 16 +#define WIMAX_EPSAKA_OPC_SIZE 16 +#define WIMAX_EPSAKA_AMF_SIZE 2 +#define WIMAX_EPSAKA_SQN_SIZE 6 +#define WIMAX_EPSAKA_MAC_A_SIZE 8 +#define WIMAX_EPSAKA_MAC_S_SIZE 8 +#define WIMAX_EPSAKA_XRES_SIZE 8 +#define WIMAX_EPSAKA_CK_SIZE 16 +#define WIMAX_EPSAKA_IK_SIZE 16 +#define WIMAX_EPSAKA_AK_SIZE 6 +#define WIMAX_EPSAKA_AK_RESYNC_SIZE 6 +#define WIMAX_EPSAKA_KK_SIZE 32 +#define WIMAX_EPSAKA_KS_SIZE 14 +#define WIMAX_EPSAKA_PLMN_SIZE 3 +#define WIMAX_EPSAKA_KASME_SIZE 32 +#define WIMAX_EPSAKA_AUTN_SIZE 16 +#define WIMAX_EPSAKA_AUTS_SIZE 14 + /* * FIXME: Fix the build system to create definitions from names. */ typedef struct rlm_wimax_t { bool delete_mppe_keys; + + DICT_ATTR const *resync_info; + DICT_ATTR const *xres; + DICT_ATTR const *autn; + DICT_ATTR const *kasme; } rlm_wimax_t; /* @@ -52,15 +79,37 @@ static const CONF_PARSER module_config[] = { CONF_PARSER_TERMINATOR }; +/* + * Print hex values in a readable format for debugging + * Example: + * FOO: 00 11 AA 22 00 FF + */ +static void rdebug_hex(REQUEST *request, char const *prefix, uint8_t const *data, int len) +{ + int i; + char buffer[256]; /* large enough for largest len */ + + /* + * Leave a trailing space, we don't really care about that. + */ + for (i = 0; i < len; i++) { + snprintf(buffer + i * 3, sizeof(buffer) - i * 3, "%02x ", data[i]); + } + + RDEBUG("%s %s", prefix, buffer); +} +#define RDEBUG_HEX if (rad_debug_lvl) rdebug_hex + /* * Find the named user in this modules database. Create the set * of attribute-value pairs to check and reply with for this user * from the database. The authentication code only needs to check * the password, the rest is done here. */ -static rlm_rcode_t CC_HINT(nonnull) mod_authorize(UNUSED void *instance, REQUEST *request) +static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) { VALUE_PAIR *vp; + rlm_wimax_t *inst = instance; /* * Fix Calling-Station-Id. Damn you, WiMAX! @@ -93,10 +142,124 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(UNUSED void *instance, REQUEST return RLM_MODULE_OK; } + /* + * Check for attr WiMAX-Re-synchronization-Info + * which contains the concatenation of RAND and AUTS + * + * If it is present then we proceed to verify the SIM and + * extract the new value of SQN + */ + VALUE_PAIR *resync_info, *ki, *opc, *sqn, *rand; + int m_ret; + + /* Look for the Re-synchronization-Info attribute in the request */ + resync_info = fr_pair_find_by_da(request->packet->vps, inst->resync_info, TAG_ANY); + if (resync_info && (resync_info->vp_length < (WIMAX_EPSAKA_RAND_SIZE + WIMAX_EPSAKA_AUTS_SIZE))) { + RWDEBUG("Found request:WiMAX-Re-synchronization-Info with incorrect length: Ignoring it"); + resync_info = NULL; + } + + /* + * These are the private keys which should be added to the control + * list after looking them up in a database by IMSI + * + * We grab them from the control list here + */ + ki = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_KI, 0, TAG_ANY); + if (ki && (ki->vp_length < MILENAGE_CK_SIZE)) { + RWDEBUG("Found config:WiMAX-SIM-Ki with incorrect length: Ignoring it"); + ki = NULL; + } + + opc = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_OPC, 0, TAG_ANY); + if (opc && (opc->vp_length < MILENAGE_IK_SIZE)) { + RWDEBUG("Found config:WiMAX-SIM-OPC with incorrect length: Ignoring it"); + opc = NULL; + } + + /* If we have resync info (RAND and AUTS), Ki and OPc then we can proceed */ + if (resync_info && ki && opc) { + uint64_t sqn_bin; + uint8_t rand_bin[WIMAX_EPSAKA_RAND_SIZE]; + uint8_t auts_bin[WIMAX_EPSAKA_AUTS_SIZE]; + + RDEBUG("Found WiMAX-Re-synchronization-Info. Proceeding with SQN resync"); + + /* Split Re-synchronization-Info into seperate RAND and AUTS */ + + memcpy(rand_bin, &resync_info->vp_octets[0], WIMAX_EPSAKA_RAND_SIZE); + memcpy(auts_bin, &resync_info->vp_octets[WIMAX_EPSAKA_RAND_SIZE], WIMAX_EPSAKA_AUTS_SIZE); + + RDEBUG_HEX(request, "RAND ", rand_bin, WIMAX_EPSAKA_RAND_SIZE); + RDEBUG_HEX(request, "AUTS ", auts_bin, WIMAX_EPSAKA_AUTS_SIZE); + + /* + * This procedure uses the secret keys Ki and OPc to authenticate + * the SIM and extract the SQN + */ + m_ret = milenage_auts(&sqn_bin, opc->vp_octets, ki->vp_octets, rand_bin, auts_bin); + + /* + * If the SIM verification fails then we can't go any further as + * we don't have the keys. And that probably means something bad + * is happening so we bail out now + */ + if (m_ret < 0) { + RDEBUG("SIM verification failed"); + return RLM_MODULE_REJECT; + } + + /* + * If we got this far it means have got a new SQN and RAND + * so we store them in: + * control:WiMAX-SIM-SQN + * control:WiMAX-SIM-RAND + * + * From there they can be grabbed by unlang and used later + */ + + /* SQN is six bytes so we extract what we need from the 64 bit variable */ + uint8_t sqn_bin_arr[WIMAX_EPSAKA_SQN_SIZE] = { + (sqn_bin & 0x0000FF0000000000ull) >> 40, + (sqn_bin & 0x000000FF00000000ull) >> 32, + (sqn_bin & 0x00000000FF000000ull) >> 24, + (sqn_bin & 0x0000000000FF0000ull) >> 16, + (sqn_bin & 0x000000000000FF00ull) >> 8, + (sqn_bin & 0x00000000000000FFull) >> 0 + }; + + /* Add SQN to control:WiMAX-SIM-SQN */ + sqn = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_SQN, 0, TAG_ANY); + if (sqn && (sqn->vp_length < WIMAX_EPSAKA_SQN_SIZE)) { + RWDEBUG("Found config:WiMAX-SIM-SQN with incorrect length: Ignoring it"); + sqn = NULL; + } + + if (!sqn) { + MEM(sqn = pair_make_config("WiMAX-SIM-SQN", NULL, T_OP_SET)); + fr_pair_value_memcpy(sqn, sqn_bin_arr, WIMAX_EPSAKA_SQN_SIZE); + } + RDEBUG_HEX(request, "SQN ", sqn->vp_octets, WIMAX_EPSAKA_SQN_SIZE); + + /* Add RAND to control:WiMAX-SIM-RAND */ + rand = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_RAND, 0, TAG_ANY); + if (rand && (rand->vp_length < WIMAX_EPSAKA_RAND_SIZE)) { + RWDEBUG("Found config:WiMAX-SIM-RAND with incorrect length: Ignoring it"); + rand = NULL; + } + + if (!rand) { + MEM(rand = pair_make_config("WiMAX-SIM-RAND", NULL, T_OP_SET)); + fr_pair_value_memcpy(rand, rand_bin, WIMAX_EPSAKA_RAND_SIZE); + } + RDEBUG_HEX(request, "RAND ", rand->vp_octets, WIMAX_EPSAKA_RAND_SIZE); + + return RLM_MODULE_UPDATED; + } + return RLM_MODULE_NOOP; } - /* * Massage the request before recording it or proxying it */ @@ -105,21 +268,14 @@ static rlm_rcode_t CC_HINT(nonnull) mod_preacct(void *instance, REQUEST *request return mod_authorize(instance, request); } -/* - * Write accounting information to this modules database. - */ -static rlm_rcode_t CC_HINT(nonnull) mod_accounting(UNUSED void *instance, UNUSED REQUEST *request) -{ - return RLM_MODULE_OK; -} /* - * Generate the keys after the user has been authenticated. + * This function generates the keys for old style WiMAX (v1 to v2.0) */ -static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request) +static int mip_keys_generate(void *instance, REQUEST *request, VALUE_PAIR *msk, VALUE_PAIR *emsk) { rlm_wimax_t *inst = instance; - VALUE_PAIR *msk, *emsk, *vp; + VALUE_PAIR *vp; VALUE_PAIR *mn_nai, *ip, *fa_rk; HMAC_CTX *hmac; unsigned int rk1_len, rk2_len, rk_len; @@ -128,13 +284,6 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque uint8_t mip_rk_1[EVP_MAX_MD_SIZE], mip_rk_2[EVP_MAX_MD_SIZE]; uint8_t mip_rk[2 * EVP_MAX_MD_SIZE]; - msk = fr_pair_find_by_num(request->reply->vps, PW_EAP_MSK, 0, TAG_ANY); - emsk = fr_pair_find_by_num(request->reply->vps, PW_EAP_EMSK, 0, TAG_ANY); - if (!msk || !emsk) { - RDEBUG("No EAP-MSK or EAP-EMSK. Cannot create WiMAX keys"); - return RLM_MODULE_NOOP; - } - /* * If we delete the MS-MPPE-*-Key attributes, then add in * the WiMAX-MSK so that the client has a key available. @@ -143,10 +292,8 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque fr_pair_delete_by_num(&request->reply->vps, 16, VENDORPEC_MICROSOFT, TAG_ANY); fr_pair_delete_by_num(&request->reply->vps, 17, VENDORPEC_MICROSOFT, TAG_ANY); - vp = pair_make_reply("WiMAX-MSK", NULL, T_OP_EQ); - if (vp) { - fr_pair_value_memcpy(vp, msk->vp_octets, msk->vp_length); - } + MEM(vp = pair_make_reply("WiMAX-MSK", NULL, T_OP_EQ)); + fr_pair_value_memcpy(vp, msk->vp_octets, msk->vp_length); } /* @@ -162,6 +309,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque */ hmac = HMAC_CTX_new(); HMAC_Init_ex(hmac, emsk->vp_octets, emsk->vp_length, EVP_sha256(), NULL); + rk1_len = SHA256_DIGEST_LENGTH; HMAC_Update(hmac, &usage_data[0], sizeof(usage_data)); HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); @@ -173,6 +321,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque HMAC_Update(hmac, (uint8_t const *) &mip_rk_1, rk1_len); HMAC_Update(hmac, &usage_data[0], sizeof(usage_data)); + rk2_len = SHA256_DIGEST_LENGTH; HMAC_Final(hmac, &mip_rk_2[0], &rk2_len); memcpy(mip_rk, mip_rk_1, rk1_len); @@ -185,6 +334,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque HMAC_Init_ex(hmac, mip_rk, rk_len, EVP_sha256(), NULL); HMAC_Update(hmac, (uint8_t const *) "SPI CMIP PMIP", 12); + rk1_len = SHA256_DIGEST_LENGTH; HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); /* @@ -195,16 +345,8 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque (mip_rk_1[2] << 8) | mip_rk_1[3]); if (mip_spi < 256) mip_spi += 256; - if (rad_debug_lvl) { - int len = rk_len; - char buffer[512]; - - if (len > 128) len = 128; /* buffer size */ - - fr_bin2hex(buffer, mip_rk, len); - RDEBUG("MIP-RK = 0x%s", buffer); - RDEBUG("MIP-SPI = %08x", ntohl(mip_spi)); - } + RDEBUG_HEX(request, "MIP-RK ", mip_rk, rk_len); + RDEBUG("MIP-SPI = %08x", ntohl(mip_spi)); /* * FIXME: Perform SPI collision prevention @@ -250,6 +392,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque HMAC_Update(hmac, (uint8_t const *) "PMIP4 MN HA", 11); HMAC_Update(hmac, (uint8_t const *) &ip->vp_ipaddr, 4); HMAC_Update(hmac, (uint8_t const *) &mn_nai->vp_strvalue, mn_nai->vp_length); + rk1_len = SHA1_DIGEST_LENGTH; HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); /* @@ -300,6 +443,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque HMAC_Update(hmac, (uint8_t const *) "CMIP4 MN HA", 11); HMAC_Update(hmac, (uint8_t const *) &ip->vp_ipaddr, 4); HMAC_Update(hmac, (uint8_t const *) &mn_nai->vp_strvalue, mn_nai->vp_length); + rk1_len = SHA1_DIGEST_LENGTH; HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); /* @@ -350,6 +494,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque HMAC_Update(hmac, (uint8_t const *) "CMIP6 MN HA", 11); HMAC_Update(hmac, (uint8_t const *) &ip->vp_ipv6addr, 16); HMAC_Update(hmac, (uint8_t const *) &mn_nai->vp_strvalue, mn_nai->vp_length); + rk1_len = SHA1_DIGEST_LENGTH; HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); /* @@ -396,6 +541,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque HMAC_Update(hmac, (uint8_t const *) "FA-RK", 5); + rk1_len = SHA1_DIGEST_LENGTH; HMAC_Final(hmac, &mip_rk_1[0], &rk1_len); fr_pair_value_memcpy(fa_rk, &mip_rk_1[0], rk1_len); @@ -455,6 +601,221 @@ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *reque return RLM_MODULE_UPDATED; } +/* + * Generate the EPS-AKA authentication vector + * + * These are the keys needed for new style WiMAX (LTE / 3gpp authentication), + for WiMAX v2.1 + */ +static rlm_rcode_t aka_keys_generate(REQUEST *request, rlm_wimax_t const *inst, VALUE_PAIR *ki, VALUE_PAIR *opc, + VALUE_PAIR *amf, VALUE_PAIR *sqn, VALUE_PAIR *plmn) +{ + size_t i; + VALUE_PAIR *rand_previous, *rand, *xres, *autn, *kasme; + + /* + * For most authentication requests we need to generate a fresh RAND + * + * The exception is after SQN re-syncronisation - in this case we + * get RAND in the request, and this module if called in authorize should + * have put it in control:WiMAX-SIM-RAND so we can grab it from there) + */ + rand_previous = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_RAND, 0, TAG_ANY); + if (rand_previous && (rand_previous->vp_length < WIMAX_EPSAKA_RAND_SIZE)) { + RWDEBUG("Found config:WiMAX-SIM-Rand with incorrect size. Ignoring it."); + rand_previous = NULL; + } + + MEM(rand = pair_make_reply("WiMAX-E-UTRAN-Vector-RAND", NULL, T_OP_SET)); + if (!rand_previous) { + uint32_t lvalue; + uint8_t buffer[WIMAX_EPSAKA_RAND_SIZE]; + + for (i = 0; i < (WIMAX_EPSAKA_RAND_SIZE / 4); i++) { + lvalue = fr_rand(); + memcpy(buffer + i * 4, &lvalue, sizeof(lvalue)); + } + + fr_pair_value_memcpy(rand, buffer, WIMAX_EPSAKA_RAND_SIZE); + + } else { + fr_pair_value_memcpy(rand, rand_previous->vp_octets, WIMAX_EPSAKA_RAND_SIZE); + } + + /* + * Feed AMF, Ki, SQN and RAND into the Milenage algorithm (f1, f2, f3, f4, f5) + * which returns AUTN, AK, CK, IK, XRES. + */ + uint8_t xres_bin[WIMAX_EPSAKA_XRES_SIZE]; + uint8_t ck_bin[WIMAX_EPSAKA_CK_SIZE]; + uint8_t ik_bin[WIMAX_EPSAKA_IK_SIZE]; + uint8_t ak_bin[WIMAX_EPSAKA_AK_SIZE]; + uint8_t autn_bin[WIMAX_EPSAKA_AUTN_SIZE]; + + /* But first convert uint8 SQN to uint64 */ + uint64_t sqn_bin = 0x0000000000000000; + for (i = 0; i < sqn->vp_length; ++i) sqn_bin = (sqn_bin << 8) | sqn->vp_octets[i]; + + if (!opc || (opc->vp_length < MILENAGE_OPC_SIZE)) { + RWDEBUG("Found config:WiMAX-SIM-OPC with incorrect size. Ignoring it"); + return RLM_MODULE_NOOP; + } + if (!amf || (amf->vp_length < MILENAGE_AMF_SIZE)) { + RWDEBUG("Found config:WiMAX-SIM-AMF with incorrect size. Ignoring it"); + return RLM_MODULE_NOOP; + } + if (!ki || (ki->vp_length < MILENAGE_KI_SIZE)) { + RWDEBUG("Found config:WiMAX-SIM-KI with incorrect size. Ignoring it"); + return RLM_MODULE_NOOP; + } + + /* Call milenage */ + milenage_umts_generate(autn_bin, ik_bin, ck_bin, ak_bin, xres_bin, opc->vp_octets, + amf->vp_octets, ki->vp_octets, sqn_bin, rand->vp_octets); + + /* + * Now we genertate KASME + * + * Officially described in 33401-g30.doc section A.2 + * But an easier to read explanation can be found at: + * https://medium.com/uw-ictd/lte-authentication-2d0810a061ec + * + */ + + /* k = CK || IK */ + uint8_t kk_bin[WIMAX_EPSAKA_KK_SIZE]; + memcpy(kk_bin, ck_bin, sizeof(ck_bin)); + memcpy(kk_bin + sizeof(ck_bin), ik_bin, sizeof(ik_bin)); + + /* Initialize a 14 byte buffer s */ + uint8_t ks_bin[WIMAX_EPSAKA_KS_SIZE]; + + /* Assign the first byte of s as 0x10 */ + ks_bin[0] = 0x10; + + /* Copy the 3 bytes of PLMN into s */ + memcpy(ks_bin + 1, plmn->vp_octets, 3); + + /* Assign 5th and 6th byte as 0x00 and 0x03 */ + ks_bin[4] = 0x00; + ks_bin[5] = 0x03; + + /* Assign the next 6 bytes as SQN XOR AK */ + for (i = 0; i < 6; i++) { + ks_bin[i+6] = sqn->vp_octets[i] ^ ak_bin[i]; + } + + /* Assign the last two bytes as 0x00 and 0x06 */ + ks_bin[12] = 0x00; + ks_bin[13] = 0x06; + + /* Perform an HMAC-SHA256 using Key k from step 1 and s as the message. */ + uint8_t kasme_bin[WIMAX_EPSAKA_KASME_SIZE]; + HMAC_CTX *hmac; + unsigned int kasme_len = sizeof(kasme_bin); + + hmac = HMAC_CTX_new(); + HMAC_Init_ex(hmac, kk_bin, sizeof(kk_bin), EVP_sha256(), NULL); + HMAC_Update(hmac, ks_bin, sizeof(ks_bin)); + kasme_len = SHA256_DIGEST_LENGTH; + HMAC_Final(hmac, &kasme_bin[0], &kasme_len); + HMAC_CTX_free(hmac); + + /* + * Add reply attributes XRES, AUTN and KASME (RAND we added earlier) + * + * Note that we can't call fr_pair_find_by_num(), as + * these attributes are buried deep inside of the WiMAX + * hierarchy. + */ + xres = fr_pair_find_by_da(request->reply->vps, inst->xres, TAG_ANY); + if (!xres) { + MEM(xres = pair_make_reply("WiMAX-E-UTRAN-Vector-XRES", NULL, T_OP_SET)); + fr_pair_value_memcpy(xres, xres_bin, WIMAX_EPSAKA_XRES_SIZE); + } + + autn = fr_pair_find_by_da(request->reply->vps, inst->autn, TAG_ANY); + if (!autn) { + MEM(autn = pair_make_reply("WiMAX-E-UTRAN-Vector-AUTN", NULL, T_OP_SET)); + fr_pair_value_memcpy(autn, autn_bin, WIMAX_EPSAKA_AUTN_SIZE); + } + + kasme = fr_pair_find_by_da(request->reply->vps, inst->kasme, TAG_ANY); + if (!kasme) { + MEM(kasme = pair_make_reply("WiMAX-E-UTRAN-Vector-KASME", NULL, T_OP_SET)); + fr_pair_value_memcpy(kasme, kasme_bin, WIMAX_EPSAKA_KASME_SIZE); + } + + /* Print keys to log for debugging */ + if (rad_debug_lvl) { + RDEBUG("-------- Milenage in --------"); + RDEBUG_HEX(request, "OPc ", opc->vp_octets, opc->vp_length); + RDEBUG_HEX(request, "Ki ", ki->vp_octets, ki->vp_length); + RDEBUG_HEX(request, "RAND ", rand->vp_octets, rand->vp_length); + RDEBUG_HEX(request, "SQN ", sqn->vp_octets, sqn->vp_length); + RDEBUG_HEX(request, "AMF ", amf->vp_octets, amf->vp_length); + RDEBUG("-------- Milenage out -------"); + RDEBUG_HEX(request, "XRES ", xres->vp_octets, xres->vp_length); + RDEBUG_HEX(request, "Ck ", ck_bin, sizeof(ck_bin)); + RDEBUG_HEX(request, "Ik ", ik_bin, sizeof(ik_bin)); + RDEBUG_HEX(request, "Ak ", ak_bin, sizeof(ak_bin)); + RDEBUG_HEX(request, "AUTN ", autn->vp_octets, autn->vp_length); + RDEBUG("-----------------------------"); + RDEBUG_HEX(request, "Kk ", kk_bin, sizeof(kk_bin)); + RDEBUG_HEX(request, "Ks ", ks_bin, sizeof(ks_bin)); + RDEBUG_HEX(request, "KASME ", kasme->vp_octets, kasme->vp_length); + } + + return RLM_MODULE_UPDATED; +} + +/* + * Generate the keys after the user has been authenticated. + */ +static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request) +{ + VALUE_PAIR *msk, *emsk, *ki, *opc, *amf, *sqn, *plmn; + + /* + * If we have MSK and EMSK then assume we want MIP keys + * Else if we have the SIM keys then we want the EPS-AKA vector + */ + + msk = fr_pair_find_by_num(request->reply->vps, PW_EAP_MSK, 0, TAG_ANY); + emsk = fr_pair_find_by_num(request->reply->vps, PW_EAP_EMSK, 0, TAG_ANY); + + if (msk && emsk) { + RDEBUG("MSK and EMSK found. Generating MIP keys"); + return mip_keys_generate(instance, request, msk, emsk); + } + + ki = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_KI, 0, TAG_ANY); + opc = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_OPC, 0, TAG_ANY); + amf = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_AMF, 0, TAG_ANY); + sqn = fr_pair_find_by_num(request->config, PW_WIMAX_SIM_SQN, 0, TAG_ANY); + plmn = fr_pair_find_by_num(request->packet->vps, 146, VENDORPEC_WIMAX, TAG_ANY); + + if (ki && opc && amf && sqn && plmn) { + RDEBUG("AKA attributes found. Generating AKA keys."); + return aka_keys_generate(request, instance, ki, opc, amf, sqn, plmn); + } + + RDEBUG("Input keys not found. Cannot create WiMAX keys"); + return RLM_MODULE_NOOP; +} + + +static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance) +{ + rlm_wimax_t *inst = instance; + + inst->resync_info = dict_attrbyname("WiMAX-Re-synchronization-Info"); + inst->xres = dict_attrbyname("WiMAX-E-UTRAN-Vector-XRES"); + inst->autn = dict_attrbyname("WiMAX-E-UTRAN-Vector-AUTN"); + inst->kasme = dict_attrbyname("WiMAX-E-UTRAN-Vector-KASME"); + + return 0; +} /* * The module name should be the only globally exported symbol. @@ -472,10 +833,10 @@ module_t rlm_wimax = { .type = RLM_TYPE_THREAD_SAFE, .inst_size = sizeof(rlm_wimax_t), .config = module_config, + .instantiate = mod_instantiate, .methods = { [MOD_AUTHORIZE] = mod_authorize, [MOD_PREACCT] = mod_preacct, - [MOD_ACCOUNTING] = mod_accounting, [MOD_POST_AUTH] = mod_post_auth }, }; diff --git a/src/tests/keywords/md4 b/src/tests/keywords/md4 new file mode 100644 index 0000000000..7e9b1ffcdf --- /dev/null +++ b/src/tests/keywords/md4 @@ -0,0 +1,58 @@ +# +# PRE: update if +# +update reply { + Filter-Id := "filter" +} + +update { + control:Cleartext-Password := 'hello' + request:Tmp-String-0 := "This is a string\n" + request:Tmp-Octets-0 := 0x000504030201 + request:Tmp-String-1 := "what do ya want for nothing?" + request:Tmp-String-2 := "Jefe" +} + +# +# Put "This is a string" into a file and call "md5sum" on it. +# You should get this string. +# +if ("%{md4:This is a string\n}" != '1f60d5cd85e17bfbdda7c923822f060c') { + update reply { + Filter-Id += 'fail 1' + } +} + +if ("%{md4:&Tmp-String-0}" != '1f60d5cd85e17bfbdda7c923822f060c') { + update reply { + Filter-Id += 'fail 2' + } +} + +if ("%{md4:&request:Tmp-String-0}" != '1f60d5cd85e17bfbdda7c923822f060c') { + update reply { + Filter-Id += 'fail 3' + } +} + +if ("%{md4:%{request:Tmp-String-0}}" != '1f60d5cd85e17bfbdda7c923822f060c') { + update reply { + Filter-Id += 'fail 4' + } +} + +# +# MD4 should also be able to cope with references to octet attributes +# +if ("%{md4:&request:Tmp-Octets-0}" != 'ac3ed17b3cf19ec38352ec534a932fc6') { + update reply { + Filter-Id += 'fail 5' + } +} + +if ("%{md4:&Tmp-String-1}" != 'f7b44afb9cfdc877aa99d44654fe808b') { + update reply { + Filter-Id += 'fail 6' + } +} +