diff --git a/Zend/Optimizer/zend_func_infos.h b/Zend/Optimizer/zend_func_infos.h index 0add5a32f911..7367018fef4d 100644 --- a/Zend/Optimizer/zend_func_infos.h +++ b/Zend/Optimizer/zend_func_infos.h @@ -297,6 +297,7 @@ static const func_info_t func_infos[] = { F1("openssl_get_curve_names", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_FALSE), #endif F1("openssl_get_cert_locations", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_STRING), + F1("openssl_get_channel_binding", MAY_BE_STRING|MAY_BE_NULL), FN("pcntl_signal_get_handler", MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_OBJECT|MAY_BE_LONG), FN("preg_replace", MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_STRING|MAY_BE_NULL), FN("preg_filter", MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_STRING|MAY_BE_NULL), diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index b05ee7418d0c..8f7619c2eccc 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -5104,3 +5104,63 @@ PHP_FUNCTION(openssl_random_pseudo_bytes) } } /* }}} */ + +/* {{{ */ +PHP_FUNCTION(openssl_get_channel_binding) +{ + php_stream *stream = NULL; + zend_string *type = NULL; + zend_string *result = NULL; + int type_code; + int ret; + + ZEND_PARSE_PARAMETERS_START(2, 2) + PHP_Z_PARAM_STREAM(stream) + Z_PARAM_STR(type) + ZEND_PARSE_PARAMETERS_END(); + + if (zend_string_equals_literal(type, "tls-unique")) { + type_code = PHP_OSSL_CB_TLS_UNIQUE; + } else if (zend_string_equals_literal(type, "tls-server-end-point")) { + type_code = PHP_OSSL_CB_TLS_SERVER_ENDPOINT; + } else if (zend_string_equals_literal(type, "tls-exporter")) { + type_code = PHP_OSSL_CB_TLS_EXPORTER; + } else { + zend_value_error( + "%s(): argument #2 ($channel_binding_type) \"%s\" is not a known " + "channel binding type, expected \"tls-unique\", " + "\"tls-server-end-point\" or \"tls-exporter\"", + get_active_function_name(), ZSTR_VAL(type)); + RETURN_THROWS(); + } + + ret = php_openssl_netstream_get_channel_binding(stream, type_code, &result); + switch (ret) { + case PHP_OSSL_CB_OK: + RETURN_STR(result); + case PHP_OSSL_CB_NOT_APPLICABLE: + RETURN_NULL(); + case PHP_OSSL_CB_NOT_TLS: + zend_throw_exception_ex(php_openssl_exception_ce, 0, + "Stream does not have transport encryption enabled"); + RETURN_THROWS(); + case PHP_OSSL_CB_ERROR: + { + unsigned long err = ERR_peek_last_error(); + if (err != 0) { + char errstr[256]; + + (void)ERR_error_string_n(err, errstr, sizeof(errstr)); + zend_throw_exception_ex(php_openssl_exception_ce, 0, + "Failed to get channel binding data: %s", errstr); + } else { + zend_throw_exception_ex(php_openssl_exception_ce, 0, + "Failed to get channel binding data"); + } + } + RETURN_THROWS(); + default: + ZEND_UNREACHABLE(); + } +} +/* }}} */ diff --git a/ext/openssl/openssl.stub.php b/ext/openssl/openssl.stub.php index 3d3fa3ea634f..712cc8d22e94 100644 --- a/ext/openssl/openssl.stub.php +++ b/ext/openssl/openssl.stub.php @@ -776,4 +776,10 @@ function openssl_password_hash(string $algo, #[\SensitiveParameter] string $pass function openssl_password_verify(string $algo, #[\SensitiveParameter] string $password, string $hash): bool {} #endif + /** + * @param resource $stream + * @refcount 1 + */ +function openssl_get_channel_binding($stream, string $channel_binding_type): ?string {} + } diff --git a/ext/openssl/openssl_arginfo.h b/ext/openssl/openssl_arginfo.h index c7fdf82c7861..0f7ea643a219 100644 --- a/ext/openssl/openssl_arginfo.h +++ b/ext/openssl/openssl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit openssl.stub.php instead. - * Stub hash: 7cad995b734d69f98d489edb97a7878a4ea8f47e */ + * Stub hash: 0c7e0942232271e2696203ab80ba01958b46c5ae */ #include "zend_attributes.h" #include "zend_constants.h" @@ -411,6 +411,11 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_password_verify, 0, 3, _ ZEND_END_ARG_INFO() #endif +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_get_channel_binding, 0, 2, IS_STRING, 1) + ZEND_ARG_INFO(0, stream) + ZEND_ARG_TYPE_INFO(0, channel_binding_type, IS_STRING, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Openssl_Psk___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, psk, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, identity, IS_STRING, 1, "null") @@ -516,6 +521,7 @@ ZEND_FUNCTION(openssl_get_cert_locations); ZEND_FUNCTION(openssl_password_hash); ZEND_FUNCTION(openssl_password_verify); #endif +ZEND_FUNCTION(openssl_get_channel_binding); ZEND_METHOD(Openssl_Psk, __construct); ZEND_METHOD(Openssl_Session, export); ZEND_METHOD(Openssl_Session, import); @@ -600,6 +606,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(openssl_password_hash, arginfo_openssl_password_hash) ZEND_FE(openssl_password_verify, arginfo_openssl_password_verify) #endif + ZEND_FE(openssl_get_channel_binding, arginfo_openssl_get_channel_binding) ZEND_FE_END }; diff --git a/ext/openssl/php_openssl.h b/ext/openssl/php_openssl.h index 588cca0e93d8..25516d33bbee 100644 --- a/ext/openssl/php_openssl.h +++ b/ext/openssl/php_openssl.h @@ -236,6 +236,25 @@ extern zend_class_entry *php_openssl_session_ce; void php_openssl_session_object_init(zval *zv, SSL_SESSION *session); bool php_openssl_is_session_ce(zval *val); SSL_SESSION *php_openssl_session_from_zval(zval *zv); +int php_openssl_netstream_get_channel_binding(struct _php_stream *stream, int type, zend_string **out); + +/* Channel binding data types (RFC 5929, RFC 9266), as used for SASL + * channel binding (e.g. SCRAM-SHA-*-PLUS, RFC 5802 / RFC 5801). The string + * values accepted by stream_get_channel_binding() are the IANA "Channel + * Binding" registry names. */ +enum php_openssl_channel_binding_type { + PHP_OSSL_CB_TLS_UNIQUE = 0, + PHP_OSSL_CB_TLS_SERVER_ENDPOINT, + PHP_OSSL_CB_TLS_EXPORTER, +}; + +/* Result of php_openssl_netstream_get_channel_binding(). */ +enum php_openssl_channel_binding_result { + PHP_OSSL_CB_OK = 0, /* *out holds a zend_string with the data */ + PHP_OSSL_CB_NOT_APPLICABLE, /* *out = NULL (e.g. tls-unique over TLS 1.3) */ + PHP_OSSL_CB_NOT_TLS, /* stream is not an active TLS stream */ + PHP_OSSL_CB_ERROR, /* an OpenSSL-level failure occurred */ +}; #if defined(HAVE_OPENSSL_ARGON2) diff --git a/ext/openssl/tests/openssl_get_channel_binding_errors.phpt b/ext/openssl/tests/openssl_get_channel_binding_errors.phpt new file mode 100644 index 000000000000..7563b0321ead --- /dev/null +++ b/ext/openssl/tests/openssl_get_channel_binding_errors.phpt @@ -0,0 +1,49 @@ +--TEST-- +openssl_get_channel_binding(): argument and type error handling +--EXTENSIONS-- +openssl +--FILE-- + ValueError (checked before the stream). */ +try { + openssl_get_channel_binding(fopen("php://memory", "r"), "not-a-type"); + echo "no error\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +/* Case-sensitivity: a mismatched case is also unknown. */ +try { + openssl_get_channel_binding(fopen("php://memory", "r"), "TLS-UNIQUE"); + echo "no error\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +/* Non-stream argument -> TypeError. */ +try { + openssl_get_channel_binding(123, "tls-unique"); + echo "no error\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + +/* A stream without transport encryption -> RuntimeException. */ +$plain = fopen("php://memory", "r"); +foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { + try { + openssl_get_channel_binding($plain, $t); + echo "$t: no error\n"; + } catch (Throwable $e) { + echo $t, ': ', $e::class, ': ', $e->getMessage(), "\n"; + } +} +fclose($plain); +?> +--EXPECT-- +ValueError: openssl_get_channel_binding(): argument #2 ($channel_binding_type) "not-a-type" is not a known channel binding type, expected "tls-unique", "tls-server-end-point" or "tls-exporter" +ValueError: openssl_get_channel_binding(): argument #2 ($channel_binding_type) "TLS-UNIQUE" is not a known channel binding type, expected "tls-unique", "tls-server-end-point" or "tls-exporter" +TypeError: openssl_get_channel_binding(): Argument #1 ($stream) must be of type resource, int given +tls-unique: Openssl\OpensslException: Stream does not have transport encryption enabled +tls-server-end-point: Openssl\OpensslException: Stream does not have transport encryption enabled +tls-exporter: Openssl\OpensslException: Stream does not have transport encryption enabled diff --git a/ext/openssl/tests/openssl_get_channel_binding_tls12.phpt b/ext/openssl/tests/openssl_get_channel_binding_tls12.phpt new file mode 100644 index 000000000000..bb09b424aae8 --- /dev/null +++ b/ext/openssl/tests/openssl_get_channel_binding_tls12.phpt @@ -0,0 +1,107 @@ +--TEST-- +openssl_get_channel_binding(): TLS 1.2 full handshake, client and server agree +--EXTENSIONS-- +openssl +--SKIPIF-- + +--FILE-- + [ + 'local_cert' => '%s', + 'verify_peer' => false, + 'verify_peer_name' => false, + 'capture_peer_cert' => true, + 'security_level' => 0, + ]]); + $flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN; + $server = stream_socket_server("tlsv1.2://127.0.0.1:0", $errno, $errstr, $flags, $ctx); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server, 30); + if ($conn === false) { + echo "SERVER_EXCEPTION accept failed\n"; + exit(1); + } + foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { + $v = openssl_get_channel_binding($conn, $t); + fwrite($conn, $t . "=" . (is_string($v) ? bin2hex($v) : "") . "\n"); + } + phpt_wait(); + fclose($conn); +CODE; +$serverCode = sprintf($serverCode, $certFile); + +/* Client: complete the handshake, compute its own values, read the server's + * values and report whether they agree. Also check tls-server-end-point against + * the SHA-256 fingerprint of the captured peer certificate. */ +$clientCode = <<<'CODE' + $ctx = stream_context_create(['ssl' => [ + 'verify_peer' => false, + 'verify_peer_name' => false, + 'capture_peer_cert' => true, + 'security_level' => 0, + ]]); + $client = stream_socket_client("tlsv1.2://{{ ADDR }}", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $ctx); + if ($client === false) { + echo "client connect failed\n"; + exit(1); + } + + $my = []; + foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { + $my[$t] = openssl_get_channel_binding($client, $t); + } + + $peer = []; + for ($i = 0; $i < 3; $i++) { + $line = fgets($client); + if ($line === false) break; + $line = rtrim($line); + $eq = strpos($line, "="); + $t = substr($line, 0, $eq); + $enc = substr($line, $eq + 1); + $peer[$t] = ($enc === "") ? null : hex2bin($enc); + } + + foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { + $c = $my[$t]; + $s = $peer[$t]; + $lc = is_string($c) ? strlen($c) : "null"; + $ls = is_string($s) ? strlen($s) : "null"; + printf("%s equal=%s len_client=%s len_server=%s\n", + $t, var_export($c === $s, true), $lc, $ls); + } + + $opts = stream_context_get_options($client); + $cert = $opts['ssl']['peer_certificate'] ?? null; + $fpr = $cert ? openssl_x509_fingerprint($cert, "sha256", true) : null; + echo "tse_matches_sha256_fingerprint=" + . var_export($fpr !== null && $my["tls-server-end-point"] === $fpr, true) . "\n"; + + phpt_notify('server'); + fclose($client); +CODE; + +include 'CertificateGenerator.inc'; +$generator = new CertificateGenerator(); +$generator->saveNewCertAsFileWithKey('cb-tls12', $certFile); + +include 'ServerClientTestCase.inc'; +ServerClientTestCase::getInstance()->run($clientCode, ['server' => $serverCode]); +?> +--CLEAN-- + +--EXPECTF-- +tls-unique equal=true len_client=%d len_server=%d +tls-server-end-point equal=true len_client=32 len_server=32 +tls-exporter equal=true len_client=32 len_server=32 +tse_matches_sha256_fingerprint=true diff --git a/ext/openssl/tests/openssl_get_channel_binding_tls13.phpt b/ext/openssl/tests/openssl_get_channel_binding_tls13.phpt new file mode 100644 index 000000000000..e147dc27e0a7 --- /dev/null +++ b/ext/openssl/tests/openssl_get_channel_binding_tls13.phpt @@ -0,0 +1,105 @@ +--TEST-- +openssl_get_channel_binding(): TLS 1.3, tls-unique is not applicable +--EXTENSIONS-- +openssl +--SKIPIF-- + +--FILE-- + [ + 'local_cert' => '%s', + 'verify_peer' => false, + 'verify_peer_name' => false, + 'capture_peer_cert' => true, + 'security_level' => 0, + ]]); + $flags = STREAM_SERVER_BIND|STREAM_SERVER_LISTEN; + $server = stream_socket_server("tlsv1.3://127.0.0.1:0", $errno, $errstr, $flags, $ctx); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server, 30); + if ($conn === false) { + echo "SERVER_EXCEPTION accept failed\n"; + exit(1); + } + foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { + $v = openssl_get_channel_binding($conn, $t); + fwrite($conn, $t . "=" . (is_string($v) ? bin2hex($v) : "") . "\n"); + } + phpt_wait(); + fclose($conn); +CODE; +$serverCode = sprintf($serverCode, $certFile); + +$clientCode = <<<'CODE' + $ctx = stream_context_create(['ssl' => [ + 'verify_peer' => false, + 'verify_peer_name' => false, + 'capture_peer_cert' => true, + 'security_level' => 0, + ]]); + $client = stream_socket_client("tlsv1.3://{{ ADDR }}", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $ctx); + if ($client === false) { + echo "client connect failed\n"; + exit(1); + } + + $my = []; + foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { + $my[$t] = openssl_get_channel_binding($client, $t); + } + + $peer = []; + for ($i = 0; $i < 3; $i++) { + $line = fgets($client); + if ($line === false) break; + $line = rtrim($line); + $eq = strpos($line, "="); + $t = substr($line, 0, $eq); + $enc = substr($line, $eq + 1); + $peer[$t] = ($enc === "") ? null : hex2bin($enc); + } + + foreach (["tls-unique", "tls-server-end-point", "tls-exporter"] as $t) { + $c = $my[$t]; + $s = $peer[$t]; + $lc = is_string($c) ? strlen($c) : "null"; + $ls = is_string($s) ? strlen($s) : "null"; + printf("%s equal=%s len_client=%s len_server=%s\n", + $t, var_export($c === $s, true), $lc, $ls); + } + + echo "tls_unique_is_null=" . var_export($my["tls-unique"] === null, true) . "\n"; + + $opts = stream_context_get_options($client); + $cert = $opts['ssl']['peer_certificate'] ?? null; + $fpr = $cert ? openssl_x509_fingerprint($cert, "sha256", true) : null; + echo "tse_matches_sha256_fingerprint=" + . var_export($fpr !== null && $my["tls-server-end-point"] === $fpr, true) . "\n"; + + phpt_notify('server'); + fclose($client); +CODE; + +include 'CertificateGenerator.inc'; +$generator = new CertificateGenerator(); +$generator->saveNewCertAsFileWithKey('cb-tls13', $certFile); + +include 'ServerClientTestCase.inc'; +ServerClientTestCase::getInstance()->run($clientCode, ['server' => $serverCode]); +?> +--CLEAN-- + +--EXPECT-- +tls-unique equal=true len_client=null len_server=null +tls-server-end-point equal=true len_client=32 len_server=32 +tls-exporter equal=true len_client=32 len_server=32 +tls_unique_is_null=true +tse_matches_sha256_fingerprint=true diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 854227318da0..55f4eceebad1 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -23,6 +23,7 @@ #include "ext/standard/file.h" #include "ext/uri/php_uri.h" #include "streams/php_streams_int.h" +#include "main/php_streams.h" #include "zend_smart_str.h" #include "zend_exceptions.h" #include "php_openssl.h" @@ -35,6 +36,7 @@ #include #include #include +#include #ifdef PHP_WIN32 #include "win32/winutil.h" @@ -3946,6 +3948,142 @@ static const php_stream_ops php_openssl_socket_ops = { php_openssl_sockop_set_option, }; +/* + * Get the NID of the message-digest algorithm used to sign a certificate, + * or NID_undef if it cannot be determined. + */ +static int php_openssl_get_cert_signature_md_nid(const X509 *cert) /* {{{ */ +{ + int md_nid, pkey_nid, secbits; + uint32_t flags = 0; + + if (!X509_get_signature_info((X509 *)cert, &md_nid, &pkey_nid, &secbits, &flags)) { + return NID_undef; + } + + return md_nid; +} +/* }}} */ + +/* + * Extract TLS channel binding data from an established openssl stream. + * + * On success *out is set to a newly-allocated zend_string holding the data + * and PHP_OSSL_CB_OK is returned. For a type that is not applicable to the + * connection (currently: tls-unique over TLS 1.3) *out is set to NULL and + * PHP_OSSL_CB_NOT_APPLICABLE is returned. If the stream is not an active TLS + * stream PHP_OSSL_CB_NOT_TLS is returned, and if an OpenSSL call fails + * PHP_OSSL_CB_ERROR is returned; in both of these cases *out is set to NULL. + */ +int php_openssl_netstream_get_channel_binding( /* {{{ */ + php_stream *stream, int type, zend_string **out) +{ + unsigned char buf[EVP_MAX_MD_SIZE]; + size_t len = 0; + php_openssl_netstream_data_t *sslsock; + SSL *ssl; + + *out = NULL; + + if (stream == NULL || stream->ops != &php_openssl_socket_ops) { + return PHP_OSSL_CB_NOT_TLS; + } + sslsock = (php_openssl_netstream_data_t *)stream->abstract; + if (sslsock == NULL || !sslsock->ssl_active || sslsock->ssl_handle == NULL) { + return PHP_OSSL_CB_NOT_TLS; + } + ssl = sslsock->ssl_handle; + + switch (type) { + case PHP_OSSL_CB_TLS_UNIQUE: + /* RFC 5929 section 3: tls-unique is not defined for TLS 1.3. */ + if (SSL_version(ssl) >= TLS1_3_VERSION) { + return PHP_OSSL_CB_NOT_APPLICABLE; + } + + /* + * In TLS 1.2 and earlier both endpoints always transmit a Finished + * message. Pick it so that the client and the server derive the same + * value: in a full handshake that is the client's Finished (the client + * reads it from its own send buffer, the server from its receive + * buffer), and in a resumed handshake the server's Finished. This is + * the same selection CPython's _ssl.get_channel_binding() makes. + */ + if (sslsock->is_client ^ SSL_session_reused(ssl)) { + len = SSL_get_finished(ssl, buf, sizeof(buf)); + } else { + len = SSL_get_peer_finished(ssl, buf, sizeof(buf)); + } + if (len == 0) { + return PHP_OSSL_CB_ERROR; + } + *out = zend_string_init((char *)buf, len, 0); + return *out != NULL ? PHP_OSSL_CB_OK : PHP_OSSL_CB_ERROR; + + case PHP_OSSL_CB_TLS_SERVER_ENDPOINT: + { + X509 *cert = sslsock->is_client ? + SSL_get_peer_certificate(ssl) : SSL_get_certificate(ssl); + int md_nid, rc; + const EVP_MD *md; + unsigned int digest_len = 0; + + if (cert == NULL) { + return PHP_OSSL_CB_ERROR; + } + + md_nid = php_openssl_get_cert_signature_md_nid(cert); + /* + * RFC 5929 section 4.1: if the digest used to sign the + * certificate is MD5 or SHA-1, or cannot be determined, use + * SHA-256 instead. + */ + if (md_nid == NID_md5 || md_nid == NID_sha1 || md_nid == NID_undef) { + md_nid = NID_sha256; + } + md = EVP_get_digestbynid(md_nid); + if (md == NULL) { + md = EVP_sha256(); + } + rc = X509_digest(cert, md, buf, &digest_len); + /* + * SSL_get_peer_certificate() returns a certificate with an + * incremented reference count that we must free; + * SSL_get_certificate() returns the certificate owned by the + * SSL_CTX, which must not be freed. + */ + if (sslsock->is_client) { + X509_free(cert); + } + if (rc != 1) { + return PHP_OSSL_CB_ERROR; + } + *out = zend_string_init((char *)buf, digest_len, 0); + return *out != NULL ? PHP_OSSL_CB_OK : PHP_OSSL_CB_ERROR; + } + + case PHP_OSSL_CB_TLS_EXPORTER: + /* + * RFC 9266 section 4 / RFC 5705: 32 octets, label + * "EXPORTER-Channel-Binding", empty context. For an empty context the + * use_context flag is immaterial (see tls13_export_keying_material() + * in OpenSSL, which zeroes the context length when it is clear). + */ + if (SSL_export_keying_material( + ssl, buf, 32, + "EXPORTER-Channel-Binding", sizeof("EXPORTER-Channel-Binding") - 1, + (const unsigned char *)"", 0, 1) != 1) { + return PHP_OSSL_CB_ERROR; + } + *out = zend_string_init((char *)buf, 32, 0); + return *out != NULL ? PHP_OSSL_CB_OK : PHP_OSSL_CB_ERROR; + + default: + return PHP_OSSL_CB_ERROR; + } +} +/* }}} */ + static zend_long php_openssl_get_crypto_method( php_stream_context *ctx, zend_long crypto_method) /* {{{ */ {