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

Reply via email to