Browse Source

Port liveness and SSL CA validation from 4.3 branch.

Make stream_select() work on ssl-enabled sockets again.
PEAR_1_4DEV
Wez Furlong 23 years ago
parent
commit
eaf0942c8b
  1. 222
      ext/openssl/openssl.c
  2. 111
      ext/openssl/xp_ssl.c

222
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

111
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) {

Loading…
Cancel
Save