From: Nikos Mavrogiannopoulos Date: Sun, 10 Jan 2010 13:21:42 +0000 (+0100) Subject: Added safe renegotiation patch from Steve Dispensa, modified to suit gnutls X-Git-Tag: gnutls_2_9_10~177 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=1a338cbaaeec11d958de8da4d1ae036979fccf3e;p=thirdparty%2Fgnutls.git Added safe renegotiation patch from Steve Dispensa, modified to suit gnutls code style and error checking. Modified to conform to draft-ietf-tls-renegotiation-03.txt. gnutls-cli will search input for **RENEGOTIATION** to perform a renegotiation and gnutls-serv will perform one if requested. --- diff --git a/doc/manpages/gnutls-cli.1 b/doc/manpages/gnutls-cli.1 index 82823a3466..6ca8da4266 100644 --- a/doc/manpages/gnutls-cli.1 +++ b/doc/manpages/gnutls-cli.1 @@ -78,6 +78,9 @@ Special keywords: "%SSL3_RECORD_VERSION" force SSL3.0 record version in the first client hello. This is to avoid buggy servers from terminating connection. .IP +"%UNSAFE_RENEGOTIATION" will enable unsafe renegotiation (default +behaviour at 2.8.5 and earlier releases) +.IP To avoid collisions in order to specify a compression algorithm in this string you have to prefix it with "COMP-", protocol versions with "VERS-" and certificate types with "CTYPE-". All other diff --git a/doc/manpages/gnutls-serv.1 b/doc/manpages/gnutls-serv.1 index 9b8c4252ea..e1b5bd16e9 100644 --- a/doc/manpages/gnutls-serv.1 +++ b/doc/manpages/gnutls-serv.1 @@ -75,6 +75,9 @@ Special keywords: .IP "%COMPAT" will enable compatibility features for a server. .IP +"%UNSAFE_RENEGOTIATION" will enable unsafe renegotiation (default +behaviour at 2.8.5 and earlier releases) +.IP To avoid collisions in order to specify a compression algorithm in this string you have to prefix it with "COMP-", protocol versions with "VERS-" and certificate types with "CTYPE-". All other diff --git a/lib/Makefile.am b/lib/Makefile.am index 54a91b737a..fd366f0f5b 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -81,7 +81,7 @@ COBJECTS = gnutls_record.c gnutls_compress.c debug.c gnutls_cipher.c \ auth_dh_common.c gnutls_helper.c gnutls_supplemental.c \ crypto.c random.c pk-libgcrypt.c mpi-libgcrypt.c cryptodev.c \ rnd-libgcrypt.c cipher-libgcrypt.c mac-libgcrypt.c ext_signature.c \ - crypto-api.c + crypto-api.c ext_safe_renegotiation.c if ENABLE_OPRFI COBJECTS += $(OPRFI_COBJECTS) @@ -101,7 +101,8 @@ HFILES = debug.h gnutls_compress.h gnutls_cipher.h gnutls_buffers.h \ ext_srp.h gnutls_srp.h auth_srp.h auth_srp_passwd.h \ gnutls_helper.h auth_psk.h auth_psk_passwd.h \ gnutls_supplemental.h ext_oprfi.h crypto.h random.h \ - ext_session_ticket.h ext_signature.h gnutls_cryptodev.h + ext_session_ticket.h ext_signature.h gnutls_cryptodev.h \ + ext_safe_renegotiation.h # Separate so we can create the documentation diff --git a/lib/ext_safe_renegotiation.c b/lib/ext_safe_renegotiation.c new file mode 100644 index 0000000000..1020a49661 --- /dev/null +++ b/lib/ext_safe_renegotiation.c @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2009 Free Software Foundation + * + * Author: Steve Dispensa () + * + * This file is part of GNUTLS. + * + * The GNUTLS library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA + * + */ + +#include +#include +#include "gnutls_errors.h" + +/* Each peer processes the extension in the same way - by moving the "current" + * value to "previous" and setting new "current" values. + */ +int +_gnutls_safe_renegotiation_recv_params (gnutls_session_t session, + const opaque * data, size_t _data_size) +{ + ssize_t data_size = _data_size; + uint8_t len; + + DECR_LEN (data_size, 1); + len = data[0]; + DECR_LEN (data_size, len); + + if (len >= MAX_VERIFY_DATA_SIZE) + { + gnutls_assert(); + return GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER; + } + + memcpy (session->security_parameters.extensions.previous_verify_data, + session->security_parameters.extensions.current_verify_data, + session->security_parameters.extensions.current_verify_data_len); + + session->security_parameters.extensions.previous_verify_data_len = + session->security_parameters.extensions.current_verify_data_len; + + memcpy (session->security_parameters.extensions.current_verify_data, + &data[1], len); + + if (session->security_parameters.entity == GNUTLS_SERVER) + len *= 2; + + session->security_parameters.extensions.current_verify_data_len = len; + + session->security_parameters.extensions.safe_renegotiation_received = 1; + + + return 0; +} + +/* As a client, this sends the verify information that was saved during the + * previous finished message. As a server, echo back whatever we just received. + */ +int +_gnutls_safe_renegotiation_send_params (gnutls_session_t session, + opaque * data, size_t data_size) +{ + uint8_t len = 0; /* return 0 if we're not sending this ext */ + + if(session->security_parameters.extensions.safe_renegotiation_received || + session->security_parameters.entity == GNUTLS_CLIENT) + { + if (!session->security_parameters.extensions.disable_safe_renegotiation) + { + len = session->security_parameters.extensions.current_verify_data_len; + + /* client only sends its verification data */ + if (session->security_parameters.entity == GNUTLS_CLIENT) + { + len /= 2; + } + + if (data_size < len + 1) /* save room for the length byte */ + { + gnutls_assert (); + return GNUTLS_E_SHORT_MEMORY_BUFFER; + } + + data[0] = len++; /* return total length = len + length byte */ + memcpy (&data[1], + session->security_parameters.extensions.current_verify_data, + session->security_parameters.extensions.current_verify_data_len); + } + } + + return len; +} + +/** + * gnutls_safe_renegotiation_set - Used to enable and disable safe renegotiation + * @session: is a #gnutls_session_t structure. + * @value: 0 to disable and 1 to enable + * + * Used to enable and disable safe renegotiation for the current + * session. Normally you shouldn't cope with this function since the + * default (enable) is sufficient, but there might be servers that + * cannot handle or correctly handle the extension. + **/ +void +gnutls_safe_renegotiation_set (gnutls_session_t session, int value) +{ + session->security_parameters.extensions.disable_safe_renegotiation = 1-value; +} + diff --git a/lib/ext_safe_renegotiation.h b/lib/ext_safe_renegotiation.h new file mode 100644 index 0000000000..dab23a46cc --- /dev/null +++ b/lib/ext_safe_renegotiation.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2009 Free Software Foundation + * + * Author: Steve Dispensa () + * + * This file is part of GNUTLS. + * + * The GNUTLS library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA + * + */ + +int _gnutls_safe_renegotiation_recv_params (gnutls_session_t state, + const opaque * data, size_t data_size); +int _gnutls_safe_renegotiation_send_params (gnutls_session_t state, + opaque * data, size_t); +void gnutls_safe_renegotiation_set (gnutls_session_t session, int value); diff --git a/lib/gnutls_errors.c b/lib/gnutls_errors.c index fdea4ef009..086386c4f1 100644 --- a/lib/gnutls_errors.c +++ b/lib/gnutls_errors.c @@ -220,6 +220,8 @@ static const gnutls_error_entry error_algorithms[] = { GNUTLS_E_OPENPGP_GETKEY_FAILED, 1), ERROR_ENTRY (N_("Could not find OpenPGP subkey."), GNUTLS_E_OPENPGP_SUBKEY_ERROR, 1), + ERROR_ENTRY (N_("Safe renegotiation failed."), + GNUTLS_E_SAFE_RENEGOTIATION_FAILED, 1), ERROR_ENTRY (N_("The SRP username supplied is illegal."), GNUTLS_E_ILLEGAL_SRP_USERNAME, 1), diff --git a/lib/gnutls_extensions.c b/lib/gnutls_extensions.c index b3f388ce73..eccbc5af27 100644 --- a/lib/gnutls_extensions.c +++ b/lib/gnutls_extensions.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -320,6 +321,14 @@ _gnutls_ext_init (void) if (ret != GNUTLS_E_SUCCESS) return ret; + ret = gnutls_ext_register (GNUTLS_EXTENSION_SAFE_RENEGOTIATION, + "SAFE_RENEGOTIATION", + GNUTLS_EXT_TLS, + _gnutls_safe_renegotiation_recv_params, + _gnutls_safe_renegotiation_send_params); + if (ret != GNUTLS_E_SUCCESS) + return ret; + #ifdef ENABLE_OPRFI ret = gnutls_ext_register (GNUTLS_EXTENSION_OPAQUE_PRF_INPUT, "OPAQUE_PRF_INPUT", diff --git a/lib/gnutls_handshake.c b/lib/gnutls_handshake.c index aca1aab73f..95d6233e5f 100644 --- a/lib/gnutls_handshake.c +++ b/lib/gnutls_handshake.c @@ -384,6 +384,26 @@ _gnutls_user_hello_func (gnutls_session_t session, return 0; } +/* traverses the list of ciphersuites to find the + * SCSV (Secure Renegotiation indicator) ciphersuite. + */ +static int check_for_scsv(opaque* data, size_t datalen) +{ +int j; + /* check for Safe renegotiation ciphersuite + * FIXME: apply correct values here. + */ + for (j = 0; j < datalen; j += 2) + { + if (data[j] == 0xFF && data[j+1] == 0xFF) + { + return 1; + } + } + + return 0; +} + /* Read a client hello packet. * A client hello must be a known version client hello * or version 2.0 client hello (only for compatibility @@ -452,6 +472,7 @@ _gnutls_read_client_hello (gnutls_session_t session, opaque * data, { /* resumed! */ resume_copy_required_values (session); session->internals.resumed = RESUME_TRUE; + return _gnutls_user_hello_func (session, adv_version); } else @@ -473,6 +494,14 @@ _gnutls_read_client_hello (gnutls_session_t session, opaque * data, suite_ptr = &data[pos]; pos += suite_size; + + if (session->security_parameters.extensions.initial_negotiation_completed != 0 \ + && check_for_scsv(suite_ptr, suite_size) != 0) + { + gnutls_assert(); + return GNUTLS_E_SAFE_RENEGOTIATION_FAILED; + } + /* Point to the compression methods */ DECR_LEN (len, 1); @@ -636,7 +665,7 @@ _gnutls_send_finished (gnutls_session_t session, int again) data_size = 36; } else - { /* TLS 1.0 */ + { /* TLS 1.0 */ ret = _gnutls_finished (session, session->security_parameters.entity, data); data_size = 12; @@ -652,6 +681,21 @@ _gnutls_send_finished (gnutls_session_t session, int again) session->internals.finished_func (session, data, data_size); } + /* Save data for safe_renegotiation. + */ + if (session->security_parameters.entity == GNUTLS_CLIENT) + { + memcpy (session->security_parameters.extensions.current_verify_data, + data, data_size); + } + else + { + memcpy (&session->security_parameters.extensions.current_verify_data[data_size], + data, data_size); + } + session->security_parameters.extensions.current_verify_data_len = + data_size*2; + ret = _gnutls_send_handshake (session, data_size ? data : NULL, data_size, GNUTLS_HANDSHAKE_FINISHED); @@ -665,7 +709,7 @@ _gnutls_send_finished (gnutls_session_t session, int again) static int _gnutls_recv_finished (gnutls_session_t session) { - uint8_t data[36], *vrfy; + uint8_t data[MAX_VERIFY_DATA_SIZE], *vrfy; int data_size; int ret; int vrfysize; @@ -726,6 +770,21 @@ _gnutls_recv_finished (gnutls_session_t session) } gnutls_free (vrfy); + /* For safe renegotiation */ + if (session->security_parameters.entity == GNUTLS_CLIENT) + { + memcpy (&session->security_parameters.extensions.current_verify_data[data_size], + data, data_size); + } + else /* server */ + { + memcpy (session->security_parameters.extensions.current_verify_data, + data, data_size); + } + session->security_parameters.extensions.current_verify_data_len = data_size*2; + + session->security_parameters.extensions.initial_negotiation_completed = 1; + return ret; } @@ -765,7 +824,6 @@ _gnutls_server_find_pk_algos_in_ciphersuites (const opaque * return algo; } - /* This selects the best supported ciphersuite from the given ones. Then * it adds the suite to the session and performs some checks. */ @@ -784,7 +842,7 @@ _gnutls_server_select_suite (gnutls_session_t session, opaque * data, x = _gnutls_supported_ciphersuites (session, &ciphers); if (x < 0) - { /* the case x==0 is handled within the function. */ + { /* the case x==0 is handled within the function. */ gnutls_assert (); return x; } @@ -830,23 +888,28 @@ _gnutls_server_select_suite (gnutls_session_t session, opaque * data, retval = GNUTLS_E_UNKNOWN_CIPHER_SUITE; - for (j = 0; j < datalen; j += 2) + if (check_for_scsv(data, datalen) != 0) { - for (i = 0; i < x; i++) - { - if (memcmp (ciphers[i].suite, &data[j], 2) == 0) - { - memcpy (&cs.suite, &data[j], 2); + session->security_parameters.extensions.safe_renegotiation_received = 1; + } - _gnutls_handshake_log - ("HSK[%p]: Selected cipher suite: %s\n", session, - _gnutls_cipher_suite_get_name (&cs)); - memcpy (session->security_parameters.current_cipher_suite.suite, - ciphers[i].suite, 2); - retval = 0; - goto finish; - } - } + for (j = 0; j < datalen; j += 2) + { + for (i = 0; i < x; i++) + { + if (memcmp (ciphers[i].suite, &data[j], 2) == 0) + { + memcpy (&cs.suite, &data[j], 2); + + _gnutls_handshake_log + ("HSK[%p]: Selected cipher suite: %s\n", session, + _gnutls_cipher_suite_get_name (&cs)); + memcpy (session->security_parameters.current_cipher_suite.suite, + ciphers[i].suite, 2); + retval = 0; + goto finish; + } + } } finish: @@ -2169,6 +2232,8 @@ _gnutls_recv_hello (gnutls_session_t session, opaque * data, int datalen) { int ret; + session->security_parameters.extensions.safe_renegotiation_received = 0; + if (session->security_parameters.entity == GNUTLS_CLIENT) { ret = _gnutls_read_server_hello (session, data, datalen); @@ -2179,7 +2244,7 @@ _gnutls_recv_hello (gnutls_session_t session, opaque * data, int datalen) } } else - { /* Server side reading a client hello */ + { /* Server side reading a client hello */ ret = _gnutls_read_client_hello (session, data, datalen); if (ret < 0) @@ -2189,6 +2254,46 @@ _gnutls_recv_hello (gnutls_session_t session, opaque * data, int datalen) } } + if (!session->security_parameters.extensions.disable_safe_renegotiation) + { + if (session->security_parameters.extensions.safe_renegotiation_received) + { + if ((session->security_parameters.extensions.current_verify_data_len != + session->security_parameters.extensions.previous_verify_data_len) || + (memcmp (session->security_parameters.extensions.current_verify_data, + session->security_parameters.extensions.previous_verify_data, + session->security_parameters.extensions.current_verify_data_len))) + { + gnutls_assert(); + return GNUTLS_E_SAFE_RENEGOTIATION_FAILED; + } + else + { + _gnutls_debug_log ("Safe renegotiation succeeded.\n"); + } + } + else + { + if (session->security_parameters.extensions.initial_negotiation_completed) + { + if (session->internals.priorities.unsafe_renegotiation) + { + _gnutls_handshake_log ("Allowing unsafe renegotiation!\n"); + } + else + { + gnutls_assert(); + _gnutls_handshake_log ("Denying unsafe renegotiation.\n"); + return GNUTLS_E_SAFE_RENEGOTIATION_FAILED; + } + } + else + { + _gnutls_handshake_log ("Allowing unsafe initial negotiation.\n"); + } + } + } + return ret; } diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h index a097bea1c8..bbb7d7f2a7 100644 --- a/lib/gnutls_int.h +++ b/lib/gnutls_int.h @@ -177,7 +177,8 @@ typedef enum extensions_t GNUTLS_EXTENSION_SRP = 12, GNUTLS_EXTENSION_SIGNATURE_ALGORITHMS = 13, GNUTLS_EXTENSION_SESSION_TICKET = 35, - GNUTLS_EXTENSION_INNER_APPLICATION = 37703 + GNUTLS_EXTENSION_INNER_APPLICATION = 37703, + GNUTLS_EXTENSION_SAFE_RENEGOTIATION = 0xff01, } extensions_t; typedef enum @@ -306,6 +307,8 @@ struct gnutls_session_ticket_key_st { opaque mac_secret[SESSION_TICKET_MAC_SECRET_SIZE]; }; +#define MAX_VERIFY_DATA_SIZE 36 /* in SSL 3.0, 12 in TLS 1.0 */ + typedef struct { server_name_st server_names[MAX_SERVER_NAME_EXTENSIONS]; @@ -333,6 +336,16 @@ typedef struct opaque *oprfi_server; uint16_t oprfi_server_len; + /* Safe renegotiation. */ + int disable_safe_renegotiation:1; + int safe_renegotiation_received:1; + int initial_negotiation_completed:1; + uint8_t current_verify_data[2*MAX_VERIFY_DATA_SIZE]; + size_t current_verify_data_len; + uint8_t previous_verify_data[2*MAX_VERIFY_DATA_SIZE]; + size_t previous_verify_data_len; + + /* Session Ticket */ opaque *session_ticket; uint16_t session_ticket_len; struct gnutls_session_ticket_key_st *session_ticket_key; @@ -446,7 +459,8 @@ struct gnutls_priority_st priority_st sign_algo; /* to disable record padding */ - int no_padding; + int no_padding:1; + int unsafe_renegotiation:1; int ssl3_record_version; int additional_verify_flags; }; diff --git a/lib/gnutls_priority.c b/lib/gnutls_priority.c index 8700f90fb3..88e454e52f 100644 --- a/lib/gnutls_priority.c +++ b/lib/gnutls_priority.c @@ -522,6 +522,8 @@ gnutls_priority_set (gnutls_session_t session, gnutls_priority_t priority) * * "%COMPAT" will enable compatibility features for a server. * + * "%UNSAFE_RENEGOTIATION" will allow unsafe renegotiation. + * * "%SSL3_RECORD_VERSION" will use SSL3.0 record version in client hello. * * "%VERIFY_ALLOW_SIGN_RSA_MD5" will allow RSA-MD5 signatures in @@ -711,6 +713,9 @@ gnutls_priority_init (gnutls_priority_t * priority_cache, "VERIFY_ALLOW_X509_V1_CA_CRT") == 0) (*priority_cache)->additional_verify_flags |= GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT; + else if (strcasecmp (&broken_list[i][1], + "UNSAFE_RENEGOTIATION") == 0) + (*priority_cache)->unsafe_renegotiation = 1; else goto error; } diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in index f148c1635e..74ab94eee6 100644 --- a/lib/includes/gnutls/gnutls.h.in +++ b/lib/includes/gnutls/gnutls.h.in @@ -533,6 +533,9 @@ extern "C" { void *data, size_t * data_length, unsigned int *type, unsigned int indx); + /* Safe renegotiation */ + void gnutls_safe_renegotiation_set (gnutls_session_t session, int value); + /* Opaque PRF Input * http://tools.ietf.org/id/draft-rescorla-tls-opaque-prf-input-00.txt */ @@ -1382,6 +1385,7 @@ extern "C" { #define GNUTLS_E_IA_VERIFY_FAILED -104 #define GNUTLS_E_UNKNOWN_ALGORITHM -105 #define GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM -106 +#define GNUTLS_E_SAFE_RENEGOTIATION_FAILED -107 #define GNUTLS_E_BASE64_ENCODING_ERROR -201 #define GNUTLS_E_INCOMPATIBLE_GCRYPT_LIBRARY -202 /* obsolete */ diff --git a/src/cli.c b/src/cli.c index 2aa2da4197..4e5bebe9d6 100644 --- a/src/cli.c +++ b/src/cli.c @@ -826,6 +826,17 @@ after_handshake: continue; } + if (strstr(buffer, "**REHANDSHAKE**") != NULL) { + fprintf (stderr, "*** Starting TLS rehandshake\n"); + ret = do_handshake (&hd); + if (ret < 0) + { + fprintf (stderr, "*** Rehandshake has failed\n"); + user_term = 1; + retval = 1; + break; + } + } if (crlf != 0) { char *b = strchr (buffer, '\n'); diff --git a/src/serv.c b/src/serv.c index 12c5ec7d76..f8c78a6be8 100644 --- a/src/serv.c +++ b/src/serv.c @@ -1240,14 +1240,24 @@ main (int argc, char **argv) } else if (r <= 0) { - j->http_state = HTTP_STATE_CLOSING; - if (r < 0 && r != GNUTLS_E_UNEXPECTED_PACKET_LENGTH) + if (r == GNUTLS_E_REHANDSHAKE) { - check_alert (j->tls_session, r); - fprintf (stderr, "Error while receiving data\n"); - GERR (r); - } - + do + { + r = gnutls_handshake (j->tls_session); + } + while (r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN); + } + else + { + j->http_state = HTTP_STATE_CLOSING; + if (r < 0 && r != GNUTLS_E_UNEXPECTED_PACKET_LENGTH) + { + check_alert (j->tls_session, r); + fprintf (stderr, "Error while receiving data\n"); + GERR (r); + } + } } else {