[pecl-networking-ssh2] master: Merge branch 'master' into fix/ssh2-shell-environment-handling

From: Date: Wed, 14 Oct 2026 22:14:25 +0000
Subject: [pecl-networking-ssh2] master: Merge branch 'master' into fix/ssh2-shell-environment-handling
Groups: php.pecl.cvs 
Request: Send a blank email to pecl-cvs+get-28701@lists.php.net to get a copy of this message
Author: Casper Langemeijer (langemeijer) Committer: GitHub (web-flow) Pusher: langemeijer Date: 2026-10-15T00:00:13+02:00 Commit: https://github.com/php/pecl-networking-ssh2/commit/7f6c7211ad42cad0a1e7fd5e3cb8af5e7d5baa22 Raw diff: https://github.com/php/pecl-networking-ssh2/commit/7f6c7211ad42cad0a1e7fd5e3cb8af5e7d5baa22.diff Merge branch 'master' into fix/ssh2-shell-environment-handling Changed paths: A tests/ssh2_close_timeout.phpt A tests/ssh2_hostkey.phpt A tests/ssh2_sensitive_auth_parameters.phpt M config.m4 M config.w32 M package.xml M php_ssh2.h M ssh2.c M ssh2_fopen_wrappers.c Diff: diff --git a/config.m4 b/config.m4 index 18b8caa..87b43d8 100644 --- a/config.m4 +++ b/config.m4 @@ -25,12 +25,27 @@ if test "$PHP_SSH2" != "no"; then PHP_ADD_INCLUDE($SSH2_DIR/include) + saved_CPPFLAGS=$CPPFLAGS + CPPFLAGS="-I$SSH2_DIR/include $CPPFLAGS" + AC_MSG_CHECKING([for libssh2 version >= 1.9.0]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #include <libssh2.h> + #if LIBSSH2_VERSION_NUM < 0x010900 + #error libssh2 1.9.0 or newer is required + #endif + ]], [[]])], [ + AC_MSG_RESULT([yes]) + ], [ + AC_MSG_ERROR([libssh2 version >= 1.9.0 not found]) + ]) + CPPFLAGS=$saved_CPPFLAGS + PHP_CHECK_LIBRARY(ssh2,libssh2_session_hostkey, [ PHP_ADD_LIBRARY_WITH_PATH(ssh2, $SSH2_DIR/lib, SSH2_SHARED_LIBADD) AC_DEFINE(HAVE_SSH2LIB,1,[Have libssh2]) ],[ - AC_MSG_ERROR([libssh2 version >= 1.2 not found]) + AC_MSG_ERROR([libssh2 library not found or too old; version 1.9.0 or newer is required]) ],[ -L$SSH2_DIR/lib -lm ]) @@ -39,7 +54,7 @@ if test "$PHP_SSH2" != "no"; then [ AC_DEFINE(PHP_SSH2_AGENT_AUTH, 1, [Have libssh2 with ssh-agent support]) ],[ - AC_MSG_WARN([libssh2 <= 1.2.3, ssh-agent subsystem support not enabled]) + AC_MSG_ERROR([libssh2_agent_init not found; ensure the linked libssh2 matches the 1.9.0 or newer headers]) ],[ -L$SSH2_DIR/lib -lm ]) @@ -48,7 +63,7 @@ if test "$PHP_SSH2" != "no"; then [ AC_DEFINE(PHP_SSH2_SESSION_TIMEOUT, 1, [Have libssh2 with session timeout support]) ],[ - AC_MSG_WARN([libssh2 < 1.2.9, session timeout support not enabled]) + AC_MSG_ERROR([libssh2_session_set_timeout not found; ensure the linked libssh2 matches the 1.9.0 or newer headers]) ],[ -L$SSH2_DIR/lib -lm ]) @@ -57,7 +72,7 @@ if test "$PHP_SSH2" != "no"; then [ AC_DEFINE(PHP_SSH2_KEEPALIVE, 1, [Have libssh2 with keepalive support]) ],[ - AC_MSG_WARN([libssh2 keepalive support not available]) + AC_MSG_ERROR([libssh2_keepalive_config not found; ensure the linked libssh2 matches the 1.9.0 or newer headers]) ],[ -L$SSH2_DIR/lib -lm ]) @@ -66,7 +81,7 @@ if test "$PHP_SSH2" != "no"; then [ AC_DEFINE(PHP_SSH2_CHANNEL_SIGNAL, 1, [Have libssh2 with channel signal support]) ],[ - AC_MSG_WARN([libssh2 < 1.9.0, channel signal support not available]) + AC_MSG_ERROR([libssh2_channel_signal_ex not found; ensure the linked libssh2 matches the 1.9.0 or newer headers]) ],[ -L$SSH2_DIR/lib -lm ]) diff --git a/config.w32 b/config.w32 index 760ea12..b1164f4 100644 --- a/config.w32 +++ b/config.w32 @@ -39,7 +39,9 @@ if (PHP_SSH2 != "no") { AC_DEFINE('PHP_SSH2_AGENT_AUTH', 1); AC_DEFINE('PHP_SSH2_SESSION_TIMEOUT', 1); AC_DEFINE('PHP_SSH2_KEEPALIVE', 1); - AC_DEFINE('PHP_SSH2_CHANNEL_SIGNAL', 1); + if (CHECK_FUNC_IN_HEADER("libssh2.h", "libssh2_channel_signal_ex", "ssh2")) { + AC_DEFINE('PHP_SSH2_CHANNEL_SIGNAL', 1); + } ADD_EXTENSION_DEP('ssh2', 'zlib') ADD_EXTENSION_DEP('ssh2', 'openssl') } diff --git a/package.xml b/package.xml index 2faf69f..f37ca54 100644 --- a/package.xml +++ b/package.xml @@ -75,6 +75,7 @@ <file role="test" name="ssh2_auth_pubkey_file.phpt"/> <file role="test" name="ssh2_connect.phpt"/> <file role="test" name="ssh2_exec.phpt"/> + <file role="test" name="ssh2_hostkey.phpt"/> <file role="test" name="ssh2_keepalive.phpt"/> <file role="test" name="ssh2_poll.phpt"/> <file role="test" name="ssh2_send_eof.phpt"/> diff --git a/php_ssh2.h b/php_ssh2.h index c27e002..34e2185 100644 --- a/php_ssh2.h +++ b/php_ssh2.h @@ -24,6 +24,10 @@ #include "ext/standard/url.h" #include "main/php_network.h" +#if LIBSSH2_VERSION_NUM < 0x010900 +#error "The ssh2 extension requires libssh2 1.9.0 or newer" +#endif + #define PHP_SSH2_VERSION "1.5.0" #define PHP_SSH2_DEFAULT_PORT 22 @@ -32,6 +36,7 @@ #define PHP_SSH2_FINGERPRINT_SHA1 0x0001 #define PHP_SSH2_FINGERPRINT_HEX 0x0000 #define PHP_SSH2_FINGERPRINT_RAW 0x0002 +#define PHP_SSH2_FINGERPRINT_SHA256 0x0004 #define PHP_SSH2_TERM_UNIT_CHARS 0x0000 #define PHP_SSH2_TERM_UNIT_PIXELS 0x0001 @@ -66,6 +71,10 @@ typedef struct _php_ssh2_session_data { zval *disconnect_cb; php_socket_t socket; + + /* Session-local state prevents concurrent authentication attempts from sharing a password. */ + const char *kbd_password; + size_t kbd_password_len; } php_ssh2_session_data; typedef struct _php_ssh2_sftp_data { diff --git a/ssh2.c b/ssh2.c index 0bb7ce6..97c34e6 100644 --- a/ssh2.c +++ b/ssh2.c @@ -21,6 +21,7 @@ #endif #include "php.h" +#include "Zend/zend_attributes.h" #include "ext/standard/info.h" #include "ext/standard/file.h" #include "php_ssh2.h" @@ -39,6 +40,10 @@ #define MD5_DIGEST_LENGTH 16 #endif +#ifndef SHA256_DIGEST_LENGTH +#define SHA256_DIGEST_LENGTH 32 +#endif + /* True global resources - no need for thread safety here */ int le_ssh2_session; int le_ssh2_listener; @@ -158,7 +163,7 @@ LIBSSH2_MACERROR_FUNC(php_ssh2_macerror_cb) if (FAILURE == call_user_function(NULL, NULL, data->macerror_cb, &zretval, 1, args)) { php_error_docref(NULL, E_WARNING, "Failure calling macerror callback"); } else { - retval = zval_is_true(&zretval) ? 0 : -1; + retval = zend_is_true(&zretval) ? 0 : -1; } if (Z_TYPE_P(&zretval) != IS_UNDEF) { zval_ptr_dtor(&zretval); @@ -591,18 +596,28 @@ PHP_FUNCTION(ssh2_fingerprint) zval *zsession; const char *fingerprint; zend_long flags = 0; - int i, fingerprint_len; + size_t i, fingerprint_len; + int hash_type; if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|l", &zsession, &flags) == FAILURE) { return; } - fingerprint_len = (flags & PHP_SSH2_FINGERPRINT_SHA1) ? SHA_DIGEST_LENGTH : MD5_DIGEST_LENGTH; + if (flags & PHP_SSH2_FINGERPRINT_SHA256) { + fingerprint_len = SHA256_DIGEST_LENGTH; + hash_type = LIBSSH2_HOSTKEY_HASH_SHA256; + } else if (flags & PHP_SSH2_FINGERPRINT_SHA1) { + fingerprint_len = SHA_DIGEST_LENGTH; + hash_type = LIBSSH2_HOSTKEY_HASH_SHA1; + } else { + fingerprint_len = MD5_DIGEST_LENGTH; + hash_type = LIBSSH2_HOSTKEY_HASH_MD5; + } if ((session = (LIBSSH2_SESSION *)zend_fetch_resource(Z_RES_P(zsession), PHP_SSH2_SESSION_RES_NAME, le_ssh2_session)) == NULL) { RETURN_FALSE; } - fingerprint = (char*)libssh2_hostkey_hash(session, (flags & PHP_SSH2_FINGERPRINT_SHA1) ? LIBSSH2_HOSTKEY_HASH_SHA1 : LIBSSH2_HOSTKEY_HASH_MD5); + fingerprint = (char*)libssh2_hostkey_hash(session, hash_type); if (!fingerprint) { php_error_docref(NULL, E_WARNING, "Unable to retrieve fingerprint from specified session"); RETURN_FALSE; @@ -631,6 +646,36 @@ PHP_FUNCTION(ssh2_fingerprint) } /* }}} */ +/* {{{ proto array ssh2_hostkey(resource session) + * Returns the server's raw host key and type + */ +PHP_FUNCTION(ssh2_hostkey) +{ + LIBSSH2_SESSION *session; + zval *zsession; + const char *hostkey; + size_t hostkey_len; + int type; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &zsession) == FAILURE) { + return; + } + + if ((session = (LIBSSH2_SESSION *)zend_fetch_resource(Z_RES_P(zsession), PHP_SSH2_SESSION_RES_NAME, le_ssh2_session)) == NULL) { + RETURN_FALSE; + } + + hostkey = libssh2_session_hostkey(session, &hostkey_len, &type); + if (!hostkey) { + RETURN_FALSE; + } + + array_init(return_value); + add_assoc_stringl(return_value, "key", (char *)hostkey, hostkey_len); + add_assoc_long(return_value, "type", type); +} +/* }}} */ + /* {{{ proto array ssh2_auth_none(resource session, string username) * Attempt "none" authentication, returns a list of allowed methods on failed authentication, * false on utter failure, or true on success @@ -669,8 +714,6 @@ PHP_FUNCTION(ssh2_auth_none) } /* }}} */ -char *password_for_kbd_callback; - static void kbd_callback(const char *name, int name_len, const char *instruction, int instruction_len, int num_prompts, @@ -678,16 +721,31 @@ static void kbd_callback(const char *name, int name_len, LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, void **abstract) { + php_ssh2_session_data *data; + int i; + (void)name; (void)name_len; (void)instruction; (void)instruction_len; - if (num_prompts == 1) { - responses[0].text = strdup(password_for_kbd_callback); - responses[0].length = responses[0].text ? strlen(password_for_kbd_callback) : 0; - } (void)prompts; - (void)abstract; + + data = abstract ? (php_ssh2_session_data*)*abstract : NULL; + + for (i = 0; i < num_prompts; i++) { + responses[i].text = NULL; + responses[i].length = 0; + } + + if (num_prompts >= 1 && data && data->kbd_password) { + /* libssh2 releases response text through the session's system free() callback. */ + responses[0].text = malloc(data->kbd_password_len + 1); + if (responses[0].text) { + memcpy(responses[0].text, data->kbd_password, data->kbd_password_len); + responses[0].text[data->kbd_password_len] = '\0'; + responses[0].length = (unsigned int)data->kbd_password_len; + } + } } /* {{{ proto bool ssh2_auth_password(resource session, string username, string password) @@ -699,6 +757,8 @@ PHP_FUNCTION(ssh2_auth_password) zval *zsession; zend_string *username, *password; char *userauthlist; + php_ssh2_session_data *data; + int authenticated; if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS", &zsession, &username, &password) == FAILURE) { return; @@ -709,9 +769,17 @@ PHP_FUNCTION(ssh2_auth_password) userauthlist = libssh2_userauth_list(session, username->val, username->len); if (userauthlist != NULL) { - password_for_kbd_callback = password->val; if (strstr(userauthlist, "keyboard-interactive") != NULL) { - if (libssh2_userauth_keyboard_interactive(session, username->val, &kbd_callback) == 0) { + data = *(php_ssh2_session_data**)libssh2_session_abstract(session); + data->kbd_password = password->val; + data->kbd_password_len = password->len; + + authenticated = libssh2_userauth_keyboard_interactive(session, username->val, &kbd_callback) == 0; + + data->kbd_password = NULL; + data->kbd_password_len = 0; + + if (authenticated) { RETURN_TRUE; } } @@ -1413,10 +1481,38 @@ PHP_MINIT_FUNCTION(ssh2) le_ssh2_sftp = zend_register_list_destructors_ex(php_ssh2_sftp_dtor, NULL, PHP_SSH2_SFTP_RES_NAME, module_number); le_ssh2_pkey_subsys = zend_register_list_destructors_ex(php_ssh2_pkey_subsys_dtor, NULL, PHP_SSH2_PKEY_SUBSYS_RES_NAME, module_number); +#if PHP_VERSION_ID >= 80200 + /* This extension keeps hand-written arginfo, so sensitive attributes are registered here. */ + zend_add_parameter_attribute( + zend_hash_str_find_ptr(CG(function_table), "ssh2_auth_password", sizeof("ssh2_auth_password") - 1), + 2, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); + zend_add_parameter_attribute( + zend_hash_str_find_ptr(CG(function_table), "ssh2_auth_pubkey_file", sizeof("ssh2_auth_pubkey_file") - 1), + 4, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); + zend_add_parameter_attribute( + zend_hash_str_find_ptr(CG(function_table), "ssh2_auth_pubkey", sizeof("ssh2_auth_pubkey") - 1), + 3, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); + zend_add_parameter_attribute( + zend_hash_str_find_ptr(CG(function_table), "ssh2_auth_pubkey", sizeof("ssh2_auth_pubkey") - 1), + 4, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); + zend_add_parameter_attribute( + zend_hash_str_find_ptr(CG(function_table), "ssh2_auth_hostbased_file", sizeof("ssh2_auth_hostbased_file") - 1), + 5, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); +#endif + REGISTER_LONG_CONSTANT("SSH2_FINGERPRINT_MD5", PHP_SSH2_FINGERPRINT_MD5, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SSH2_FINGERPRINT_SHA1", PHP_SSH2_FINGERPRINT_SHA1, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SSH2_FINGERPRINT_HEX", PHP_SSH2_FINGERPRINT_HEX, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SSH2_FINGERPRINT_RAW", PHP_SSH2_FINGERPRINT_RAW, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SSH2_FINGERPRINT_SHA256", PHP_SSH2_FINGERPRINT_SHA256, CONST_CS | CONST_PERSISTENT); + + REGISTER_LONG_CONSTANT("SSH2_HOSTKEY_TYPE_UNKNOWN", LIBSSH2_HOSTKEY_TYPE_UNKNOWN, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SSH2_HOSTKEY_TYPE_RSA", LIBSSH2_HOSTKEY_TYPE_RSA, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SSH2_HOSTKEY_TYPE_DSS", LIBSSH2_HOSTKEY_TYPE_DSS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SSH2_HOSTKEY_TYPE_ECDSA_256", LIBSSH2_HOSTKEY_TYPE_ECDSA_256, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SSH2_HOSTKEY_TYPE_ECDSA_384", LIBSSH2_HOSTKEY_TYPE_ECDSA_384, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SSH2_HOSTKEY_TYPE_ECDSA_521", LIBSSH2_HOSTKEY_TYPE_ECDSA_521, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SSH2_HOSTKEY_TYPE_ED25519", LIBSSH2_HOSTKEY_TYPE_ED25519, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SSH2_TERM_UNIT_CHARS", PHP_SSH2_TERM_UNIT_CHARS, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("SSH2_TERM_UNIT_PIXELS", PHP_SSH2_TERM_UNIT_PIXELS, CONST_CS | CONST_PERSISTENT); @@ -1513,6 +1609,10 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_ssh2_fingerprint, 0, 0, 1) ZEND_ARG_INFO(0, flags) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO(arginfo_ssh2_hostkey, 1) + ZEND_ARG_INFO(0, session) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_INFO(arginfo_ssh2_auth_none, 2) ZEND_ARG_INFO(0, session) ZEND_ARG_INFO(0, username) @@ -1727,6 +1827,7 @@ zend_function_entry ssh2_functions[] = { #endif PHP_FE(ssh2_methods_negotiated, arginfo_ssh2_methods_negotiated) PHP_FE(ssh2_fingerprint, arginfo_ssh2_fingerprint) + PHP_FE(ssh2_hostkey, arginfo_ssh2_hostkey) PHP_FE(ssh2_auth_none, arginfo_ssh2_auth_none) PHP_FE(ssh2_auth_password, arginfo_ssh2_auth_password) diff --git a/ssh2_fopen_wrappers.c b/ssh2_fopen_wrappers.c index 76585ec..c11623d 100644 --- a/ssh2_fopen_wrappers.c +++ b/ssh2_fopen_wrappers.c @@ -141,8 +141,31 @@ static int php_ssh2_channel_stream_close(php_stream *stream, int close_handle) if (abstract->refcount) { efree(abstract->refcount); } +#ifdef PHP_SSH2_SESSION_TIMEOUT + /* Apply session timeout during channel close to prevent blocking + * indefinitely when the remote command hasn't finished or the + * connection has been lost. libssh2_channel_free() calls + * libssh2_channel_close() internally which blocks waiting for + * SSH_MSG_CHANNEL_CLOSE from the remote end. */ + if (abstract->timeout > 0) { + LIBSSH2_SESSION *session; + session = (LIBSSH2_SESSION *)zend_fetch_resource(abstract->session_rsrc, PHP_SSH2_SESSION_RES_NAME, le_ssh2_session); + if (session) { + libssh2_session_set_timeout(session, abstract->timeout); + } + } +#endif libssh2_channel_eof(abstract->channel); libssh2_channel_free(abstract->channel); +#ifdef PHP_SSH2_SESSION_TIMEOUT + if (abstract->timeout > 0) { + LIBSSH2_SESSION *session; + session = (LIBSSH2_SESSION *)zend_fetch_resource(abstract->session_rsrc, PHP_SSH2_SESSION_RES_NAME, le_ssh2_session); + if (session) { + libssh2_session_set_timeout(session, 0); + } + } +#endif zend_list_delete(abstract->session_rsrc); } efree(abstract); @@ -551,7 +574,7 @@ static php_stream *php_ssh2_shell_open(LIBSSH2_SESSION *session, zend_resource * if (libssh2_channel_setenv_ex(channel, key->val, key->len, Z_STRVAL(copyval), Z_STRLEN(copyval))) { php_error_docref(NULL, E_WARNING, "Failed setting environment variable %s on remote end", ZSTR_VAL(key)); } - zval_dtor(&copyval); + zval_ptr_dtor_nogc(&copyval); } else { php_error_docref(NULL, E_NOTICE, "Skipping numeric index in environment array"); } @@ -817,7 +840,7 @@ static php_stream *php_ssh2_exec_command(LIBSSH2_SESSION *session, zend_resource if (libssh2_channel_setenv_ex(channel, key->val, key->len, Z_STRVAL(copyval), Z_STRLEN(copyval))) { php_error_docref(NULL, E_WARNING, "Failed setting environment variable %s on remote end", ZSTR_VAL(key)); } - zval_dtor(&copyval); + zval_ptr_dtor_nogc(&copyval); } else { php_error_docref(NULL, E_NOTICE, "Skipping numeric index in environment array"); } diff --git a/tests/ssh2_close_timeout.phpt b/tests/ssh2_close_timeout.phpt new file mode 100644 index 0000000..e8079cb --- /dev/null +++ b/tests/ssh2_close_timeout.phpt @@ -0,0 +1,34 @@ +--TEST-- +ssh2_exec() channel close respects stream timeout +--SKIPIF-- +<?php +require('ssh2_skip.inc'); +if (!function_exists('ssh2_set_timeout')) { + die("skip ssh2_set_timeout not available"); +} +?> +--FILE-- +<?php require('ssh2_test.inc'); + +$ssh = ssh2_connect(TEST_SSH2_HOSTNAME, TEST_SSH2_PORT); +ssh2_auth_pubkey_file($ssh, TEST_SSH2_USER, TEST_SSH2_PUB_KEY, TEST_SSH2_PRIV_KEY); + +$start = microtime(true); + +/* Run a command that takes much longer than our timeout */ +$stream = ssh2_exec($ssh, 'sleep 60'); +stream_set_timeout($stream, 3); + +/* Without the fix, fclose() blocks for 60s waiting for + * SSH_MSG_CHANNEL_CLOSE. With the fix, it respects the + * stream timeout and returns in ~3s. */ +fclose($stream); + +$elapsed = microtime(true) - $start; + +/* Should complete well under 10s. Without the fix this would take 60s. */ +var_dump($elapsed < 10); +printf("Completed in %.1fs\n", $elapsed); +--EXPECTF-- +bool(true) +Completed in %fs diff --git a/tests/ssh2_hostkey.phpt b/tests/ssh2_hostkey.phpt new file mode 100644 index 0000000..51afefd --- /dev/null +++ b/tests/ssh2_hostkey.phpt @@ -0,0 +1,28 @@ +--TEST-- +Raw host-key metadata and SHA-256 fingerprints describe the same server key +--SKIPIF-- +<?php require('ssh2_skip.inc'); ?> +--FILE-- +<?php +require('ssh2_test.inc'); + +$ssh = ssh2_connect(TEST_SSH2_HOSTNAME, TEST_SSH2_PORT); +$hostkey = ssh2_hostkey($ssh); +$fingerprint = ssh2_fingerprint($ssh, SSH2_FINGERPRINT_SHA256 | SSH2_FINGERPRINT_RAW); + +var_dump(is_array($hostkey)); +var_dump(is_string($hostkey['key'])); +var_dump($hostkey['key'] !== ''); +var_dump(is_int($hostkey['type'])); +var_dump($hostkey['type'] !== SSH2_HOSTKEY_TYPE_UNKNOWN); +var_dump(strlen($fingerprint)); +var_dump($fingerprint === hash('sha256', $hostkey['key'], true)); +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +int(32) +bool(true) diff --git a/tests/ssh2_sensitive_auth_parameters.phpt b/tests/ssh2_sensitive_auth_parameters.phpt new file mode 100644 index 0000000..71461f2 --- /dev/null +++ b/tests/ssh2_sensitive_auth_parameters.phpt @@ -0,0 +1,76 @@ +--TEST-- +SSH2 authentication parameters publish sensitive metadata and redact traces +--SKIPIF-- +<?php +if (PHP_VERSION_ID < 80200) { + echo 'skip SensitiveParameter requires PHP 8.2 or newer'; +} +if (!extension_loaded('ssh2')) { + echo 'skip extension not loaded'; +} +?> +--INI-- +zend.exception_ignore_args=0 +--FILE-- +<?php +$sensitiveParameters = [ + ['ssh2_auth_password', 'password'], + ['ssh2_auth_pubkey_file', 'passphrase'], + ['ssh2_auth_pubkey', 'privkey'], + ['ssh2_auth_pubkey', 'passphrase'], + ['ssh2_auth_hostbased_file', 'passphrase'], +]; + +foreach ($sensitiveParameters as [$function, $parameter]) { + $reflectionParameter = new ReflectionParameter($function, $parameter); + + printf( + "%s:%s sensitive: %s\n", + $function, + $parameter, + count($reflectionParameter->getAttributes(SensitiveParameter::class)) === 1 ? 'true' : 'false', + ); +} + +$ordinaryParameters = [ + ['ssh2_auth_password', 'username'], + ['ssh2_auth_pubkey', 'pubkey'], + ['ssh2_auth_pubkey_file', 'privkeyfile'], + ['ssh2_auth_hostbased_file', 'privkeyfile'], +]; + +foreach ($ordinaryParameters as [$function, $parameter]) { + $reflectionParameter = new ReflectionParameter($function, $parameter); + + printf( + "%s:%s sensitive: %s\n", + $function, + $parameter, + $reflectionParameter->getAttributes(SensitiveParameter::class) === [] ? 'false' : 'true', + ); +} + +try { + ssh2_auth_password(null, 'trace-user', 'trace-secret'); +} catch (Throwable $exception) { + $arguments = $exception->getTrace()[0]['args']; + $trace = print_r($exception->getTrace(), true); + + printf("trace username: %s\n", $arguments[1]); + printf("trace password wrapped: %s\n", $arguments[2] instanceof SensitiveParameterValue ? 'true' : 'false'); + printf("trace contains secret: %s\n", str_contains($trace, 'trace-secret') ? 'true' : 'false'); +} +?> +--EXPECT-- +ssh2_auth_password:password sensitive: true +ssh2_auth_pubkey_file:passphrase sensitive: true +ssh2_auth_pubkey:privkey sensitive: true +ssh2_auth_pubkey:passphrase sensitive: true +ssh2_auth_hostbased_file:passphrase sensitive: true +ssh2_auth_password:username sensitive: false +ssh2_auth_pubkey:pubkey sensitive: false +ssh2_auth_pubkey_file:privkeyfile sensitive: false +ssh2_auth_hostbased_file:privkeyfile sensitive: false +trace username: trace-user +trace password wrapped: true +trace contains secret: false

« previous php.pecl.cvs (#28701) next »