diff --git a/ext/session/php_session.h b/ext/session/php_session.h index ef139196ca900..dcc6f1caf2baf 100644 --- a/ext/session/php_session.h +++ b/ext/session/php_session.h @@ -20,6 +20,10 @@ #include "ext/standard/php_var.h" #include "ext/hash/php_hash.h" +#if defined(HAVE_OPENSSL_EXT) +# include "ext/openssl/php_openssl.h" +#endif + #define PHP_SESSION_API 20161017 #include "php_version.h" @@ -201,6 +205,16 @@ typedef struct _php_ps_globals { bool lazy_write; /* omit session write when it is possible */ bool in_save_handler; /* state if session is in save handler or not */ bool set_handler; /* state if session module i setting handler or not */ +#if defined(HAVE_OPENSSL_EXT) && !defined(ZTS) + bool ssl_encrypt; /* encrypt the session data */ + zend_string *ssl_iv; + zend_string *ssl_pw_tok; + char *ssl_tag; + char *ssl_method; + zend_long ssl_method_len; + zend_long ssl_iv_len; + zend_long ssl_tag_len; +#endif zend_string *session_vars; /* serialized original session data */ } php_ps_globals; diff --git a/ext/session/session.c b/ext/session/session.c index 8212ad23cd7bb..d473beffb5392 100644 --- a/ext/session/session.c +++ b/ext/session/session.c @@ -74,6 +74,7 @@ zend_class_entry *php_session_id_iface_entry; zend_class_entry *php_session_update_timestamp_iface_entry; #define PS_MAX_SID_LENGTH 256 +#define PW_TOK_LEN 32 /* *********** * Helpers * @@ -116,6 +117,11 @@ static inline void php_rinit_session_globals(void) /* {{{ */ PS(define_sid) = 1; PS(session_vars) = NULL; PS(module_number) = my_module_number; +#if defined(HAVE_OPENSSL_EXT) && !defined(ZTS) + PS(ssl_iv_len) = 0; + PS(ssl_iv) = NULL; + PS(ssl_pw_tok) = NULL; +#endif ZVAL_UNDEF(&PS(http_session_vars)); } /* }}} */ @@ -143,6 +149,18 @@ static inline void php_rshutdown_session_globals(void) /* {{{ */ PS(session_vars) = NULL; } +#if defined(HAVE_OPENSSL_EXT) && !defined(ZTS) + if (PS(ssl_iv)) { + zend_string_release_ex(PS(ssl_iv), 0); + PS(ssl_iv) = NULL; + } + + if (PS(ssl_pw_tok)) { + zend_string_release_ex(PS(ssl_pw_tok), 0); + PS(ssl_pw_tok) = NULL; + } +#endif + /* User save handlers may end up directly here by misuse, bugs in user script, etc. */ /* Set session status to prevent error while restoring save handler INI value. */ PS(session_status) = php_session_none; @@ -462,6 +480,49 @@ static int php_session_initialize(void) /* {{{ */ php_session_decode(val); zend_string_release_ex(val, 0); } +#if defined(HAVE_OPENSSL_EXT) && !defined(ZTS) + if (PS(ssl_encrypt)) { + zend_long ssl_method_len = strlen(PS(ssl_method)); + if (!ssl_method_len) { + php_error_docref(NULL, E_WARNING, "A cipher method is needed to encrypt the session"); + PS(ssl_encrypt) = 0; + } else { + zend_string *iv; + zend_string *pw_tok; + zend_long iv_len; + zend_long ssl_tag_len = PS(ssl_tag) ? strlen(PS(ssl_tag)) : 0; + + if (PS(ssl_iv)) + zend_string_release_ex(PS(ssl_iv), 0); + + if ((iv_len = php_openssl_cipher_iv_length(PS(ssl_method))) == -1 || iv_len == 0) { + php_error_docref(NULL, E_ERROR, "session.ssl_method `%s` is invalid", PS(ssl_method)); + return FAILURE; + } + + if ((iv = php_openssl_random_pseudo_bytes(iv_len)) == NULL) { + php_error_docref(NULL, E_ERROR, "session iv data failure"); + return FAILURE; + } + + if ((pw_tok = php_openssl_random_pseudo_bytes(PW_TOK_LEN)) == NULL) { + php_error_docref(NULL, E_ERROR, "session token data failure"); + return FAILURE; + } + + if (!ssl_tag_len) { + PS(ssl_tag) = NULL; + } + + PS(ssl_tag_len) = ssl_tag_len; + + PS(ssl_method_len) = ssl_method_len; + PS(ssl_iv) = iv; + PS(ssl_iv_len) = iv_len; + PS(ssl_pw_tok) = pw_tok; + } + } +#endif return SUCCESS; } /* }}} */ @@ -823,6 +884,12 @@ PHP_INI_BEGIN() PHP_INI_ENTRY("session.sid_length", "32", PHP_INI_ALL, OnUpdateSidLength) PHP_INI_ENTRY("session.sid_bits_per_character", "4", PHP_INI_ALL, OnUpdateSidBits) STD_PHP_INI_BOOLEAN("session.lazy_write", "1", PHP_INI_ALL, OnUpdateLazyWrite, lazy_write, php_ps_globals, ps_globals) +#if defined(HAVE_OPENSSL_EXT) && !defined(ZTS) + STD_PHP_INI_BOOLEAN("session.ssl_encrypt", "0", PHP_INI_ALL, OnUpdateBool, ssl_encrypt, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.ssl_method", "", PHP_INI_ALL, OnUpdateSessionString, ssl_method, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.ssl_tag", "", PHP_INI_ALL, OnUpdateSessionString, ssl_tag, php_ps_globals, ps_globals) +#endif + /* Upload progress */ STD_PHP_INI_BOOLEAN("session.upload_progress.enabled", @@ -836,7 +903,6 @@ PHP_INI_BEGIN() STD_PHP_INI_ENTRY("session.upload_progress.freq", "1%", ZEND_INI_PERDIR, OnUpdateRfc1867Freq, rfc1867_freq, php_ps_globals, ps_globals) STD_PHP_INI_ENTRY("session.upload_progress.min_freq", "1", ZEND_INI_PERDIR, OnUpdateReal, rfc1867_min_freq,php_ps_globals, ps_globals) - /* Commented out until future discussion */ /* PHP_INI_ENTRY("session.encode_sources", "globals,track", PHP_INI_ALL, NULL) */ PHP_INI_END() @@ -845,6 +911,63 @@ PHP_INI_END() /* *************** * Serializers * *************** */ + +#if defined(HAVE_OPENSSL_EXT) && !defined(ZTS) +static int php_session_encrypt(smart_str *buf) /* {{{ */ +{ + zend_string* buffer; + smart_str res = {0}; + + if (!PS(ssl_encrypt) || !PS(ssl_pw_tok) || !buf->a) { + return SUCCESS; + } + + zval *ztag = NULL; + + if (PS(ssl_tag_len) > 0) { + ztag = emalloc(sizeof(*ztag)); + ZVAL_STRINGL(ztag, PS(ssl_tag), PS(ssl_tag_len)); + } + + if ((buffer = php_openssl_encrypt(ZSTR_VAL(buf->s), buf->a, PS(ssl_method), PS(ssl_method_len), + ZSTR_VAL(PS(ssl_pw_tok)), PW_TOK_LEN, 0, ZSTR_VAL(PS(ssl_iv)), PS(ssl_iv_len), + ztag, PS(ssl_tag_len), NULL, 0)) == NULL) { + php_error_docref(NULL, E_WARNING, "Cannot encrypt the session data with method '%s', tag '%s'", + PS(ssl_method), PS(ssl_tag)); + efree(ztag); + return FAILURE; + } + + smart_str_free(buf); + res.s = zend_string_dup(buffer, 0); + res.a = ZSTR_LEN(buffer); + *buf = res; + zend_string_release_ex(buffer, 0); + efree(ztag); + return SUCCESS; +} +/* }}} */ + +static zend_string *php_session_decrypt(PS_SERIALIZER_DECODE_ARGS) /* {{{ */ +{ + zend_string* buffer; + + if (!PS(ssl_encrypt) || !PS(ssl_pw_tok) || !vallen) + return NULL; + + if ((buffer = php_openssl_decrypt((char *)val, vallen, PS(ssl_method), PS(ssl_method_len), + ZSTR_VAL(PS(ssl_pw_tok)), PW_TOK_LEN, 0, ZSTR_VAL(PS(ssl_iv)), PS(ssl_iv_len), + PS(ssl_tag), PS(ssl_tag_len), NULL, 0)) == NULL) { + php_error_docref(NULL, E_WARNING, "Cannot decrypt the session data with method '%s'", + PS(ssl_method)); + return NULL; + } + + return buffer; +} +/* }}} */ +#endif + PS_SERIALIZER_ENCODE_FUNC(php_serialize) /* {{{ */ { smart_str buf = {0}; @@ -853,6 +976,9 @@ PS_SERIALIZER_ENCODE_FUNC(php_serialize) /* {{{ */ IF_SESSION_VARS() { PHP_VAR_SERIALIZE_INIT(var_hash); php_var_serialize(&buf, Z_REFVAL(PS(http_session_vars)), &var_hash); +#if defined(HAVE_OPENSSL_EXT) && !defined(ZTS) + php_session_encrypt(&buf); +#endif PHP_VAR_SERIALIZE_DESTROY(var_hash); } return buf.s; @@ -867,6 +993,13 @@ PS_SERIALIZER_DECODE_FUNC(php_serialize) /* {{{ */ int result; zend_string *var_name = zend_string_init("_SESSION", sizeof("_SESSION") - 1, 0); +#if defined(HAVE_OPENSSL_EXT) && !defined(ZTS) + zend_string* buffer = php_session_decrypt(val, vallen); + if (buffer) { + val = ZSTR_VAL(buffer); + endptr = val + ZSTR_LEN(buffer); + } +#endif ZVAL_NULL(&session_vars); PHP_VAR_UNSERIALIZE_INIT(var_hash); result = php_var_unserialize( @@ -887,6 +1020,12 @@ PS_SERIALIZER_DECODE_FUNC(php_serialize) /* {{{ */ Z_ADDREF_P(&PS(http_session_vars)); zend_hash_update_ind(&EG(symbol_table), var_name, &PS(http_session_vars)); zend_string_release_ex(var_name, 0); +#if defined(HAVE_OPENSSL_EXT) && !defined(ZTS) + if (buffer) { + zend_string_release_ex(buffer, 0); + } +#endif + return result || !vallen ? SUCCESS : FAILURE; } /* }}} */ @@ -911,6 +1050,9 @@ PS_SERIALIZER_ENCODE_FUNC(php_binary) /* {{{ */ ); smart_str_0(&buf); +#if defined(HAVE_OPENSSL_EXT) && !defined(ZTS) + php_session_encrypt(&buf); +#endif PHP_VAR_SERIALIZE_DESTROY(var_hash); return buf.s; @@ -925,6 +1067,13 @@ PS_SERIALIZER_DECODE_FUNC(php_binary) /* {{{ */ zend_string *name; php_unserialize_data_t var_hash; zval *current, rv; +#if defined(HAVE_OPENSSL_EXT) && !defined(ZTS) + zend_string* buffer = php_session_decrypt(val, vallen); + if (buffer) { + val = ZSTR_VAL(buffer); + endptr = val + ZSTR_LEN(buffer); + } +#endif PHP_VAR_UNSERIALIZE_INIT(var_hash); @@ -954,6 +1103,10 @@ PS_SERIALIZER_DECODE_FUNC(php_binary) /* {{{ */ php_session_normalize_vars(); PHP_VAR_UNSERIALIZE_DESTROY(var_hash); +#if defined(HAVE_OPENSSL_EXT) && !defined(ZTS) + if (buffer) + zend_string_release_ex(buffer, 0); +#endif return SUCCESS; } @@ -981,6 +1134,9 @@ PS_SERIALIZER_ENCODE_FUNC(php) /* {{{ */ ); smart_str_0(&buf); +#if defined(HAVE_OPENSSL_EXT) && !defined(ZTS) + php_session_encrypt(&buf); +#endif PHP_VAR_SERIALIZE_DESTROY(var_hash); return buf.s; @@ -997,6 +1153,13 @@ PS_SERIALIZER_DECODE_FUNC(php) /* {{{ */ php_unserialize_data_t var_hash; zval *current, rv; +#if defined(HAVE_OPENSSL_EXT) && !defined(ZTS) + zend_string* buffer = php_session_decrypt(val, vallen); + if (buffer) { + val = ZSTR_VAL(buffer); + endptr = val + ZSTR_LEN(buffer); + } +#endif PHP_VAR_UNSERIALIZE_INIT(var_hash); p = val; @@ -1031,6 +1194,11 @@ PS_SERIALIZER_DECODE_FUNC(php) /* {{{ */ php_session_normalize_vars(); PHP_VAR_UNSERIALIZE_DESTROY(var_hash); +#if defined(HAVE_OPENSSL_EXT) && !defined(ZTS) + if (buffer) { + zend_string_release_ex(buffer, 0); + } +#endif return retval; } @@ -2262,6 +2430,19 @@ PHP_FUNCTION(session_regenerate_id) } zend_string_release_ex(PS(id), 0); PS(id) = NULL; +#if defined(HAVE_OPENSSL_EXT) && !defined(ZTS) + if (PS(ssl_pw_tok)) { + zend_string_release_ex(PS(ssl_pw_tok), 0); + PS(ssl_pw_tok) = NULL; + } + + PS(ssl_pw_tok) = php_openssl_random_pseudo_bytes(PW_TOK_LEN); + if (!PS(ssl_pw_tok)) { + PS(session_status) = php_session_none; + zend_throw_error(NULL, "Failed to create new session ID: %s (path: %s)", PS(mod)->s_name, PS(save_path)); + RETURN_FALSE; + } +#endif if (PS(mod)->s_open(&PS(mod_data), PS(save_path), PS(session_name)) == FAILURE) { PS(session_status) = php_session_none;