[pecl-networking-ssh2] master: Add SHA-256 and raw host key inspection

From: Date: Wed, 14 Oct 2026 19:55:46 +0000
Subject: [pecl-networking-ssh2] master: Add SHA-256 and raw host key inspection
Groups: php.pecl.cvs 
Request: Send a blank email to pecl-cvs+get-28697@lists.php.net to get a copy of this message
Author: Raj Siva-Rajah (binaryfire) Committer: Casper Langemeijer (langemeijer) Date: 2026-10-14T21:55:43+02:00 Commit: https://github.com/php/pecl-networking-ssh2/commit/7a01397f8844a43d8b849db036bac156343a2a72 Raw diff: https://github.com/php/pecl-networking-ssh2/commit/7a01397f8844a43d8b849db036bac156343a2a72.diff Add SHA-256 and raw host key inspection Raise the libssh2 build floor to 1.9.0 so every successful build exposes the complete host-key API, and fail configuration clearly when headers and the linked library do not match.\n\nAdd SHA-256 fingerprint selection, raw negotiated host-key retrieval, and constants for the supported host-key types. Preserve the existing MD5 default and SHA-1 behavior.\n\nCover the new surface with an algorithm-independent PHPT that verifies the raw key and SHA-256 fingerprint describe the same negotiated server key. Changed paths: A tests/ssh2_hostkey.phpt M config.m4 M package.xml M php_ssh2.h M ssh2.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/package.xml b/package.xml index 07c0427..97a9209 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 7c9cbcb..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 diff --git a/ssh2.c b/ssh2.c index b685b21..97c34e6 100644 --- a/ssh2.c +++ b/ssh2.c @@ -40,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; @@ -592,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; @@ -632,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 @@ -1460,6 +1504,15 @@ PHP_MINIT_FUNCTION(ssh2) 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); @@ -1556,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) @@ -1770,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/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)

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