wez Thu Nov 27 12:40:16 2003 EDT Modified files: /php-src/ext/openssl openssl.c xp_ssl.c Log: Port liveness and SSL CA validation from 4.3 branch. Make stream_select() work on ssl-enabled sockets again.
Index: php-src/ext/openssl/openssl.c diff -u php-src/ext/openssl/openssl.c:1.84 php-src/ext/openssl/openssl.c:1.85 --- php-src/ext/openssl/openssl.c:1.84 Mon Oct 13 07:43:11 2003 +++ php-src/ext/openssl/openssl.c Thu Nov 27 12:40:15 2003 @@ -18,7 +18,7 @@ +----------------------------------------------------------------------+ */ -/* $Id: openssl.c,v 1.84 2003/10/13 11:43:11 wez Exp $ */ +/* $Id: openssl.c,v 1.85 2003/11/27 17:40:15 wez Exp $ */ #ifdef HAVE_CONFIG_H #include "config.h" @@ -41,6 +41,7 @@ #include <openssl/err.h> #include <openssl/conf.h> #include <openssl/rand.h> +#include <openssl/ssl.h> #define DEFAULT_KEY_LENGTH 512 #define MIN_KEY_LENGTH 384 @@ -153,6 +154,7 @@ static int le_key; static int le_x509; static int le_csr; +static int ssl_stream_data_index; /* {{{ resource destructors */ static void php_pkey_free(zend_rsrc_list_entry *rsrc TSRMLS_DC) @@ -563,6 +565,10 @@ ERR_load_crypto_strings(); ERR_load_EVP_strings(); + /* register a resource id number with openSSL so that we can map SSL -> stream structures in + * openSSL callbacks */ + ssl_stream_data_index = SSL_get_ex_new_index(0, "PHP stream index", NULL, NULL, NULL); + /* purposes for cert purpose checking */ REGISTER_LONG_CONSTANT("X509_PURPOSE_SSL_CLIENT", X509_PURPOSE_SSL_CLIENT, CONST_CS|CONST_PERSISTENT); REGISTER_LONG_CONSTANT("X509_PURPOSE_SSL_SERVER", X509_PURPOSE_SSL_SERVER, CONST_CS|CONST_PERSISTENT); @@ -3060,6 +3066,222 @@ } /* }}} */ +/* SSL verification functions */ + +#define GET_VER_OPT(name) (stream->context && SUCCESS == php_stream_context_get_option(stream->context, "ssl", name, &val)) +#define GET_VER_OPT_STRING(name, str) if (GET_VER_OPT(name)) { convert_to_string_ex(val); str = Z_STRVAL_PP(val); } + +static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) +{ + php_stream *stream; + SSL *ssl; + X509 *err_cert; + int err, depth, ret; + zval **val; + TSRMLS_FETCH(); + + ret = preverify_ok; + + /* determine the status for the current cert */ + err_cert = X509_STORE_CTX_get_current_cert(ctx); + err = X509_STORE_CTX_get_error(ctx); + depth = X509_STORE_CTX_get_error_depth(ctx); + + /* conjure the stream & context to use */ + ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + stream = (php_stream*)SSL_get_ex_data(ssl, ssl_stream_data_index); + + /* if allow_self_signed is set, make sure that verification succeeds */ + if (err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT && GET_VER_OPT("allow_self_signed") && zval_is_true(*val)) { + ret = 1; + } + + /* check the depth */ + if (GET_VER_OPT("verify_depth")) { + convert_to_long_ex(val); + + if (depth > Z_LVAL_PP(val)) { + ret = 0; + X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_CHAIN_TOO_LONG); + } + } + + return ret; + +} + +int php_openssl_apply_verification_policy(SSL *ssl, X509 *peer, php_stream *stream TSRMLS_DC) +{ + zval **val = NULL; + char *cnmatch = NULL; + X509_NAME *name; + char buf[1024]; + int err; + + /* verification is turned off */ + if (!(GET_VER_OPT("verify_peer") && zval_is_true(*val))) { + return SUCCESS; + } + + if (peer == NULL) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not get peer certificate"); + return FAILURE; + } + + err = SSL_get_verify_result(ssl); + switch (err) { + case X509_V_OK: + /* fine */ + break; + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + if (GET_VER_OPT("allow_self_signed") && zval_is_true(*val)) { + /* allowed */ + break; + } + /* not allowed, so fall through */ + default: + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not verify peer: code:%d %s", err, X509_verify_cert_error_string(err)); + return FAILURE; + } + + /* if the cert passed the usual checks, apply our own local policies now */ + + name = X509_get_subject_name(peer); + + /* Does the common name match ? (used primarily for https://) */ + GET_VER_OPT_STRING("CN_match", cnmatch); + if (cnmatch) { + int match = 0; + + X509_NAME_get_text_by_NID(name, NID_commonName, buf, sizeof(buf)); + + match = strcmp(cnmatch, buf) == 0; + if (!match && strlen(buf) > 3 && buf[0] == '*' && buf[1] == '.') { + /* Try wildcard */ + + if (strchr(buf+2, '.')) { + char *tmp = strstr(cnmatch, buf+1); + + match = tmp && strcmp(tmp, buf+2) && tmp == strchr(cnmatch, '.'); + } + } + + if (!match) { + /* didn't match */ + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Peer certificate CN=`%s' did not match expected CN=`%s'", + buf, cnmatch); + + return FAILURE; + } + } + + return SUCCESS; +} + +static int passwd_callback(char *buf, int num, int verify, void *data) +{ + php_stream *stream = (php_stream *)data; + zval **val = NULL; + char *passphrase = NULL; + /* TODO: could expand this to make a callback into PHP user-space */ + + GET_VER_OPT_STRING("passphrase", passphrase); + + if (passphrase) { + if (Z_STRLEN_PP(val) < num - 1) { + memcpy(buf, Z_STRVAL_PP(val), Z_STRLEN_PP(val)+1); + return Z_STRLEN_PP(val); + } + } + return 0; +} + +SSL *php_SSL_new_from_context(SSL_CTX *ctx, php_stream *stream TSRMLS_DC) +{ + zval **val = NULL; + char *cafile = NULL; + char *capath = NULL; + char *certfile = NULL; + int ok = 1; + + + /* look at context options in the stream and set appropriate verification flags */ + if (GET_VER_OPT("verify_peer") && zval_is_true(*val)) { + + /* turn on verification callback */ + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback); + + /* CA stuff */ + GET_VER_OPT_STRING("cafile", cafile); + GET_VER_OPT_STRING("capath", capath); + + if (cafile || capath) { + if (!SSL_CTX_load_verify_locations(ctx, cafile, capath)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to set verify locations `%s' `%s'\n", cafile, capath); + return NULL; + } + } + + if (GET_VER_OPT("verify_depth")) { + convert_to_long_ex(val); + SSL_CTX_set_verify_depth(ctx, Z_LVAL_PP(val)); + } + } else { + SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); + } + + /* callback for the passphrase (for localcert) */ + if (GET_VER_OPT("passphrase")) { + SSL_CTX_set_default_passwd_cb_userdata(ctx, stream); + SSL_CTX_set_default_passwd_cb(ctx, passwd_callback); + } + + GET_VER_OPT_STRING("local_cert", certfile); + if (certfile) { + X509 *cert = NULL; + EVP_PKEY *key = NULL; + SSL *tmpssl; + + /* a certificate to use for authentication */ + if (SSL_CTX_use_certificate_chain_file(ctx, certfile) != 1) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to set local cert chain file `%s'; Check that your cafile/capath settings include details of your certificate and its issuer", certfile); + return NULL; + } + + if (SSL_CTX_use_PrivateKey_file(ctx, certfile, SSL_FILETYPE_PEM) != 1) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to set private key file `%s'", certfile); + return NULL; + } + + tmpssl = SSL_new(ctx); + cert = SSL_get_certificate(tmpssl); + + if (cert) { + key = X509_get_pubkey(cert); + EVP_PKEY_copy_parameters(key, SSL_get_privatekey(tmpssl)); + EVP_PKEY_free(key); + } + SSL_free(tmpssl); + + if (!SSL_CTX_check_private_key(ctx)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Private key does not match certificate!"); + } + } + if (ok) { + SSL *ssl = SSL_new(ctx); + + if (ssl) { + /* map SSL => stream */ + SSL_set_ex_data(ssl, ssl_stream_data_index, stream); + } + return ssl; + } + + return NULL; +} + + /* * Local variables: * tab-width: 8 Index: php-src/ext/openssl/xp_ssl.c diff -u php-src/ext/openssl/xp_ssl.c:1.10 php-src/ext/openssl/xp_ssl.c:1.11 --- php-src/ext/openssl/xp_ssl.c:1.10 Wed Oct 8 07:23:46 2003 +++ php-src/ext/openssl/xp_ssl.c Thu Nov 27 12:40:15 2003 @@ -16,7 +16,7 @@ +----------------------------------------------------------------------+ */ -/* $Id: xp_ssl.c,v 1.10 2003/10/08 11:23:46 wez Exp $ */ +/* $Id: xp_ssl.c,v 1.11 2003/11/27 17:40:15 wez Exp $ */ #include "php.h" #include "ext/standard/file.h" @@ -24,8 +24,11 @@ #include "php_network.h" #include "php_openssl.h" #include <openssl/ssl.h> +#include <openssl/x509.h> #include <openssl/err.h> +int php_openssl_apply_verification_policy(SSL *ssl, X509 *peer, php_stream *stream TSRMLS_DC); +SSL *php_SSL_new_from_context(SSL_CTX *ctx, php_stream *stream TSRMLS_DC); /* This implementation is very closely tied to the that of the native * sockets implemented in the core. @@ -207,6 +210,7 @@ sslsock->ssl_handle = NULL; } if (sslsock->s.socket != -1) { +#ifdef PHP_WIN32 /* prevent more data from coming in */ shutdown(sslsock->s.socket, SHUT_RD); @@ -226,6 +230,7 @@ n = select(sslsock->s.socket + 1, NULL, &wrfds, &efds, &timeout); } while (n == -1 && php_socket_errno() == EINTR); +#endif closesocket(sslsock->s.socket); sslsock->s.socket = -1; @@ -247,6 +252,7 @@ return php_stream_socket_ops.stat(stream, ssb TSRMLS_CC); } + static inline int php_openssl_setup_crypto(php_stream *stream, php_openssl_netstream_data_t *sslsock, php_stream_xport_crypto_param *cparam @@ -307,7 +313,7 @@ return -1; } - sslsock->ssl_handle = SSL_new(ctx); + sslsock->ssl_handle = php_SSL_new_from_context(ctx, stream TSRMLS_CC); if (sslsock->ssl_handle == NULL) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to create an SSL handle"); SSL_CTX_free(ctx); @@ -335,29 +341,47 @@ { int n, retry = 1; - if (cparam->inputs.activate) { + if (cparam->inputs.activate && !sslsock->ssl_active) { if (sslsock->is_client) { - do { + SSL_set_connect_state(sslsock->ssl_handle); + } else { + SSL_set_accept_state(sslsock->ssl_handle); + } + + do { + if (sslsock->is_client) { n = SSL_connect(sslsock->ssl_handle); + } else { + n = SSL_accept(sslsock->ssl_handle); + } - if (n <= 0) { - retry = handle_ssl_error(stream, n TSRMLS_CC); - } else { - break; - } - } while (retry); + if (n <= 0) { + retry = handle_ssl_error(stream, n TSRMLS_CC); + } else { + break; + } + } while (retry); - if (n == 1) { + if (n == 1) { + X509 *peer_cert; + + peer_cert = SSL_get_peer_certificate(sslsock->ssl_handle); + + if (FAILURE == php_openssl_apply_verification_policy(sslsock->ssl_handle, peer_cert, stream TSRMLS_CC)) { + SSL_shutdown(sslsock->ssl_handle); + } else { sslsock->ssl_active = 1; } - - return n; - - } else { - + + X509_free(peer_cert); } - } else { + + return n; + + } else if (!cparam->inputs.activate && sslsock->ssl_active) { /* deactivate - common for server/client */ + SSL_shutdown(sslsock->ssl_handle); + sslsock->ssl_active = 0; } return -1; } @@ -385,7 +409,7 @@ clisockdata = pemalloc(sizeof(*clisockdata), stream->is_persistent); if (clisockdata == NULL) { - close(clisock); + closesocket(clisock); /* technically a fatal error */ } else { /* copy underlying tcp fields */ @@ -410,6 +434,53 @@ php_stream_xport_param *xparam = (php_stream_xport_param *)ptrparam; switch (option) { + case PHP_STREAM_OPTION_CHECK_LIVENESS: + { + fd_set rfds; + struct timeval tv = {0,0}; + char buf; + int alive = 1; + + if (sslsock->s.socket == -1) { + alive = 0; + } else { + FD_ZERO(&rfds); + FD_SET(sslsock->s.socket, &rfds); + + if (select(sslsock->s.socket + 1, &rfds, NULL, NULL, &tv) > 0 && FD_ISSET(sslsock->s.socket, &rfds)) { + if (sslsock->ssl_active) { + int n; + + do { + n = SSL_peek(sslsock->ssl_handle, &buf, sizeof(buf)); + if (n <= 0) { + int err = SSL_get_error(sslsock->ssl_handle, n); + + if (err == SSL_ERROR_SYSCALL) { + alive = php_socket_errno() == EAGAIN; + break; + } + + if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { + /* re-negotiate */ + continue; + } + + /* any other problem is a fatal error */ + alive = 0; + } + /* either peek succeeded or there was an error; we + * have set the alive flag appropriately */ + break; + } while (1); + } else if (0 == recv(sslsock->s.socket, &buf, sizeof(buf), MSG_PEEK) && php_socket_errno() != EAGAIN) { + alive = 0; + } + } + } + return alive ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR; + } + case PHP_STREAM_OPTION_CRYPTO_API: switch(cparam->op) { @@ -481,7 +552,13 @@ return FAILURE; } return SUCCESS; + case PHP_STREAM_AS_FD_FOR_SELECT: + if (ret) { + *ret = (void*)sslsock->s.socket; + } + return SUCCESS; + case PHP_STREAM_AS_FD: case PHP_STREAM_AS_SOCKETD: if (sslsock->ssl_active) {
-- PHP CVS Mailing List (http://www.php.net/) To unsubscribe, visit: http://www.php.net/unsub.php