summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Wright <daverandom@php.net>2014-02-21 12:08:13 +0000
committerChris Wright <daverandom@php.net>2014-02-25 16:51:49 +0000
commit480e4f8541f03a5d85f3f20e4b64f192906c5481 (patch)
tree6d57ef51aff4d233df4582431d86f44357c09a6d
parentaad7b724721c01f6269951511ee4c92843f56e0c (diff)
downloadphp-git-480e4f8541f03a5d85f3f20e4b64f192906c5481.tar.gz
Add peer certificate verification on windows
Peer certificate verification on Windows using the native certificate store and the Windows API
-rw-r--r--ext/openssl/config.w321
-rwxr-xr-xext/openssl/openssl.c253
2 files changed, 225 insertions, 29 deletions
diff --git a/ext/openssl/config.w32 b/ext/openssl/config.w32
index 49edb068a8..066d7bc853 100644
--- a/ext/openssl/config.w32
+++ b/ext/openssl/config.w32
@@ -6,6 +6,7 @@ ARG_WITH("openssl", "OpenSSL support", "no");
if (PHP_OPENSSL != "no") {
if (CHECK_LIB("ssleay32.lib", "openssl", PHP_OPENSSL) &&
CHECK_LIB("libeay32.lib", "openssl", PHP_OPENSSL) &&
+ CHECK_LIB("crypt32.lib", "openssl") &&
CHECK_HEADER_ADD_INCLUDE("openssl/ssl.h", "CFLAGS_OPENSSL")) {
EXTENSION("openssl", "openssl.c xp_ssl.c");
diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c
index e649fca795..46a0b74ccd 100755
--- a/ext/openssl/openssl.c
+++ b/ext/openssl/openssl.c
@@ -53,6 +53,16 @@
#include <openssl/ssl.h>
#include <openssl/pkcs12.h>
+/* Windows platform includes */
+#ifdef PHP_WIN32
+# include <windows.h>
+# include <Wincrypt.h>
+/* These are from Wincrypt.h, they conflict with OpenSSL */
+# undef X509_NAME
+# undef X509_CERT_PAIR
+# undef X509_EXTENSIONS
+#endif
+
/* Common */
#include <time.h>
@@ -629,6 +639,8 @@ static STACK_OF(X509) * load_all_certs_from_file(char *certfile);
static X509_REQ * php_openssl_csr_from_zval(zval ** val, int makeresource, long * resourceval TSRMLS_DC);
static EVP_PKEY * php_openssl_generate_private_key(struct php_x509_request * req TSRMLS_DC);
+#define PHP_X509_NAME_ENTRY_TO_UTF8(ne, i, out) ASN1_STRING_to_UTF8(&out, X509_NAME_ENTRY_get_data(X509_NAME_get_entry(ne, i)))
+
static void add_assoc_name_entry(zval * val, char * key, X509_NAME * name, int shortname TSRMLS_DC) /* {{{ */
{
zval **data;
@@ -5240,6 +5252,165 @@ static int passwd_callback(char *buf, int num, int verify, void *data) /* {{{ */
}
/* }}} */
+#if defined(PHP_WIN32) && OPENSSL_VERSION_NUMBER >= 0x00907000L
+#define RETURN_CERT_VERIFY_FAILURE(code) X509_STORE_CTX_set_error(x509_store_ctx, code); return 0;
+static int win_cert_verify_callback(X509_STORE_CTX *x509_store_ctx, void *arg) /* {{{ */
+{
+ PCCERT_CONTEXT cert_ctx = NULL;
+ PCCERT_CHAIN_CONTEXT cert_chain_ctx = NULL;
+
+ php_stream *stream;
+ php_openssl_netstream_data_t *sslsock;
+ zval **val;
+ zend_bool is_self_signed = 0;
+
+ TSRMLS_FETCH();
+
+ stream = (php_stream*)arg;
+ sslsock = (php_openssl_netstream_data_t*)stream->abstract;
+
+ { /* First convert the x509 struct back to a DER encoded buffer and let Windows decode it into a form it can work with */
+ unsigned char *der_buf = NULL;
+ int der_len;
+
+ der_len = i2d_X509(x509_store_ctx->cert, &der_buf);
+ if (der_len < 0) {
+ unsigned long err_code, e;
+ char err_buf[512];
+
+ while ((e = ERR_get_error()) != 0) {
+ err_code = e;
+ }
+
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error encoding X509 certificate: %d: %s", err_code, ERR_error_string(err_code, err_buf));
+ RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED);
+ }
+
+ cert_ctx = CertCreateCertificateContext(X509_ASN_ENCODING, der_buf, der_len);
+ OPENSSL_free(der_buf);
+
+ if (cert_ctx == NULL) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error creating certificate context: %s", php_win_err());
+ RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED);
+ }
+ }
+
+ { /* Next fetch the relevant cert chain from the store */
+ CERT_ENHKEY_USAGE enhkey_usage = {0};
+ CERT_USAGE_MATCH cert_usage = {0};
+ CERT_CHAIN_PARA chain_params = {sizeof(CERT_CHAIN_PARA)};
+ DWORD chain_flags = 0;
+ unsigned long verify_depth = PHP_OPENSSL_DEFAULT_STREAM_VERIFY_DEPTH;
+ unsigned int i;
+
+ enhkey_usage.cUsageIdentifier = 0;
+ enhkey_usage.rgpszUsageIdentifier = NULL;
+ cert_usage.dwType = USAGE_MATCH_TYPE_AND;
+ cert_usage.Usage = enhkey_usage;
+ chain_params.RequestedUsage = cert_usage;
+ chain_flags = CERT_CHAIN_CACHE_END_CERT | CERT_CHAIN_REVOCATION_CHECK_CHAIN;
+
+ if (!CertGetCertificateChain(NULL, cert_ctx, NULL, NULL, &chain_params, chain_flags, NULL, &cert_chain_ctx)) {
+ CertFreeCertificateContext(cert_ctx);
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error getting certificate chain: %s", php_win_err());
+ RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED);
+ }
+
+ /* check if the cert is self-signed */
+ if (cert_chain_ctx->cChain > 0 && cert_chain_ctx->rgpChain[0]->cElement > 0
+ && (cert_chain_ctx->rgpChain[0]->rgpElement[0]->TrustStatus.dwInfoStatus & CERT_TRUST_IS_SELF_SIGNED) != 0) {
+ is_self_signed = 1;
+ }
+
+ /* check the depth */
+ if (GET_VER_OPT("verify_depth")) {
+ convert_to_long_ex(val);
+ verify_depth = (unsigned long)Z_LVAL_PP(val);
+ }
+
+ for (i = 0; i < cert_chain_ctx->cChain; i++) {
+ if (cert_chain_ctx->rgpChain[i]->cElement > verify_depth) {
+ CertFreeCertificateContext(cert_ctx);
+ RETURN_CERT_VERIFY_FAILURE(X509_V_ERR_CERT_CHAIN_TOO_LONG);
+ }
+ }
+ }
+
+ { /* Then verify it against a policy */
+ SSL_EXTRA_CERT_CHAIN_POLICY_PARA ssl_policy_params = {sizeof(SSL_EXTRA_CERT_CHAIN_POLICY_PARA)};
+ CERT_CHAIN_POLICY_PARA chain_policy_params = {sizeof(CERT_CHAIN_POLICY_PARA)};
+ CERT_CHAIN_POLICY_STATUS chain_policy_status = {sizeof(CERT_CHAIN_POLICY_STATUS)};
+ LPWSTR server_name = NULL;
+ BOOL verify_result;
+
+ { /* This looks ridiculous and it is - but we validate the name ourselves using the CN_match
+ ctx option, so just use the CN from the cert here */
+
+ X509_NAME *cert_name;
+ unsigned char *cert_name_utf8;
+ int index, cert_name_utf8_len;
+ DWORD num_wchars;
+
+ cert_name = X509_get_subject_name(x509_store_ctx->cert);
+ index = X509_NAME_get_index_by_NID(cert_name, NID_commonName, -1);
+ if (index < 0) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to locate certificate CN");
+ RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED);
+ }
+
+ cert_name_utf8_len = PHP_X509_NAME_ENTRY_TO_UTF8(cert_name, index, cert_name_utf8);
+
+ num_wchars = MultiByteToWideChar(CP_UTF8, 0, (char*)cert_name_utf8, -1, NULL, 0);
+ if (num_wchars == 0) {
+ OPENSSL_free(cert_name_utf8);
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to convert %s to wide character string", cert_name_utf8);
+ RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED);
+ }
+
+ server_name = emalloc((num_wchars * sizeof(WCHAR)) + sizeof(WCHAR));
+
+ num_wchars = MultiByteToWideChar(CP_UTF8, 0, (char*)cert_name_utf8, -1, server_name, num_wchars);
+ if (num_wchars == 0) {
+ OPENSSL_free(cert_name_utf8);
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to convert %s to wide character string", cert_name_utf8);
+ RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED);
+ }
+
+ OPENSSL_free(cert_name_utf8);
+ }
+
+ ssl_policy_params.dwAuthType = (sslsock->is_client) ? AUTHTYPE_SERVER : AUTHTYPE_CLIENT;
+ ssl_policy_params.pwszServerName = server_name;
+ chain_policy_params.pvExtraPolicyPara = &ssl_policy_params;
+
+ verify_result = CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, cert_chain_ctx, &chain_policy_params, &chain_policy_status);
+
+ CertFreeCertificateChain(cert_chain_ctx);
+ CertFreeCertificateContext(cert_ctx);
+ efree(server_name);
+
+ if (!verify_result) {
+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error verifying certificate chain policy: %s", php_win_err());
+ RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED);
+ }
+
+ if (chain_policy_status.dwError != 0) {
+ /* The chain does not match the policy */
+ if (is_self_signed && chain_policy_status.dwError == CERT_E_UNTRUSTEDROOT
+ && GET_VER_OPT("allow_self_signed") && zval_is_true(*val)) {
+ /* allow self-signed certs */
+ X509_STORE_CTX_set_error(x509_store_ctx, X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT);
+ } else {
+ RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED);
+ }
+ }
+ }
+
+ return 1;
+}
+/* }}} */
+#endif
+
static long load_stream_cafile(X509_STORE *cert_store, const char *cafile TSRMLS_DC) /* {{{ */
{
php_stream *stream;
@@ -5312,8 +5483,31 @@ static long load_stream_cafile(X509_STORE *cert_store, const char *cafile TSRMLS
}
/* }}} */
-static int load_verify_locations(SSL_CTX *ctx, php_stream *stream, char *cafile, char *capath TSRMLS_DC) /* {{{ */
+static void enable_peer_verify_callback(SSL_CTX *ctx, php_stream *stream) /* {{{ */
+{
+ zval **val = NULL;
+
+ /* turn on verification callback */
+ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback);
+
+ 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_depth(ctx, PHP_OPENSSL_DEFAULT_STREAM_VERIFY_DEPTH);
+ }
+}
+/* }}} */
+
+static int enable_peer_verification(SSL_CTX *ctx, php_stream *stream TSRMLS_DC) /* {{{ */
{
+ zval **val = NULL;
+ char *cafile = NULL;
+ char *capath = NULL;
+
+ GET_VER_OPT_STRING("cafile", cafile);
+ GET_VER_OPT_STRING("capath", capath);
+
if (!cafile) {
cafile = zend_ini_string("openssl.cafile", sizeof("openssl.cafile"), 0);
cafile = strlen(cafile) ? cafile : NULL;
@@ -5330,53 +5524,57 @@ static int load_verify_locations(SSL_CTX *ctx, php_stream *stream, char *cafile,
return 0;
}
}
+
+ enable_peer_verify_callback(ctx, stream);
} else {
+#if defined(PHP_WIN32) && OPENSSL_VERSION_NUMBER >= 0x00907000L
+ SSL_CTX_set_cert_verify_callback(ctx, win_cert_verify_callback, (void *)stream);
+ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
+#else
php_openssl_netstream_data_t *sslsock;
sslsock = (php_openssl_netstream_data_t*)stream->abstract;
+
if (sslsock->is_client && !SSL_CTX_set_default_verify_paths(ctx)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"Unable to set default verify locations and no CA settings specified");
return 0;
}
+
+ enable_peer_verify_callback(ctx, stream);
+#endif
}
return 1;
}
/* }}} */
+static int disable_peer_verification(SSL_CTX *ctx, php_stream *stream TSRMLS_DC) /* {{{ */
+{
+ SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
+
+ return 1;
+}
+/* }}} */
+
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;
char *cipherlist = NULL;
int ok = 1;
+ SSL *ssl;
ERR_clear_error();
/* look at context options in the stream and set appropriate verification flags */
if (GET_VER_OPT("verify_peer") && !zval_is_true(*val)) {
- SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
+ ok = disable_peer_verification(ctx, stream TSRMLS_CC);
} else {
+ ok = enable_peer_verification(ctx, stream TSRMLS_CC);
+ }
- /* 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 (!load_verify_locations(ctx, stream, cafile, capath TSRMLS_CC)) {
- 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_depth(ctx, PHP_OPENSSL_DEFAULT_STREAM_VERIFY_DEPTH);
- }
+ if (!ok) {
+ return NULL;
}
/* callback for the passphrase (for localcert) */
@@ -5443,17 +5641,14 @@ SSL *php_SSL_new_from_context(SSL_CTX *ctx, php_stream *stream TSRMLS_DC) /* {{{
}
}
- if (ok) {
- SSL *ssl = SSL_new(ctx);
+ ssl = SSL_new(ctx);
- if (ssl) {
- /* map SSL => stream */
- SSL_set_ex_data(ssl, ssl_stream_data_index, stream);
- }
- return ssl;
+ if (ssl) {
+ /* map SSL => stream */
+ SSL_set_ex_data(ssl, ssl_stream_data_index, stream);
}
- return NULL;
+ return ssl;
}
/* }}} */