diff --git a/ChangeLog b/ChangeLog index 39120372b..f4551fb2b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -38,6 +38,7 @@ Features * Add compile-time option POLARSSL_X509_MAX_INTERMEDIATE_CA to limit the length of an X.509 verification chain. * Support for renegotiation can now be disabled at compile-time + * Support for 1/n-1 record splitting, a countermeasure against BEAST. Bugfix * Stack buffer overflow if ctr_drbg_update() is called with too large diff --git a/include/polarssl/check_config.h b/include/polarssl/check_config.h index 262ff44ed..d2c36437d 100644 --- a/include/polarssl/check_config.h +++ b/include/polarssl/check_config.h @@ -303,6 +303,11 @@ #error "POLARSSL_SSL_SESSION_TICKETS_C defined, but not all prerequisites" #endif +#if defined(POLARSSL_SSL_CBC_RECORD_SPLITTING) && \ + !defined(POLARSSL_SSL_PROTO_SSL3) && !defined(POLARSSL_SSL_PROTO_TLS1) +#error "POLARSSL_SSL_CBC_RECORD_SPLITTING defined, but not all prerequisites" +#endif + #if defined(POLARSSL_SSL_SERVER_NAME_INDICATION) && \ !defined(POLARSSL_X509_CRT_PARSE_C) #error "POLARSSL_SSL_SERVER_NAME_INDICATION defined, but not all prerequisites" diff --git a/include/polarssl/config.h b/include/polarssl/config.h index 860a40bbe..6d768834e 100644 --- a/include/polarssl/config.h +++ b/include/polarssl/config.h @@ -886,6 +886,18 @@ */ //#define POLARSSL_SSL_HW_RECORD_ACCEL +/** + * \def POLARSSL_SSL_CBC_RECORD_SPLITTING + * + * Enable 1/n-1 record splitting for CBC mode in SSLv3 and TLS 1.0. + * + * This is a countermeasure to the BEAST attack, which also minimizes the risk + * of interoperability issues compared to sending 0-length records. + * + * Comment this macro to disable 1/n-1 record splitting. + */ +#define POLARSSL_SSL_CBC_RECORD_SPLITTING + /** * \def POLARSSL_SSL_DISABLE_RENEGOTIATION * diff --git a/include/polarssl/ssl.h b/include/polarssl/ssl.h index 8e0ba5458..26110d800 100644 --- a/include/polarssl/ssl.h +++ b/include/polarssl/ssl.h @@ -265,6 +265,9 @@ #define SSL_SESSION_TICKETS_DISABLED 0 #define SSL_SESSION_TICKETS_ENABLED 1 +#define SSL_CBC_RECORD_SPLITTING_DISABLED -1 +#define SSL_CBC_RECORD_SPLITTING_ENABLED 0 + /* * DTLS retransmission states, see RFC 6347 4.2.4 * @@ -943,6 +946,10 @@ struct _ssl_context #if defined(POLARSSL_SSL_MAX_FRAGMENT_LENGTH) unsigned char mfl_code; /*!< MaxFragmentLength chosen by us */ #endif /* POLARSSL_SSL_MAX_FRAGMENT_LENGTH */ +#if defined(POLARSSL_SSL_CBC_RECORD_SPLITTING) + char split_done; /*!< flag for record splitting: + -1 disabled, 0 todo, 1 done */ +#endif /* * PKI layer @@ -1857,6 +1864,21 @@ int ssl_set_max_frag_len( ssl_context *ssl, unsigned char mfl_code ); int ssl_set_truncated_hmac( ssl_context *ssl, int truncate ); #endif /* POLARSSL_SSL_TRUNCATED_HMAC */ +#if defined(POLARSSL_SSL_CBC_RECORD_SPLITTING) +/** + * \brief Enable / Disable 1/n-1 record splitting + * (Default: SSL_CBC_RECORD_SPLITTING_ENABLED) + * + * \note Only affects SSLv3 and TLS 1.0, not higher versions. + * Does not affect non-CBC ciphersuites in any version. + * + * \param ssl SSL context + * \param split SSL_CBC_RECORD_SPLITTING_ENABLED or + * SSL_CBC_RECORD_SPLITTING_DISABLED + */ +void ssl_set_cbc_record_splitting( ssl_context *ssl, char split ); +#endif /* POLARSSL_SSL_CBC_RECORD_SPLITTING */ + #if defined(POLARSSL_SSL_SESSION_TICKETS) /** * \brief Enable / Disable session tickets @@ -2147,11 +2169,13 @@ int ssl_read( ssl_context *ssl, unsigned char *buf, size_t len ); * it must be called later with the *same* arguments, * until it returns a positive value. * - * \note When DTLS is in use, and a maximum fragment length was - * either set with \c ssl_set_max_frag_len() or negotiated by - * the peer, len must not not be greater than the maximum - * fragment length, or POLARSSL_ERR_SSL_BAD_INPUT_DATA is - * returned. + * \note If the requested length is greater than the maximum + * fragment length (either the built-in limit or the one set + * or negotiated with the peer), then: + * - with TLS, less bytes than requested are written. (In + * order to write larger messages, this function should be + * called in a loop.) + * - with DTLS, POLARSSL_ERR_SSL_BAD_INPUT_DATA is returned. */ int ssl_write( ssl_context *ssl, const unsigned char *buf, size_t len ); diff --git a/library/ssl_tls.c b/library/ssl_tls.c index e25e5655c..12ee22e91 100644 --- a/library/ssl_tls.c +++ b/library/ssl_tls.c @@ -5027,6 +5027,10 @@ int ssl_session_reset( ssl_context *ssl ) ssl->out_msgtype = 0; ssl->out_msglen = 0; ssl->out_left = 0; +#if defined(POLARSSL_SSL_CBC_RECORD_SPLITTING) + if( ssl->split_done != SSL_CBC_RECORD_SPLITTING_DISABLED ) + ssl->split_done = 0; +#endif ssl->transform_in = NULL; ssl->transform_out = NULL; @@ -5697,6 +5701,13 @@ int ssl_set_truncated_hmac( ssl_context *ssl, int truncate ) } #endif /* POLARSSL_SSL_TRUNCATED_HMAC */ +#if defined(POLARSSL_SSL_CBC_RECORD_SPLITTING) +void ssl_set_cbc_record_splitting( ssl_context *ssl, char split ) +{ + ssl->split_done = split; +} +#endif + void ssl_legacy_renegotiation( ssl_context *ssl, int allow_legacy ) { ssl->allow_legacy_renegotiation = allow_legacy; @@ -6309,7 +6320,11 @@ int ssl_read( ssl_context *ssl, unsigned char *buf, size_t len ) /* * Send application data to be encrypted by the SSL layer */ +#if defined(POLARSSL_SSL_CBC_RECORD_SPLITTING) +static int ssl_write_real( ssl_context *ssl, const unsigned char *buf, size_t len ) +#else int ssl_write( ssl_context *ssl, const unsigned char *buf, size_t len ) +#endif { int ret; #if defined(POLARSSL_SSL_MAX_FRAGMENT_LENGTH) @@ -6392,6 +6407,45 @@ int ssl_write( ssl_context *ssl, const unsigned char *buf, size_t len ) return( (int) len ); } +/* + * Write application data, doing 1/n-1 splitting if necessary. + * + * With non-blocking I/O, ssl_write_real() may return WANT_WRITE, + * then the caller will call us again with the same arguments, so + * remember wether we already did the split or not. + */ +#if defined(POLARSSL_SSL_CBC_RECORD_SPLITTING) +int ssl_write( ssl_context *ssl, const unsigned char *buf, size_t len ) +{ + int ret; + + if( ssl->split_done == SSL_CBC_RECORD_SPLITTING_DISABLED || + len <= 1 || + ssl->minor_ver > SSL_MINOR_VERSION_1 || + cipher_get_cipher_mode( &ssl->transform_out->cipher_ctx_enc ) + != POLARSSL_MODE_CBC ) + { + return( ssl_write_real( ssl, buf, len ) ); + } + + if( ssl->split_done == 0 ) + { + ssl->split_done = 1; + if( ( ret = ssl_write_real( ssl, buf, 1 ) ) < 0 ) + return( ret ); + } + + if( ssl->split_done == 1 ) + { + ssl->split_done = 0; + if( ( ret = ssl_write_real( ssl, buf + 1, len - 1 ) ) < 0 ) + return( ret ); + } + + return( ret + 1 ); +} +#endif /* POLARSSL_SSL_CBC_RECORD_SPLITTING */ + /* * Notify the peer that the connection is being closed */ diff --git a/programs/ssl/ssl_client2.c b/programs/ssl/ssl_client2.c index b49a48e16..0ddd802c2 100644 --- a/programs/ssl/ssl_client2.c +++ b/programs/ssl/ssl_client2.c @@ -93,6 +93,7 @@ int main( int argc, char *argv[] ) #define DFL_AUTH_MODE SSL_VERIFY_REQUIRED #define DFL_MFL_CODE SSL_MAX_FRAG_LEN_NONE #define DFL_TRUNC_HMAC 0 +#define DFL_RECSPLIT -1 #define DFL_RECONNECT 0 #define DFL_RECO_DELAY 0 #define DFL_TICKETS SSL_SESSION_TICKETS_ENABLED @@ -138,6 +139,7 @@ struct options int auth_mode; /* verify mode for connection */ unsigned char mfl_code; /* code for maximum fragment length */ int trunc_hmac; /* negotiate truncated hmac or not */ + int recsplit; /* enable record splitting? */ int reconnect; /* attempt to resume session */ int reco_delay; /* delay in seconds before resuming session */ int tickets; /* enable / disable session tickets */ @@ -285,6 +287,13 @@ static int my_verify( void *data, x509_crt *crt, int depth, int *flags ) #define USAGE_MAX_FRAG_LEN "" #endif /* POLARSSL_SSL_MAX_FRAGMENT_LENGTH */ +#if defined(POLARSSL_SSL_CBC_RECORD_SPLITTING) +#define USAGE_RECSPLIT \ + " recplit=%%d default: (library default)\n" +#else +#define USAGE_RECSPLIT +#endif + #if defined(POLARSSL_TIMING_C) #define USAGE_TIME \ " reco_delay=%%d default: 0 seconds\n" @@ -373,6 +382,7 @@ static int my_verify( void *data, x509_crt *crt, int depth, int *flags ) USAGE_FALLBACK \ USAGE_EMS \ USAGE_ETM \ + USAGE_RECSPLIT \ "\n" \ " min_version=%%s default: \"\" (ssl3)\n" \ " max_version=%%s default: \"\" (tls1_2)\n" \ @@ -471,6 +481,7 @@ int main( int argc, char *argv[] ) opt.auth_mode = DFL_AUTH_MODE; opt.mfl_code = DFL_MFL_CODE; opt.trunc_hmac = DFL_TRUNC_HMAC; + opt.recsplit = DFL_RECSPLIT; opt.reconnect = DFL_RECONNECT; opt.reco_delay = DFL_RECO_DELAY; opt.tickets = DFL_TICKETS; @@ -743,6 +754,12 @@ int main( int argc, char *argv[] ) if( opt.hs_to_min == 0 || opt.hs_to_max < opt.hs_to_min ) goto usage; } + else if( strcmp( p, "recsplit" ) == 0 ) + { + opt.recsplit = atoi( q ); + if( opt.recsplit < 0 || opt.recsplit > 1 ) + goto usage; + } else goto usage; } @@ -1058,6 +1075,13 @@ int main( int argc, char *argv[] ) ssl_set_encrypt_then_mac( &ssl, opt.etm ); #endif +#if defined(POLARSSL_SSL_CBC_RECORD_SPLITTING) + if( opt.recsplit != DFL_RECSPLIT ) + ssl_set_cbc_record_splitting( &ssl, opt.recsplit + ? SSL_CBC_RECORD_SPLITTING_ENABLED + : SSL_CBC_RECORD_SPLITTING_DISABLED ); +#endif + #if defined(POLARSSL_SSL_ALPN) if( opt.alpn_string != NULL ) if( ( ret = ssl_set_alpn_protocols( &ssl, alpn_list ) ) != 0 ) diff --git a/programs/ssl/ssl_server2.c b/programs/ssl/ssl_server2.c index 1f9e97f6d..f2beaf1b9 100644 --- a/programs/ssl/ssl_server2.c +++ b/programs/ssl/ssl_server2.c @@ -192,14 +192,14 @@ struct options char *sni; /* string describing sni information */ const char *alpn_string; /* ALPN supported protocols */ const char *dhm_file; /* the file with the DH parameters */ + int extended_ms; /* allow negotiation of extended MS? */ + int etm; /* allow negotiation of encrypt-then-MAC? */ int transport; /* TLS or DTLS? */ int cookies; /* Use cookies for DTLS? -1 to break them */ int anti_replay; /* Use anti-replay for DTLS? -1 for default */ uint32_t hs_to_min; /* Initial value of DTLS handshake timer */ uint32_t hs_to_max; /* Max value of DTLS handshake timer */ int badmac_limit; /* Limit of records with bad MAC */ - char extended_ms; /* allow negotiation of extended MS? */ - char etm; /* allow negotiation of encrypt-then-MAC? */ } opt; static void my_debug( void *ctx, int level, const char *str ) diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh index 2e6e1fde4..3099adcf1 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -791,6 +791,62 @@ run_test "Fallback SCSV: enabled, max version, openssl client" \ -s "received FALLBACK_SCSV" \ -S "inapropriate fallback" +# Tests for CBC 1/n-1 record splitting + +run_test "CBC Record splitting: TLS 1.2, no splitting" \ + "$P_SRV" \ + "$P_CLI force_ciphersuite=TLS-RSA-WITH-AES-128-CBC-SHA \ + request_size=123 force_version=tls1_2" \ + 0 \ + -s "Read from client: 123 bytes read" \ + -S "Read from client: 1 bytes read" \ + -S "122 bytes read" + +run_test "CBC Record splitting: TLS 1.1, no splitting" \ + "$P_SRV" \ + "$P_CLI force_ciphersuite=TLS-RSA-WITH-AES-128-CBC-SHA \ + request_size=123 force_version=tls1_1" \ + 0 \ + -s "Read from client: 123 bytes read" \ + -S "Read from client: 1 bytes read" \ + -S "122 bytes read" + +run_test "CBC Record splitting: TLS 1.0, splitting" \ + "$P_SRV" \ + "$P_CLI force_ciphersuite=TLS-RSA-WITH-AES-128-CBC-SHA \ + request_size=123 force_version=tls1" \ + 0 \ + -S "Read from client: 123 bytes read" \ + -s "Read from client: 1 bytes read" \ + -s "122 bytes read" + +run_test "CBC Record splitting: SSLv3, splitting" \ + "$P_SRV" \ + "$P_CLI force_ciphersuite=TLS-RSA-WITH-AES-128-CBC-SHA \ + request_size=123 force_version=ssl3" \ + 0 \ + -S "Read from client: 123 bytes read" \ + -s "Read from client: 1 bytes read" \ + -s "122 bytes read" + +run_test "CBC Record splitting: TLS 1.0 RC4, no splitting" \ + "$P_SRV" \ + "$P_CLI force_ciphersuite=TLS-RSA-WITH-RC4-128-SHA \ + request_size=123 force_version=tls1" \ + 0 \ + -s "Read from client: 123 bytes read" \ + -S "Read from client: 1 bytes read" \ + -S "122 bytes read" + +run_test "CBC Record splitting: TLS 1.0, splitting disabled" \ + "$P_SRV" \ + "$P_CLI force_ciphersuite=TLS-RSA-WITH-AES-128-CBC-SHA \ + request_size=123 force_version=tls1 recsplit=0" \ + 0 \ + -s "Read from client: 123 bytes read" \ + -S "Read from client: 1 bytes read" \ + -S "122 bytes read" + # Tests for Session Tickets run_test "Session resume using tickets: basic" \ @@ -2283,7 +2339,8 @@ run_test "Small packet TLS 1.2 BlockCipher without EtM" \ run_test "Small packet TLS 1.2 BlockCipher larger MAC" \ "$P_SRV" \ - "$P_CLI request_size=1 force_version=tls1_2 force_ciphersuite=TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384" \ + "$P_CLI request_size=1 force_version=tls1_2 \ + force_ciphersuite=TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384" \ 0 \ -s "Read from client: 1 bytes read" @@ -2328,7 +2385,7 @@ run_test "Small packet TLS 1.2 AEAD shorter tag" \ run_test "Large packet SSLv3 BlockCipher" \ "$P_SRV" \ - "$P_CLI request_size=16384 force_version=ssl3 \ + "$P_CLI request_size=16384 force_version=ssl3 recsplit=0 \ force_ciphersuite=TLS-RSA-WITH-AES-256-CBC-SHA" \ 0 \ -s "Read from client: 16384 bytes read" @@ -2342,14 +2399,14 @@ run_test "Large packet SSLv3 StreamCipher" \ run_test "Large packet TLS 1.0 BlockCipher" \ "$P_SRV" \ - "$P_CLI request_size=16384 force_version=tls1 \ + "$P_CLI request_size=16384 force_version=tls1 recsplit=0 \ force_ciphersuite=TLS-RSA-WITH-AES-256-CBC-SHA" \ 0 \ -s "Read from client: 16384 bytes read" run_test "Large packet TLS 1.0 BlockCipher truncated MAC" \ "$P_SRV" \ - "$P_CLI request_size=16384 force_version=tls1 \ + "$P_CLI request_size=16384 force_version=tls1 recsplit=0 \ force_ciphersuite=TLS-RSA-WITH-AES-256-CBC-SHA \ trunc_hmac=1" \ 0 \ @@ -2402,7 +2459,8 @@ run_test "Large packet TLS 1.2 BlockCipher" \ run_test "Large packet TLS 1.2 BlockCipher larger MAC" \ "$P_SRV" \ - "$P_CLI request_size=16384 force_version=tls1_2 force_ciphersuite=TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384" \ + "$P_CLI request_size=16384 force_version=tls1_2 \ + force_ciphersuite=TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384" \ 0 \ -s "Read from client: 16384 bytes read"