diff --git a/ChangeLog b/ChangeLog index dadca0e175..0ea522e6a9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -28,6 +28,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 80b037eb24..cce7b2b741 100644 --- a/include/polarssl/check_config.h +++ b/include/polarssl/check_config.h @@ -277,6 +277,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 b155e26898..04a5b26d81 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 bf2527ae5c..5f4b5f7508 100644 --- a/include/polarssl/ssl.h +++ b/include/polarssl/ssl.h @@ -253,6 +253,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 + /** * \name SECTION: Module settings * @@ -832,6 +835,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 @@ -1528,6 +1535,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 @@ -1796,6 +1818,10 @@ int ssl_read( ssl_context *ssl, unsigned char *buf, size_t len ); * \note When this function returns POLARSSL_ERR_NET_WANT_WRITE, * it must be called later with the *same* arguments, * until it returns a positive value. + * + * \note This function may write less than the number of bytes + * requested if len is greater than the maximum record length. + * For arbitrary-sized messages, it should be called in a loop. */ 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 91ce32d6c9..22bd6974c7 100644 --- a/library/ssl_tls.c +++ b/library/ssl_tls.c @@ -3717,6 +3717,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; @@ -4263,6 +4267,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; @@ -4741,7 +4752,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; size_t n; @@ -4810,6 +4825,45 @@ int ssl_write( ssl_context *ssl, const unsigned char *buf, size_t len ) return( (int) n ); } +/* + * 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 2ad61582dc..07dee7f582 100644 --- a/programs/ssl/ssl_client2.c +++ b/programs/ssl/ssl_client2.c @@ -91,6 +91,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 @@ -131,6 +132,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 */ @@ -275,6 +277,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" @@ -350,6 +359,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" \ @@ -446,6 +456,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; @@ -671,6 +682,12 @@ int main( int argc, char *argv[] ) if( opt.trunc_hmac < 0 || opt.trunc_hmac > 1 ) goto usage; } + else if( strcmp( p, "recsplit" ) == 0 ) + { + opt.recsplit = atoi( q ); + if( opt.recsplit < 0 || opt.recsplit > 1 ) + goto usage; + } else goto usage; } @@ -963,6 +980,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/tests/ssl-opt.sh b/tests/ssl-opt.sh index 456dbabe17..cd4f6a67f7 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -662,6 +662,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" \ @@ -2087,7 +2143,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" @@ -2132,7 +2189,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" @@ -2146,14 +2203,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 \ @@ -2206,7 +2263,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"