From: Daniel Lowrey Date: Wed, 5 Mar 2014 17:23:54 +0000 (-0700) Subject: Merge branch 'PHP-5.6' X-Git-Tag: PRE_PHPNG_MERGE~503^2~14 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=1f5459572eb10504299b1872fbcf4cf1fcdde144;p=php Merge branch 'PHP-5.6' * PHP-5.6: Add encrypted server SNI support Raise timeout to 2s, reworded ssl timeout warning Refactor + reorganize openssl files --- 1f5459572eb10504299b1872fbcf4cf1fcdde144 diff --cc ext/openssl/openssl.c index e47986cbce,97740d361e..31761522bf --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@@ -5009,673 -4991,8 +4991,6 @@@ 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; - int err, depth, ret; - zval **val; - - ret = preverify_ok; - - /* determine the status for the current cert */ - 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; - - } - /* }}} */ - - static zend_bool matches_wildcard_name(const char *subjectname, const char *certname) - { - char *wildcard; - int prefix_len, suffix_len, subject_len; - - if (strcasecmp(subjectname, certname) == 0) { - return 1; - } - - if (!(wildcard = strchr(certname, '*'))) { - return 0; - } - - // 1) prefix, if not empty, must match subject - prefix_len = wildcard - certname; - if (prefix_len && strncasecmp(subjectname, certname, prefix_len) != 0) { - return 0; - } - - suffix_len = strlen(wildcard + 1); - subject_len = strlen(subjectname); - if (suffix_len <= subject_len) { - /* 2) suffix must match - * 3) no . between prefix and suffix - **/ - return strcasecmp(wildcard + 1, subjectname + subject_len - suffix_len) == 0 && - memchr(subjectname + prefix_len, '.', subject_len - suffix_len - prefix_len) == NULL; - } - - return 0; - } - - static zend_bool matches_san_list(X509 *peer, const char *subject_name TSRMLS_DC) - { - int i, san_name_len; - zend_bool is_match = 0; - unsigned char *cert_name; - - GENERAL_NAMES *alt_names = X509_get_ext_d2i(peer, NID_subject_alt_name, 0, 0); - int alt_name_count = sk_GENERAL_NAME_num(alt_names); - - for (i = 0; i < alt_name_count; i++) { - GENERAL_NAME *san = sk_GENERAL_NAME_value(alt_names, i); - if (san->type != GEN_DNS) { - /* we only care about DNS names */ - continue; - } - - san_name_len = ASN1_STRING_length(san->d.dNSName); - ASN1_STRING_to_UTF8(&cert_name, san->d.dNSName); - - /* prevent null byte poisoning */ - if (san_name_len != strlen((const char*)cert_name)) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Peer SAN entry is malformed"); - } else { - is_match = strcasecmp(subject_name, (const char*)cert_name) == 0; - } - - OPENSSL_free(cert_name); - - if (is_match) { - break; - } - } - - return is_match; - } - - static zend_bool matches_common_name(X509 *peer, const char *subject_name TSRMLS_DC) - { - char buf[1024]; - X509_NAME *cert_name; - zend_bool is_match = 0; - int cert_name_len; - - cert_name = X509_get_subject_name(peer); - cert_name_len = X509_NAME_get_text_by_NID(cert_name, NID_commonName, buf, sizeof(buf)); - - if (cert_name_len == -1) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to locate peer certificate CN"); - } else if (cert_name_len != strlen(buf)) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Peer certificate CN=`%.*s' is malformed", cert_name_len, buf); - } else if (matches_wildcard_name(subject_name, buf)) { - is_match = 1; - } else { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Peer certificate CN=`%.*s' did not match expected CN=`%s'", cert_name_len, buf, subject_name); - } - - return is_match; - } - - int php_openssl_apply_verification_policy(SSL *ssl, X509 *peer, php_stream *stream TSRMLS_DC) /* {{{ */ - { - zval **val = NULL; - char *peer_name = NULL; - int err; - zend_bool must_verify_peer; - zend_bool must_verify_peer_name; - zend_bool must_verify_fingerprint; - zend_bool has_cnmatch_ctx_opt; - php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; - - must_verify_peer = GET_VER_OPT("verify_peer") - ? zend_is_true(*val TSRMLS_CC) - : sslsock->is_client; - - has_cnmatch_ctx_opt = GET_VER_OPT("CN_match"); - must_verify_peer_name = (has_cnmatch_ctx_opt || GET_VER_OPT("verify_peer_name")) - ? zend_is_true(*val TSRMLS_CC) - : sslsock->is_client; - - must_verify_fingerprint = (GET_VER_OPT("peer_fingerprint") && zend_is_true(*val TSRMLS_CC)); - - if ((must_verify_peer || must_verify_peer_name || must_verify_fingerprint) && peer == NULL) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not get peer certificate"); - return FAILURE; - } - - /* Verify the peer against using CA file/path settings */ - if (must_verify_peer) { - 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 a peer_fingerprint match is required this trumps peer and peer_name verification */ - if (must_verify_fingerprint) { - if (Z_TYPE_PP(val) == IS_STRING || Z_TYPE_PP(val) == IS_ARRAY) { - if (!php_x509_fingerprint_match(peer, *val TSRMLS_CC)) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "Peer fingerprint doesn't match" - ); - return FAILURE; - } - } else { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "Expected peer fingerprint must be a string or an array" - ); - } - } - - /* verify the host name presented in the peer certificate */ - if (must_verify_peer_name) { - GET_VER_OPT_STRING("peer_name", peer_name); - - if (has_cnmatch_ctx_opt) { - GET_VER_OPT_STRING("CN_match", peer_name); - php_error(E_DEPRECATED, - "the 'CN_match' SSL context option is deprecated in favor of 'peer_name'" - ); - } - /* If no peer name was specified we use the autodetected url name in client environments */ - if (peer_name == NULL && sslsock->is_client) { - peer_name = sslsock->url_name; - } - - if (peer_name) { - if (matches_san_list(peer, peer_name TSRMLS_CC)) { - return SUCCESS; - } else if (matches_common_name(peer, peer_name TSRMLS_CC)) { - return SUCCESS; - } else { - return FAILURE; - } - } else { - 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; - } - /* }}} */ - - #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)}; - LPSTR usages[] = {szOID_PKIX_KP_SERVER_AUTH, szOID_SERVER_GATED_CRYPTO, szOID_SGC_NETSCAPE}; - DWORD chain_flags = 0; - unsigned long verify_depth = PHP_OPENSSL_DEFAULT_STREAM_VERIFY_DEPTH; - unsigned int i; - - enhkey_usage.cUsageIdentifier = 3; - enhkey_usage.rgpszUsageIdentifier = usages; - cert_usage.dwType = USAGE_MATCH_TYPE_OR; - cert_usage.Usage = enhkey_usage; - chain_params.RequestedUsage = cert_usage; - chain_flags = CERT_CHAIN_CACHE_END_CERT | CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT; - - if (!CertGetCertificateChain(NULL, cert_ctx, NULL, NULL, &chain_params, chain_flags, NULL, &cert_chain_ctx)) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error getting certificate chain: %s", php_win_err()); - CertFreeCertificateContext(cert_ctx); - 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) { - CertFreeCertificateChain(cert_chain_ctx); - 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 peer_name - 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"); - CertFreeCertificateChain(cert_chain_ctx); - CertFreeCertificateContext(cert_ctx); - 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) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to convert %s to wide character string", cert_name_utf8); - OPENSSL_free(cert_name_utf8); - CertFreeCertificateChain(cert_chain_ctx); - CertFreeCertificateContext(cert_ctx); - 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) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to convert %s to wide character string", cert_name_utf8); - efree(server_name); - OPENSSL_free(cert_name_utf8); - CertFreeCertificateChain(cert_chain_ctx); - CertFreeCertificateContext(cert_ctx); - 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); - - efree(server_name); - CertFreeCertificateChain(cert_chain_ctx); - CertFreeCertificateContext(cert_ctx); - - 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; - X509 *cert; - BIO *buffer; - int buffer_active = 0; - char *line; - size_t line_len; - long certs_added = 0; - - stream = php_stream_open_wrapper(cafile, "rb", 0, NULL); - - if (stream == NULL) { - php_error(E_WARNING, "failed loading cafile stream: `%s'", cafile); - return 0; - } else if (stream->wrapper->is_url) { - php_stream_close(stream); - php_error(E_WARNING, "remote cafile streams are disabled for security purposes"); - return 0; - } - - cert_start: { - line = php_stream_get_line(stream, NULL, 0, &line_len); - if (line == NULL) { - goto stream_complete; - } else if (!strcmp(line, "-----BEGIN CERTIFICATE-----\n") || - !strcmp(line, "-----BEGIN CERTIFICATE-----\r\n") - ) { - buffer = BIO_new(BIO_s_mem()); - buffer_active = 1; - goto cert_line; - } else { - efree(line); - goto cert_start; - } - } - - cert_line: { - BIO_puts(buffer, line); - efree(line); - line = php_stream_get_line(stream, NULL, 0, &line_len); - if (line == NULL) { - goto stream_complete; - } else if (!strcmp(line, "-----END CERTIFICATE-----") || - !strcmp(line, "-----END CERTIFICATE-----\n") || - !strcmp(line, "-----END CERTIFICATE-----\r\n") - ) { - goto add_cert; - } else { - goto cert_line; - } - } - - add_cert: { - BIO_puts(buffer, line); - efree(line); - cert = PEM_read_bio_X509(buffer, NULL, 0, NULL); - BIO_free(buffer); - buffer_active = 0; - if (cert && X509_STORE_add_cert(cert_store, cert)) { - ++certs_added; - } - goto cert_start; - } - - stream_complete: { - php_stream_close(stream); - if (buffer_active == 1) { - BIO_free(buffer); - } - } - - if (certs_added == 0) { - php_error(E_WARNING, "no valid certs found cafile stream: `%s'", cafile); - } - - return certs_added; - } - /* }}} */ - - 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; - } - - if (!capath) { - capath = zend_ini_string("openssl.capath", sizeof("openssl.capath"), 0); - capath = strlen(capath) ? capath : NULL; - } - - if (cafile || capath) { - if (!SSL_CTX_load_verify_locations(ctx, cafile, capath)) { - if (cafile && !load_stream_cafile(SSL_CTX_get_cert_store(ctx), cafile TSRMLS_CC)) { - 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 *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)) { - ok = disable_peer_verification(ctx, stream TSRMLS_CC); - } else { - ok = enable_peer_verification(ctx, stream TSRMLS_CC); - } - - if (!ok) { - return 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("ciphers", cipherlist); - if (!cipherlist) { - cipherlist = PHP_OPENSSL_DEFAULT_STREAM_CIPHERS; - } - if (SSL_CTX_set_cipher_list(ctx, cipherlist) != 1) { - return NULL; - } - - GET_VER_OPT_STRING("local_cert", certfile); - if (certfile) { - char resolved_path_buff[MAXPATHLEN]; - const char * private_key = NULL; - - if (VCWD_REALPATH(certfile, resolved_path_buff)) { - /* a certificate to use for authentication */ - if (SSL_CTX_use_certificate_chain_file(ctx, resolved_path_buff) != 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; - } - GET_VER_OPT_STRING("local_pk", private_key); - - if (private_key) { - char resolved_path_buff_pk[MAXPATHLEN]; - if (VCWD_REALPATH(private_key, resolved_path_buff_pk)) { - if (SSL_CTX_use_PrivateKey_file(ctx, resolved_path_buff_pk, SSL_FILETYPE_PEM) != 1) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to set private key file `%s'", resolved_path_buff_pk); - return NULL; - } - } - } else { - if (SSL_CTX_use_PrivateKey_file(ctx, resolved_path_buff, SSL_FILETYPE_PEM) != 1) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to set private key file `%s'", resolved_path_buff); - return NULL; - } - } - - #if OPENSSL_VERSION_NUMBER < 0x10001001L - do { - /* Unnecessary as of OpenSSLv1.0.1 (will segfault if used with >= 10001001 ) */ - X509 *cert = NULL; - EVP_PKEY *key = NULL; - SSL *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); - } while (0); - #endif - if (!SSL_CTX_check_private_key(ctx)) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Private key does not match certificate!"); - } - } - } - - ssl = SSL_new(ctx); - - if (ssl) { - /* map SSL => stream */ - SSL_set_ex_data(ssl, ssl_stream_data_index, stream); - } -- - return ssl; - } - /* }}} */ -- static void openssl_add_method_or_alias(const OBJ_NAME *name, void *arg) /* {{{ */ { add_next_index_string((zval*)arg, (char*)name->name, 1); diff --cc ext/openssl/xp_ssl.c index a4046a61fb,e251d2124a..eb9258b15e --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@@ -164,264 -222,612 +222,614 @@@ static int handle_ssl_error(php_stream } return retry; } + /* }}} */ + + static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) /* {{{ */ + { + php_stream *stream; + SSL *ssl; + int err, depth, ret; + zval **val; + unsigned long allowed_depth = OPENSSL_DEFAULT_STREAM_VERIFY_DEPTH; + ++ TSRMLS_FETCH(); ++ + ret = preverify_ok; + + /* determine the status for the current cert */ + 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, php_openssl_get_ssl_stream_data_index()); - static size_t php_openssl_sockop_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC) + /* 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") && - zend_is_true(*val) ++ zend_is_true(*val TSRMLS_CC) + ) { + ret = 1; + } + + /* check the depth */ + GET_VER_OPT_LONG("verify_depth", allowed_depth); + if ((unsigned long)depth > allowed_depth) { + ret = 0; + X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_CHAIN_TOO_LONG); + } + + return ret; + } + /* }}} */ + + static zend_bool matches_wildcard_name(const char *subjectname, const char *certname) /* {{{ */ { - php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; - int didwrite; - - if (sslsock->ssl_active) { - int retry = 1; + char *wildcard = NULL; + int prefix_len, suffix_len, subject_len; - do { - didwrite = SSL_write(sslsock->ssl_handle, buf, count); + if (strcasecmp(subjectname, certname) == 0) { + return 1; + } - if (didwrite <= 0) { - retry = handle_ssl_error(stream, didwrite, 0 TSRMLS_CC); - } else { - break; - } - } while(retry); + if (!(wildcard = strchr(certname, '*'))) { + return 0; + } - if (didwrite > 0) { - php_stream_notify_progress_increment(stream->context, didwrite, 0); - } - } else { - didwrite = php_stream_socket_ops.write(stream, buf, count TSRMLS_CC); + // 1) prefix, if not empty, must match subject + prefix_len = wildcard - certname; + if (prefix_len && strncasecmp(subjectname, certname, prefix_len) != 0) { + return 0; } - if (didwrite < 0) { - didwrite = 0; + suffix_len = strlen(wildcard + 1); + subject_len = strlen(subjectname); + if (suffix_len <= subject_len) { + /* 2) suffix must match + * 3) no . between prefix and suffix + **/ + return strcasecmp(wildcard + 1, subjectname + subject_len - suffix_len) == 0 && + memchr(subjectname + prefix_len, '.', subject_len - suffix_len - prefix_len) == NULL; } - - return didwrite; + + return 0; } + /* }}} */ - static size_t php_openssl_sockop_read(php_stream *stream, char *buf, size_t count TSRMLS_DC) + static zend_bool matches_san_list(X509 *peer, const char *subject_name TSRMLS_DC) /* {{{ */ { - php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; - int nr_bytes = 0; + int i, san_name_len; + zend_bool is_match = 0; + unsigned char *cert_name = NULL; + + GENERAL_NAMES *alt_names = X509_get_ext_d2i(peer, NID_subject_alt_name, 0, 0); + int alt_name_count = sk_GENERAL_NAME_num(alt_names); + + for (i = 0; i < alt_name_count; i++) { + GENERAL_NAME *san = sk_GENERAL_NAME_value(alt_names, i); + if (san->type != GEN_DNS) { + /* we only care about DNS names */ + continue; + } - if (sslsock->ssl_active) { - int retry = 1; + san_name_len = ASN1_STRING_length(san->d.dNSName); + ASN1_STRING_to_UTF8(&cert_name, san->d.dNSName); - do { - nr_bytes = SSL_read(sslsock->ssl_handle, buf, count); + /* prevent null byte poisoning */ + if (san_name_len != strlen((const char*)cert_name)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Peer SAN entry is malformed"); + } else { + is_match = strcasecmp(subject_name, (const char*)cert_name) == 0; + } - if (sslsock->reneg && sslsock->reneg->should_close) { - /* renegotiation rate limiting triggered */ - php_stream_xport_shutdown(stream, (stream_shutdown_t)SHUT_RDWR TSRMLS_CC); - nr_bytes = 0; - stream->eof = 1; - break; - } else if (nr_bytes <= 0) { - retry = handle_ssl_error(stream, nr_bytes, 0 TSRMLS_CC); - stream->eof = (retry == 0 && errno != EAGAIN && !SSL_pending(sslsock->ssl_handle)); - - } else { - /* we got the data */ - break; - } - } while (retry); + OPENSSL_free(cert_name); - if (nr_bytes > 0) { - php_stream_notify_progress_increment(stream->context, nr_bytes, 0); + if (is_match) { + break; } } - else - { - nr_bytes = php_stream_socket_ops.read(stream, buf, count TSRMLS_CC); - } - if (nr_bytes < 0) { - nr_bytes = 0; + return is_match; + } + /* }}} */ + + static zend_bool matches_common_name(X509 *peer, const char *subject_name TSRMLS_DC) /* {{{ */ + { + char buf[1024]; + X509_NAME *cert_name; + zend_bool is_match = 0; + int cert_name_len; + + cert_name = X509_get_subject_name(peer); + cert_name_len = X509_NAME_get_text_by_NID(cert_name, NID_commonName, buf, sizeof(buf)); + + if (cert_name_len == -1) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to locate peer certificate CN"); + } else if (cert_name_len != strlen(buf)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Peer certificate CN=`%.*s' is malformed", cert_name_len, buf); + } else if (matches_wildcard_name(subject_name, buf)) { + is_match = 1; + } else { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Peer certificate CN=`%.*s' did not match expected CN=`%s'", cert_name_len, buf, subject_name); } - return nr_bytes; + return is_match; } + /* }}} */ - static int php_openssl_sockop_close(php_stream *stream, int close_handle TSRMLS_DC) + static int apply_peer_verification_policy(SSL *ssl, X509 *peer, php_stream *stream TSRMLS_DC) /* {{{ */ { + zval **val = NULL; + char *peer_name = NULL; + int err, + must_verify_peer, + must_verify_peer_name, + must_verify_fingerprint, + has_cnmatch_ctx_opt; + php_openssl_netstream_data_t *sslsock = (php_openssl_netstream_data_t*)stream->abstract; - #ifdef PHP_WIN32 - int n; - #endif - if (close_handle) { - if (sslsock->ssl_active) { - SSL_shutdown(sslsock->ssl_handle); - sslsock->ssl_active = 0; - } - if (sslsock->ssl_handle) { - SSL_free(sslsock->ssl_handle); - sslsock->ssl_handle = NULL; - } - if (sslsock->ctx) { - SSL_CTX_free(sslsock->ctx); - sslsock->ctx = NULL; - } - #ifdef PHP_WIN32 - if (sslsock->s.socket == -1) - sslsock->s.socket = SOCK_ERR; - #endif - if (sslsock->s.socket != SOCK_ERR) { - #ifdef PHP_WIN32 - /* prevent more data from coming in */ - shutdown(sslsock->s.socket, SHUT_RD); + must_verify_peer = GET_VER_OPT("verify_peer") - ? zend_is_true(*val) ++ ? zend_is_true(*val TSRMLS_CC) + : sslsock->is_client; - /* try to make sure that the OS sends all data before we close the connection. - * Essentially, we are waiting for the socket to become writeable, which means - * that all pending data has been sent. - * We use a small timeout which should encourage the OS to send the data, - * but at the same time avoid hanging indefinitely. - * */ - do { - n = php_pollfd_for_ms(sslsock->s.socket, POLLOUT, 500); - } while (n == -1 && php_socket_errno() == EINTR); - #endif - closesocket(sslsock->s.socket); - sslsock->s.socket = SOCK_ERR; - } + has_cnmatch_ctx_opt = GET_VER_OPT("CN_match"); + must_verify_peer_name = (has_cnmatch_ctx_opt || GET_VER_OPT("verify_peer_name")) - ? zend_is_true(*val) ++ ? zend_is_true(*val TSRMLS_CC) + : sslsock->is_client; + - must_verify_fingerprint = (GET_VER_OPT("peer_fingerprint") && zend_is_true(*val)); ++ must_verify_fingerprint = (GET_VER_OPT("peer_fingerprint") && zend_is_true(*val TSRMLS_CC)); + + if ((must_verify_peer || must_verify_peer_name || must_verify_fingerprint) && peer == NULL) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not get peer certificate"); + return FAILURE; } - if (sslsock->url_name) { - pefree(sslsock->url_name, php_stream_is_persistent(stream)); + /* Verify the peer against using CA file/path settings */ + if (must_verify_peer) { + 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") && zend_is_true(*val)) { ++ if (GET_VER_OPT("allow_self_signed") && zend_is_true(*val TSRMLS_CC)) { + /* 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 (sslsock->reneg) { - pefree(sslsock->reneg, php_stream_is_persistent(stream)); + /* If a peer_fingerprint match is required this trumps peer and peer_name verification */ + if (must_verify_fingerprint) { + if (Z_TYPE_PP(val) == IS_STRING || Z_TYPE_PP(val) == IS_ARRAY) { + if (!php_x509_fingerprint_match(peer, *val TSRMLS_CC)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Peer fingerprint doesn't match" + ); + return FAILURE; + } + } else { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Expected peer fingerprint must be a string or an array" + ); + } } - pefree(sslsock, php_stream_is_persistent(stream)); + /* verify the host name presented in the peer certificate */ + if (must_verify_peer_name) { + GET_VER_OPT_STRING("peer_name", peer_name); - return 0; - } + if (has_cnmatch_ctx_opt) { + GET_VER_OPT_STRING("CN_match", peer_name); + php_error(E_DEPRECATED, + "the 'CN_match' SSL context option is deprecated in favor of 'peer_name'" + ); + } + /* If no peer name was specified we use the autodetected url name in client environments */ + if (peer_name == NULL && sslsock->is_client) { + peer_name = sslsock->url_name; + } - static int php_openssl_sockop_flush(php_stream *stream TSRMLS_DC) - { - return php_stream_socket_ops.flush(stream TSRMLS_CC); + if (peer_name) { + if (matches_san_list(peer, peer_name TSRMLS_CC)) { + return SUCCESS; + } else if (matches_common_name(peer, peer_name TSRMLS_CC)) { + return SUCCESS; + } else { + return FAILURE; + } + } else { + return FAILURE; + } + } + + return SUCCESS; } + /* }}} */ - static int php_openssl_sockop_stat(php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC) + static int passwd_callback(char *buf, int num, int verify, void *data) /* {{{ */ { - return php_stream_socket_ops.stat(stream, ssb TSRMLS_CC); + 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; } + /* }}} */ - static inline void limit_handshake_reneg(const SSL *ssl) /* {{{ */ + #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; - struct timeval now; - long elapsed_time; + zval **val; + zend_bool is_self_signed = 0; - stream = php_openssl_get_stream_from_ssl_handle(ssl); + TSRMLS_FETCH(); + + stream = (php_stream*)arg; sslsock = (php_openssl_netstream_data_t*)stream->abstract; - gettimeofday(&now, NULL); - /* The initial handshake is never rate-limited */ - if (sslsock->reneg->prev_handshake == 0) { - sslsock->reneg->prev_handshake = now.tv_sec; - return; - } + { /* 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; - elapsed_time = (now.tv_sec - sslsock->reneg->prev_handshake); - sslsock->reneg->prev_handshake = now.tv_sec; - sslsock->reneg->tokens -= (elapsed_time * (sslsock->reneg->limit / sslsock->reneg->window)); + der_len = i2d_X509(x509_store_ctx->cert, &der_buf); + if (der_len < 0) { + unsigned long err_code, e; + char err_buf[512]; - if (sslsock->reneg->tokens < 0) { - sslsock->reneg->tokens = 0; + 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); + } } - ++sslsock->reneg->tokens; - /* The token level exceeds our allowed limit */ - if (sslsock->reneg->tokens > sslsock->reneg->limit) { - zval **val; + { /* 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)}; + LPSTR usages[] = {szOID_PKIX_KP_SERVER_AUTH, szOID_SERVER_GATED_CRYPTO, szOID_SGC_NETSCAPE}; + DWORD chain_flags = 0; + unsigned long allowed_depth = OPENSSL_DEFAULT_STREAM_VERIFY_DEPTH; + unsigned int i; + + enhkey_usage.cUsageIdentifier = 3; + enhkey_usage.rgpszUsageIdentifier = usages; + cert_usage.dwType = USAGE_MATCH_TYPE_OR; + cert_usage.Usage = enhkey_usage; + chain_params.RequestedUsage = cert_usage; + chain_flags = CERT_CHAIN_CACHE_END_CERT | CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT; + + if (!CertGetCertificateChain(NULL, cert_ctx, NULL, NULL, &chain_params, chain_flags, NULL, &cert_chain_ctx)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error getting certificate chain: %s", php_win_err()); + CertFreeCertificateContext(cert_ctx); + RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED); + } - TSRMLS_FETCH(); + /* 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; + } - sslsock->reneg->should_close = 1; + /* check the depth */ + GET_VER_OPT_LONG("verify_depth", allowed_depth); - if (stream->context && SUCCESS == php_stream_context_get_option(stream->context, - "ssl", "reneg_limit_callback", &val) - ) { - zval *param, **params[1], *retval; + for (i = 0; i < cert_chain_ctx->cChain; i++) { + if (cert_chain_ctx->rgpChain[i]->cElement > allowed_depth) { + CertFreeCertificateChain(cert_chain_ctx); + CertFreeCertificateContext(cert_ctx); + RETURN_CERT_VERIFY_FAILURE(X509_V_ERR_CERT_CHAIN_TOO_LONG); + } + } + } - MAKE_STD_ZVAL(param); - php_stream_to_zval(stream, param); - params[0] = ¶m; + { /* 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 peer_name + 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"); + CertFreeCertificateChain(cert_chain_ctx); + CertFreeCertificateContext(cert_ctx); + RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED); + } - /* Closing the stream inside this callback would segfault! */ - stream->flags |= PHP_STREAM_FLAG_NO_FCLOSE; - if (FAILURE == call_user_function_ex(EG(function_table), NULL, *val, &retval, 1, params, 0, NULL TSRMLS_CC)) { - php_error(E_WARNING, "SSL: failed invoking reneg limit notification callback"); + 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) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to convert %s to wide character string", cert_name_utf8); + OPENSSL_free(cert_name_utf8); + CertFreeCertificateChain(cert_chain_ctx); + CertFreeCertificateContext(cert_ctx); + RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED); } - stream->flags ^= PHP_STREAM_FLAG_NO_FCLOSE; - /* If the reneg_limit_callback returned true don't auto-close */ - if (retval != NULL && Z_TYPE_P(retval) == IS_BOOL && Z_BVAL_P(retval) == 1) { - sslsock->reneg->should_close = 0; + 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) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to convert %s to wide character string", cert_name_utf8); + efree(server_name); + OPENSSL_free(cert_name_utf8); + CertFreeCertificateChain(cert_chain_ctx); + CertFreeCertificateContext(cert_ctx); + RETURN_CERT_VERIFY_FAILURE(SSL_R_CERTIFICATE_VERIFY_FAILED); } - FREE_ZVAL(param); - if (retval != NULL) { - zval_ptr_dtor(&retval); + 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); + + efree(server_name); + CertFreeCertificateChain(cert_chain_ctx); + CertFreeCertificateContext(cert_ctx); + + 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") && zend_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); } - } else { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "SSL: client-initiated handshake rate limit exceeded by peer"); } } - } - /* }}} */ - static void php_openssl_info_callback(const SSL *ssl, int where, int ret) /* {{{ */ - { - /* Rate-limit client-initiated handshake renegotiation to prevent DoS */ - if (where & SSL_CB_HANDSHAKE_START) { - limit_handshake_reneg(ssl); - } + return 1; } /* }}} */ + #endif - static inline void init_handshake_limiting(php_stream *stream, php_openssl_netstream_data_t *sslsock) /* {{{ */ + static long load_stream_cafile(X509_STORE *cert_store, const char *cafile TSRMLS_DC) /* {{{ */ { - zval **val; - long limit = DEFAULT_RENEG_LIMIT; - long window = DEFAULT_RENEG_WINDOW; + php_stream *stream; + X509 *cert; + BIO *buffer; + int buffer_active = 0; + char *line = NULL; + size_t line_len; + long certs_added = 0; - if (stream->context && - SUCCESS == php_stream_context_get_option(stream->context, - "ssl", "reneg_limit", &val) - ) { - convert_to_long(*val); - limit = Z_LVAL_PP(val); + stream = php_stream_open_wrapper(cafile, "rb", 0, NULL); + + if (stream == NULL) { + php_error(E_WARNING, "failed loading cafile stream: `%s'", cafile); + return 0; + } else if (stream->wrapper->is_url) { + php_stream_close(stream); + php_error(E_WARNING, "remote cafile streams are disabled for security purposes"); + return 0; } - /* No renegotiation rate-limiting */ - if (limit < 0) { - return; + cert_start: { + line = php_stream_get_line(stream, NULL, 0, &line_len); + if (line == NULL) { + goto stream_complete; + } else if (!strcmp(line, "-----BEGIN CERTIFICATE-----\n") || + !strcmp(line, "-----BEGIN CERTIFICATE-----\r\n") + ) { + buffer = BIO_new(BIO_s_mem()); + buffer_active = 1; + goto cert_line; + } else { + efree(line); + goto cert_start; + } } - if (stream->context && - SUCCESS == php_stream_context_get_option(stream->context, - "ssl", "reneg_window", &val) - ) { - convert_to_long(*val); - window = Z_LVAL_PP(val); + cert_line: { + BIO_puts(buffer, line); + efree(line); + line = php_stream_get_line(stream, NULL, 0, &line_len); + if (line == NULL) { + goto stream_complete; + } else if (!strcmp(line, "-----END CERTIFICATE-----") || + !strcmp(line, "-----END CERTIFICATE-----\n") || + !strcmp(line, "-----END CERTIFICATE-----\r\n") + ) { + goto add_cert; + } else { + goto cert_line; + } } - sslsock->reneg = (void*)pemalloc(sizeof(php_openssl_handshake_bucket_t), - php_stream_is_persistent(stream) - ); + add_cert: { + BIO_puts(buffer, line); + efree(line); + cert = PEM_read_bio_X509(buffer, NULL, 0, NULL); + BIO_free(buffer); + buffer_active = 0; + if (cert && X509_STORE_add_cert(cert_store, cert)) { + ++certs_added; + } + goto cert_start; + } - sslsock->reneg->limit = limit; - sslsock->reneg->window = window; - sslsock->reneg->prev_handshake = 0; - sslsock->reneg->tokens = 0; - sslsock->reneg->should_close = 0; + stream_complete: { + php_stream_close(stream); + if (buffer_active == 1) { + BIO_free(buffer); + } + } + + if (certs_added == 0) { + php_error(E_WARNING, "no valid certs found cafile stream: `%s'", cafile); + } + + return certs_added; + } + /* }}} */ + + 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; + } + + if (!capath) { + capath = zend_ini_string("openssl.capath", sizeof("openssl.capath"), 0); + capath = strlen(capath) ? capath : NULL; + } + + if (cafile || capath) { + if (!SSL_CTX_load_verify_locations(ctx, cafile, capath)) { + if (cafile && !load_stream_cafile(SSL_CTX_get_cert_store(ctx), cafile TSRMLS_CC)) { + return FAILURE; + } + } + } 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 FAILURE; + } + #endif + } + + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback); + + return SUCCESS; + } + /* }}} */ + + static void disable_peer_verification(SSL_CTX *ctx, php_stream *stream TSRMLS_DC) /* {{{ */ + { + SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); + } + /* }}} */ + + static int set_local_cert(SSL_CTX *ctx, php_stream *stream TSRMLS_DC) /* {{{ */ + { + zval **val = NULL; + char *certfile = NULL; - SSL_CTX_set_info_callback(sslsock->ctx, php_openssl_info_callback); + GET_VER_OPT_STRING("local_cert", certfile); + + if (certfile) { + char resolved_path_buff[MAXPATHLEN]; + const char * private_key = NULL; + + if (VCWD_REALPATH(certfile, resolved_path_buff)) { + /* a certificate to use for authentication */ + if (SSL_CTX_use_certificate_chain_file(ctx, resolved_path_buff) != 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 FAILURE; + } + GET_VER_OPT_STRING("local_pk", private_key); + + if (private_key) { + char resolved_path_buff_pk[MAXPATHLEN]; + if (VCWD_REALPATH(private_key, resolved_path_buff_pk)) { + if (SSL_CTX_use_PrivateKey_file(ctx, resolved_path_buff_pk, SSL_FILETYPE_PEM) != 1) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to set private key file `%s'", resolved_path_buff_pk); + return FAILURE; + } + } + } else { + if (SSL_CTX_use_PrivateKey_file(ctx, resolved_path_buff, SSL_FILETYPE_PEM) != 1) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to set private key file `%s'", resolved_path_buff); + return FAILURE; + } + } + + #if OPENSSL_VERSION_NUMBER < 0x10001001L + do { + /* Unnecessary as of OpenSSLv1.0.1 (will segfault if used with >= 10001001 ) */ + X509 *cert = NULL; + EVP_PKEY *key = NULL; + SSL *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); + } while (0); + #endif + if (!SSL_CTX_check_private_key(ctx)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Private key does not match certificate!"); + } + } + } + + return SUCCESS; } /* }}} */ @@@ -562,272 -1135,376 +1137,376 @@@ static int set_server_specific_opts(php } #endif - #if OPENSSL_VERSION_NUMBER >= 0x10000000L - { - if (stream->context && (FAILURE == php_stream_context_get_option( - stream->context, "ssl", "disable_compression", &val) || - zend_is_true(*val TSRMLS_CC)) - ) { - SSL_CTX_set_options(sslsock->ctx, SSL_OP_NO_COMPRESSION); + if (php_stream_context_get_option(stream->context, "ssl", "dh_param", &val) == SUCCESS) { + convert_to_string_ex(val); + if (FAILURE == set_server_dh_param(ctx, Z_STRVAL_PP(val) TSRMLS_CC)) { + return FAILURE; } } - #endif - if (!sslsock->is_client && stream->context && SUCCESS == php_stream_context_get_option( + if (FAILURE == set_server_rsa_key(stream, ctx TSRMLS_CC)) { + return FAILURE; + } + + if (SUCCESS == php_stream_context_get_option( stream->context, "ssl", "honor_cipher_order", &val) && - zend_is_true(*val) + zend_is_true(*val TSRMLS_CC) ) { - SSL_CTX_set_options(sslsock->ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); + ssl_ctx_options |= SSL_OP_CIPHER_SERVER_PREFERENCE; } - if (!sslsock->is_client && stream->context && SUCCESS == php_stream_context_get_option( + if (SUCCESS == php_stream_context_get_option( stream->context, "ssl", "single_dh_use", &val) && - zend_is_true(*val) + zend_is_true(*val TSRMLS_CC) ) { - SSL_CTX_set_options(sslsock->ctx, SSL_OP_SINGLE_DH_USE); + ssl_ctx_options |= SSL_OP_SINGLE_DH_USE; } - if (!sslsock->is_client && stream->context && SUCCESS == php_stream_context_get_option( + if (SUCCESS == php_stream_context_get_option( stream->context, "ssl", "single_ecdh_use", &val) && - zend_is_true(*val) + zend_is_true(*val TSRMLS_CC) ) { - SSL_CTX_set_options(sslsock->ctx, SSL_OP_SINGLE_ECDH_USE); + ssl_ctx_options |= SSL_OP_SINGLE_ECDH_USE; } - sslsock->ssl_handle = php_SSL_new_from_context(sslsock->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(sslsock->ctx); - sslsock->ctx = NULL; - return -1; - } + SSL_CTX_set_options(ctx, ssl_ctx_options); - if (!sslsock->is_client && stream->context && SUCCESS == php_stream_context_get_option( - stream->context, "ssl", "honor_cipher_order", &val) && - zend_is_true(*val TSRMLS_CC) - ) { - SSL_CTX_set_options(sslsock->ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); - } + return SUCCESS; + } + /* }}} */ - #ifdef SSL_MODE_RELEASE_BUFFERS - mode = SSL_get_mode(sslsock->ssl_handle); - SSL_set_mode(sslsock->ssl_handle, mode | SSL_MODE_RELEASE_BUFFERS); - #endif + #ifdef HAVE_SNI + static int server_sni_callback(SSL *ssl_handle, int *al, void *arg) /* {{{ */ + { + php_stream *stream; + php_openssl_netstream_data_t *sslsock; + unsigned i; + const char *server_name; - if (!sslsock->is_client) { - init_handshake_limiting(stream, sslsock); + server_name = SSL_get_servername(ssl_handle, TLSEXT_NAMETYPE_host_name); + + if (!server_name) { + return SSL_TLSEXT_ERR_NOACK; } - if (!SSL_set_fd(sslsock->ssl_handle, sslsock->s.socket)) { - handle_ssl_error(stream, 0, 1 TSRMLS_CC); + stream = (php_stream*)SSL_get_ex_data(ssl_handle, php_openssl_get_ssl_stream_data_index()); + sslsock = (php_openssl_netstream_data_t*)stream->abstract; + + if (!(sslsock->sni_cert_count && sslsock->sni_certs)) { + return SSL_TLSEXT_ERR_NOACK; } - if (cparam->inputs.session) { - if (cparam->inputs.session->ops != &php_openssl_socket_ops) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "supplied session stream must be an SSL enabled stream"); - } else if (((php_openssl_netstream_data_t*)cparam->inputs.session->abstract)->ssl_handle == NULL) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "supplied SSL session stream is not initialized"); - } else { - SSL_copy_session_id(sslsock->ssl_handle, ((php_openssl_netstream_data_t*)cparam->inputs.session->abstract)->ssl_handle); + for (i=0; i < sslsock->sni_cert_count; i++) { + if (matches_wildcard_name(server_name, sslsock->sni_certs[i].name)) { + SSL_set_SSL_CTX(ssl_handle, sslsock->sni_certs[i].ctx); + return SSL_TLSEXT_ERR_OK; } } - return 0; + + return SSL_TLSEXT_ERR_NOACK; } + /* }}} */ - static zval *php_capture_ssl_session_meta(SSL *ssl_handle) + static int enable_server_sni(php_stream *stream, php_openssl_netstream_data_t *sslsock TSRMLS_DC) { - zval *meta_arr; - char *proto_str; - long proto = SSL_version(ssl_handle); - const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl_handle); + zval **val; + zval **current; + char *key; + uint key_len; + ulong key_index; + int key_type; + HashPosition pos; + int i = 0; + char resolved_path_buff[MAXPATHLEN]; + SSL_CTX *ctx; + + /* If the stream ctx disables SNI we're finished here */ - if (GET_VER_OPT("SNI_enabled") && !zend_is_true(*val)) { ++ if (GET_VER_OPT("SNI_enabled") && !zend_is_true(*val TSRMLS_CC)) { + return SUCCESS; + } - switch (proto) { - #if OPENSSL_VERSION_NUMBER >= 0x10001001L - case TLS1_2_VERSION: proto_str = "TLSv1.2"; break; - case TLS1_1_VERSION: proto_str = "TLSv1.1"; break; - #endif - case TLS1_VERSION: proto_str = "TLSv1"; break; - case SSL3_VERSION: proto_str = "SSLv3"; break; - case SSL2_VERSION: proto_str = "SSLv2"; break; - default: proto_str = "UNKNOWN"; + /* If no SNI cert array is specified we're finished here */ + if (!GET_VER_OPT("SNI_server_certs")) { + return SUCCESS; } - MAKE_STD_ZVAL(meta_arr); - array_init(meta_arr); - add_assoc_string(meta_arr, "protocol", proto_str, 1); - add_assoc_string(meta_arr, "cipher_name", (char *) SSL_CIPHER_get_name(cipher), 1); - add_assoc_long(meta_arr, "cipher_bits", SSL_CIPHER_get_bits(cipher, NULL)); - add_assoc_string(meta_arr, "cipher_version", SSL_CIPHER_get_version(cipher), 1); + if (Z_TYPE_PP(val) != IS_ARRAY) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "SNI_server_certs requires an array mapping host names to cert paths" + ); + return FAILURE; + } - return meta_arr; - } + sslsock->sni_cert_count = zend_hash_num_elements(Z_ARRVAL_PP(val)); + if (sslsock->sni_cert_count == 0) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "SNI_server_certs host cert array must not be empty" + ); + return FAILURE; + } - static int php_set_server_rsa_key(php_stream *stream, - php_openssl_netstream_data_t *sslsock - TSRMLS_DC) - { - zval ** val; - int rsa_key_size; - RSA* rsa; - int retval = 1; + sslsock->sni_certs = (php_openssl_sni_cert_t*)safe_pemalloc(sslsock->sni_cert_count, + sizeof(php_openssl_sni_cert_t), 0, php_stream_is_persistent(stream) + ); - if (php_stream_context_get_option(stream->context, "ssl", "rsa_key_size", &val) == SUCCESS) { - rsa_key_size = (int) Z_LVAL_PP(val); - if ((rsa_key_size != 1) && (rsa_key_size & (rsa_key_size - 1))) { + for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_PP(val), &pos); + zend_hash_get_current_data_ex(Z_ARRVAL_PP(val), (void **)¤t, &pos) == SUCCESS; + zend_hash_move_forward_ex(Z_ARRVAL_PP(val), &pos) + ) { + key_type = zend_hash_get_current_key_ex(Z_ARRVAL_PP(val), &key, &key_len, &key_index, 0, &pos); + if (key_type != HASH_KEY_IS_STRING) { php_error_docref(NULL TSRMLS_CC, E_WARNING, - "RSA key size requires a power of 2: %d", - rsa_key_size); + "SNI_server_certs array requires string host name keys" + ); + return FAILURE; + } - rsa_key_size = 2048; + if (VCWD_REALPATH(Z_STRVAL_PP(current), resolved_path_buff)) { + /* The hello method is not inherited by SSL structs when assigning a new context + * inside the SNI callback, so the just use SSLv23 */ + ctx = SSL_CTX_new(SSLv23_server_method()); + + if (SSL_CTX_use_certificate_chain_file(ctx, resolved_path_buff) != 1) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "failed setting local cert chain file `%s'; " \ + "check that your cafile/capath settings include " \ + "details of your certificate and its issuer", + resolved_path_buff + ); + SSL_CTX_free(ctx); + return FAILURE; + } else if (SSL_CTX_use_PrivateKey_file(ctx, resolved_path_buff, SSL_FILETYPE_PEM) != 1) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "failed setting private key from file `%s'", + resolved_path_buff + ); + SSL_CTX_free(ctx); + return FAILURE; + } else { + sslsock->sni_certs[i].name = pestrdup(key, php_stream_is_persistent(stream)); + sslsock->sni_certs[i].ctx = ctx; + ++i; + } + } else { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "failed setting local cert chain file `%s'; file not found", + Z_STRVAL_PP(current) + ); + return FAILURE; } - } else { - rsa_key_size = 2048; } - rsa = RSA_generate_key(rsa_key_size, RSA_F4, NULL, NULL); + SSL_CTX_set_tlsext_servername_callback(sslsock->ctx, server_sni_callback); - if (!SSL_set_tmp_rsa(sslsock->ssl_handle, rsa)) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "Failed setting RSA key"); - retval = 0; + return SUCCESS; + } + -static void enable_client_sni(php_stream *stream, php_openssl_netstream_data_t *sslsock) /* {{{ */ ++static void enable_client_sni(php_stream *stream, php_openssl_netstream_data_t *sslsock TSRMLS_DC) /* {{{ */ + { + zval **val; + char *sni_server_name; + + /* If SNI is explicitly disabled we're finished here */ - if (GET_VER_OPT("SNI_enabled") && !zend_is_true(*val)) { ++ if (GET_VER_OPT("SNI_enabled") && !zend_is_true(*val TSRMLS_CC)) { + return; } - RSA_free(rsa); + sni_server_name = sslsock->url_name; - return retval; - } + GET_VER_OPT_STRING("peer_name", sni_server_name); + if (GET_VER_OPT("SNI_server_name")) { + GET_VER_OPT_STRING("SNI_server_name", sni_server_name); + php_error(E_DEPRECATED, "SNI_server_name is deprecated in favor of peer_name"); + } - static int php_set_server_dh_param(php_openssl_netstream_data_t *sslsock, - char *dh_path - TSRMLS_DC) + if (sni_server_name) { + SSL_set_tlsext_host_name(sslsock->ssl_handle, sni_server_name); + } + } + /* }}} */ + #endif + + int php_openssl_setup_crypto(php_stream *stream, + php_openssl_netstream_data_t *sslsock, + php_stream_xport_crypto_param *cparam + TSRMLS_DC) /* {{{ */ { - DH *dh; - BIO* bio; + const SSL_METHOD *method; + long ssl_ctx_options; + long method_flags; + char *cipherlist = NULL; + zval **val; - bio = BIO_new_file(dh_path, "r"); + if (sslsock->ssl_handle) { + if (sslsock->s.is_blocked) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL/TLS already set-up for this stream"); + return FAILURE; + } else { + return SUCCESS; + } + } - if (bio == NULL) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "Invalid dh_param file: %s", - dh_path); + ERR_clear_error(); - return 0; - } + /* We need to do slightly different things based on client/server method + * so lets remember which method was selected */ + sslsock->is_client = cparam->inputs.method & STREAM_CRYPTO_IS_CLIENT; + method_flags = ((cparam->inputs.method >> 1) << 1); - dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); - BIO_free(bio); + /* Should we use a specific crypto method or is generic SSLv23 okay? */ + if ((method_flags & (method_flags-1)) == 0) { + ssl_ctx_options = SSL_OP_ALL; + method = php_select_crypto_method(method_flags, sslsock->is_client TSRMLS_CC); + if (method == NULL) { + return FAILURE; + } + } else { + method = sslsock->is_client ? SSLv23_client_method() : SSLv23_server_method(); + ssl_ctx_options = php_get_crypto_method_ctx_flags(method_flags TSRMLS_CC); + if (ssl_ctx_options == -1) { + return FAILURE; + } + } - if (dh == NULL) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "Failed reading DH params from file: %s", - dh_path); + #if OPENSSL_VERSION_NUMBER >= 0x10001001L + sslsock->ctx = SSL_CTX_new(method); + #else + /* Avoid const warning with old versions */ + sslsock->ctx = SSL_CTX_new((SSL_METHOD*)method); + #endif - return 0; + if (sslsock->ctx == NULL) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL context creation failure"); + return FAILURE; } - if (SSL_set_tmp_dh(sslsock->ssl_handle, dh) < 0) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "DH param assignment failed"); - DH_free(dh); - return 0; + #if OPENSSL_VERSION_NUMBER >= 0x0090806fL - if (GET_VER_OPT("no_ticket") && zend_is_true(*val)) { ++ if (GET_VER_OPT("no_ticket") && zend_is_true(*val TSRMLS_CC)) { + ssl_ctx_options |= SSL_OP_NO_TICKET; } + #endif - DH_free(dh); - - return 1; - } + #if OPENSSL_VERSION_NUMBER >= 0x0090605fL + ssl_ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; + #endif - static int php_enable_server_crypto_opts(php_stream *stream, - php_openssl_netstream_data_t *sslsock - TSRMLS_DC) - { - zval **val; - #ifdef HAVE_ECDH - int curve_nid; - EC_KEY *ecdh; + #if OPENSSL_VERSION_NUMBER >= 0x10000000L - if (!GET_VER_OPT("disable_compression") || zend_is_true(*val)) { ++ if (!GET_VER_OPT("disable_compression") || zend_is_true(*val TSRMLS_CC)) { + ssl_ctx_options |= SSL_OP_NO_COMPRESSION; + } #endif - if (php_stream_context_get_option(stream->context, "ssl", "dh_param", &val) == SUCCESS) { - convert_to_string_ex(val); - if (!php_set_server_dh_param(sslsock, Z_STRVAL_PP(val) TSRMLS_CC)) { - return 0; - } - if (GET_VER_OPT("verify_peer") && !zend_is_true(*val)) { ++ if (GET_VER_OPT("verify_peer") && !zend_is_true(*val TSRMLS_CC)) { + disable_peer_verification(sslsock->ctx, stream TSRMLS_CC); + } else if (FAILURE == enable_peer_verification(sslsock->ctx, stream TSRMLS_CC)) { + return FAILURE; } - #ifdef HAVE_ECDH + /* callback for the passphrase (for localcert) */ + if (GET_VER_OPT("passphrase")) { + SSL_CTX_set_default_passwd_cb_userdata(sslsock->ctx, stream); + SSL_CTX_set_default_passwd_cb(sslsock->ctx, passwd_callback); + } - if (php_stream_context_get_option(stream->context, "ssl", "ecdh_curve", &val) == SUCCESS) { - char *curve_str; - convert_to_string_ex(val); - curve_str = Z_STRVAL_PP(val); - curve_nid = OBJ_sn2nid(curve_str); - if (curve_nid == NID_undef) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "Invalid ECDH curve: %s", - curve_str); + GET_VER_OPT_STRING("ciphers", cipherlist); + if (!cipherlist) { + cipherlist = OPENSSL_DEFAULT_STREAM_CIPHERS; + } + if (SSL_CTX_set_cipher_list(sslsock->ctx, cipherlist) != 1) { + return FAILURE; + } - return 0; - } - } else { - curve_nid = NID_X9_62_prime256v1; + if (FAILURE == set_local_cert(sslsock->ctx, stream TSRMLS_CC)) { + return FAILURE; } - ecdh = EC_KEY_new_by_curve_name(curve_nid); - if (ecdh == NULL) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "Failed generating ECDH curve"); + SSL_CTX_set_options(sslsock->ctx, ssl_ctx_options); - return 0; + if (sslsock->is_client == 0 && + stream->context && + FAILURE == set_server_specific_opts(stream, sslsock->ctx TSRMLS_CC) + ) { + return FAILURE; } - SSL_set_tmp_ecdh(sslsock->ssl_handle, ecdh); - EC_KEY_free(ecdh); + sslsock->ssl_handle = SSL_new(sslsock->ctx); + if (sslsock->ssl_handle == NULL) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL handle creation failure"); + SSL_CTX_free(sslsock->ctx); + sslsock->ctx = NULL; + return FAILURE; + } else { + SSL_set_ex_data(sslsock->ssl_handle, php_openssl_get_ssl_stream_data_index(), stream); + } - #else + if (!SSL_set_fd(sslsock->ssl_handle, sslsock->s.socket)) { + handle_ssl_error(stream, 0, 1 TSRMLS_CC); + } - if (php_stream_context_get_option(stream->context, "ssl", "ecdh_curve", &val) == SUCCESS) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, - "ECDH curve support not compiled into the OpenSSL lib against which PHP is linked"); + #ifdef HAVE_SNI + /* Enable server-side SNI */ + if (sslsock->is_client == 0 && enable_server_sni(stream, sslsock TSRMLS_CC) == FAILURE) { + return FAILURE; + } + #endif + + /* Enable server-side handshake renegotiation rate-limiting */ + if (sslsock->is_client == 0) { + init_server_reneg_limit(stream, sslsock); } + #ifdef SSL_MODE_RELEASE_BUFFERS + do { + long mode = SSL_get_mode(sslsock->ssl_handle); + SSL_set_mode(sslsock->ssl_handle, mode | SSL_MODE_RELEASE_BUFFERS); + } while (0); #endif - if (!php_set_server_rsa_key(stream, sslsock TSRMLS_CC)) { - return 0; + if (cparam->inputs.session) { + if (cparam->inputs.session->ops != &php_openssl_socket_ops) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "supplied session stream must be an SSL enabled stream"); + } else if (((php_openssl_netstream_data_t*)cparam->inputs.session->abstract)->ssl_handle == NULL) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "supplied SSL session stream is not initialized"); + } else { + SSL_copy_session_id(sslsock->ssl_handle, ((php_openssl_netstream_data_t*)cparam->inputs.session->abstract)->ssl_handle); + } } - return 1; + return SUCCESS; } + /* }}} */ - #if OPENSSL_VERSION_NUMBER >= 0x00908070L && !defined(OPENSSL_NO_TLSEXT) - static inline void enable_client_sni(php_stream *stream, php_openssl_netstream_data_t *sslsock) /* {{{ */ + static zval *capture_session_meta(SSL *ssl_handle) /* {{{ */ { - zval **val; - - TSRMLS_FETCH(); + zval *meta_arr; + char *proto_str; + long proto = SSL_version(ssl_handle); + const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl_handle); - if (stream->context && - (php_stream_context_get_option(stream->context, "ssl", "SNI_enabled", &val) == FAILURE - || zend_is_true(*val TSRMLS_CC)) - ) { - if (php_stream_context_get_option(stream->context, "ssl", "SNI_server_name", &val) == SUCCESS) { - convert_to_string_ex(val); - SSL_set_tlsext_host_name(sslsock->ssl_handle, Z_STRVAL_PP(val)); - } else if (sslsock->url_name) { - SSL_set_tlsext_host_name(sslsock->ssl_handle, sslsock->url_name); - } - } else if (!stream->context && sslsock->url_name) { - SSL_set_tlsext_host_name(sslsock->ssl_handle, sslsock->url_name); + switch (proto) { + #if OPENSSL_VERSION_NUMBER >= 0x10001001L + case TLS1_2_VERSION: proto_str = "TLSv1.2"; break; + case TLS1_1_VERSION: proto_str = "TLSv1.1"; break; + #endif + case TLS1_VERSION: proto_str = "TLSv1"; break; + case SSL3_VERSION: proto_str = "SSLv3"; break; + case SSL2_VERSION: proto_str = "SSLv2"; break; + default: proto_str = "UNKNOWN"; } + + MAKE_STD_ZVAL(meta_arr); + array_init(meta_arr); + add_assoc_string(meta_arr, "protocol", proto_str, 1); + add_assoc_string(meta_arr, "cipher_name", (char *) SSL_CIPHER_get_name(cipher), 1); + add_assoc_long(meta_arr, "cipher_bits", SSL_CIPHER_get_bits(cipher, NULL)); + add_assoc_string(meta_arr, "cipher_version", SSL_CIPHER_get_version(cipher), 1); + + return meta_arr; } /* }}} */ - #endif - static int capture_peer_certs(php_stream *stream, - php_openssl_netstream_data_t *sslsock, - X509 *peer_cert - TSRMLS_DC) + static int capture_peer_certs(php_stream *stream, php_openssl_netstream_data_t *sslsock, X509 *peer_cert TSRMLS_DC) /* {{{ */ { zval **val, *zcert; int cert_captured = 0; @@@ -892,18 -1570,12 +1572,12 @@@ static int php_openssl_enable_crypto(ph int blocked = sslsock->s.is_blocked, has_timeout = 0; - #if OPENSSL_VERSION_NUMBER >= 0x00908070L && !defined(OPENSSL_NO_TLSEXT) - { + #ifdef HAVE_SNI if (sslsock->is_client) { -- enable_client_sni(stream, sslsock); ++ enable_client_sni(stream, sslsock TSRMLS_CC); } - } #endif - if (!sslsock->is_client && !php_enable_server_crypto_opts(stream, sslsock TSRMLS_CC)) { - return -1; - } - if (!sslsock->state_set) { if (sslsock->is_client) { SSL_set_connect_state(sslsock->ssl_handle); @@@ -997,9 -1669,9 +1671,9 @@@ if (SUCCESS == php_stream_context_get_option(stream->context, "ssl", "capture_session_meta", &val) && - zend_is_true(*val) + zend_is_true(*val TSRMLS_CC) ) { - zval *meta_arr = php_capture_ssl_session_meta(sslsock->ssl_handle); + zval *meta_arr = capture_session_meta(sslsock->ssl_handle); php_stream_context_set_option(stream->context, "ssl", "session_meta", meta_arr); zval_dtor(meta_arr); efree(meta_arr);