summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWez Furlong <wez@php.net>2003-11-27 17:40:16 +0000
committerWez Furlong <wez@php.net>2003-11-27 17:40:16 +0000
commiteaf0942c8b3080e0dc545a8eb14938f341da16bc (patch)
tree5fae79bc283273aba8f614c16bf53e05349d541e
parent3ee72aa5be43a26a96ec6cfab1cc96afd6ee73cb (diff)
downloadphp-git-eaf0942c8b3080e0dc545a8eb14938f341da16bc.tar.gz
Port liveness and SSL CA validation from 4.3 branch.
Make stream_select() work on ssl-enabled sockets again.
-rw-r--r--ext/openssl/openssl.c222
-rw-r--r--ext/openssl/xp_ssl.c111
2 files changed, 316 insertions, 17 deletions
diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c
index 490f7810a5..2e37f75741 100644
--- a/ext/openssl/openssl.c
+++ b/ext/openssl/openssl.c
@@ -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 @@ ZEND_GET_MODULE(openssl)
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 @@ PHP_MINIT_FUNCTION(openssl)
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 @@ PHP_FUNCTION(openssl_open)
}
/* }}} */
+/* 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
diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c
index 1157179c53..be5d9afedb 100644
--- a/ext/openssl/xp_ssl.c
+++ b/ext/openssl/xp_ssl.c
@@ -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 @@ static int php_openssl_sockop_close(php_stream *stream, int close_handle TSRMLS_
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 @@ static int php_openssl_sockop_close(php_stream *stream, int close_handle TSRMLS_
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 @@ static int php_openssl_sockop_stat(php_stream *stream, php_stream_statbuf *ssb T
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 @@ static inline int php_openssl_setup_crypto(php_stream *stream,
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 @@ static inline int php_openssl_enable_crypto(php_stream *stream,
{
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 @@ static inline int php_openssl_tcp_sockop_accept(php_stream *stream, php_openssl_
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 @@ static int php_openssl_sockop_set_option(php_stream *stream, int option, int val
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 @@ static int php_openssl_sockop_cast(php_stream *stream, int castas, void **ret TS
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) {