⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 ssh.c

📁 cryptlib是功能强大的安全工具集。允许开发人员快速在自己的软件中集成加密和认证服务。
💻 C
📖 第 1 页 / 共 2 页
字号:
/****************************************************************************
*																			*
*						cryptlib SSH Session Management						*
*						Copyright Peter Gutmann 1998-2004					*
*																			*
****************************************************************************/

#include <stdlib.h>
#include <string.h>
#if defined( INC_ALL )
  #include "crypt.h"
  #include "misc_rw.h"
  #include "session.h"
  #include "ssh.h"
#elif defined( INC_CHILD )
  #include "../crypt.h"
  #include "../misc/misc_rw.h"
  #include "session.h"
  #include "ssh.h"
#else
  #include "crypt.h"
  #include "misc/misc_rw.h"
  #include "session/session.h"
  #include "session/ssh.h"
#endif /* Compiler-specific includes */

#if defined( USE_SSH1 ) || defined( USE_SSH2 )

/****************************************************************************
*																			*
*								Utility Functions							*
*																			*
****************************************************************************/

/* Initialise and destroy the handshake state information */

static int initHandshakeInfo( SSH_HANDSHAKE_INFO *handshakeInfo )
	{
	/* Initialise the handshake state info values */
	memset( handshakeInfo, 0, sizeof( SSH_HANDSHAKE_INFO ) );
	handshakeInfo->iExchangeHashcontext = \
		handshakeInfo->iServerCryptContext = CRYPT_ERROR;

	return( CRYPT_OK );
	}

static void destroyHandshakeInfo( SSH_HANDSHAKE_INFO *handshakeInfo )
	{
	/* Destroy any active contexts.  We need to do this here (even though
	   it's also done in the general session code) to provide a clean exit in
	   case the session activation fails, so that a second activation attempt
	   doesn't overwrite still-active contexts */
	if( handshakeInfo->iExchangeHashcontext != CRYPT_ERROR )
		krnlSendNotifier( handshakeInfo->iExchangeHashcontext,
						  IMESSAGE_DECREFCOUNT );
	if( handshakeInfo->iServerCryptContext != CRYPT_ERROR )
		krnlSendNotifier( handshakeInfo->iServerCryptContext,
						  IMESSAGE_DECREFCOUNT );

	zeroise( handshakeInfo, sizeof( SSH_HANDSHAKE_INFO ) );
	}

/* Read the SSH version information string */

static int readVersionLine( STREAM *stream, BYTE *buffer )
	{
	int length, status;

	/* Try and read the initial ID string data */
	status = sread( stream, buffer, SSH_ID_SIZE );
	if( cryptStatusError( status ) )
		return( status );
	if( status < SSH_ID_SIZE )
		/* This can happen if the caller sets a very short read timeout */
		return( CRYPT_ERROR_UNDERFLOW );

	/* Read the remainder of the text line, one character at a time.  If
	   this was an HTTP stream we could use speculative read-ahead buffering,
	   but there's no easy way to communicate this requirement to the stream-
	   handling code */
	for( length = SSH_ID_SIZE; length < SSH_ID_MAX_SIZE; length++ )
		{
		status = sread( stream, buffer + length, 1 );
		if( cryptStatusError( status ) )
			return( status );
		if( status <= 0 )
			return( CRYPT_ERROR_UNDERFLOW );
		if( !buffer[ length ] )
			/* The spec doesn't really say what is and isn't valid in the ID
			   strings, although it does say that nuls shouldn't be used.
			   In any case we can't allow these because they'd cause
			   problems for the string-handling functions */
			return( CRYPT_ERROR_BADDATA );
		if( buffer[ length ] == '\n' )
			break;
		}
	if( ( length < SSH_ID_SIZE + 3 ) || ( length >= SSH_ID_MAX_SIZE ) )
		return( CRYPT_ERROR_BADDATA );

	/* Null-terminate the string so that we can hash it to create the SSHv2
	   exchange hash */
	while( length > 0 && \
		   ( buffer[ length - 1 ] == '\r' || buffer[ length - 1 ] == '\n' ) )
		length--;
	buffer[ length ] = '\0';

	return( CRYPT_OK );
	}

static int readVersionString( SESSION_INFO *sessionInfoPtr )
	{
	const char *versionStringPtr = sessionInfoPtr->receiveBuffer + SSH_ID_SIZE;
	int linesRead = 0, status;

	/* Read the server version info, with the format for the ID string being
	   "SSH-protocolversion-softwareversion comments", which (in the original
	   ssh.com interpretation) was "SSH-x.y-x.y vendorname" (e.g.
	   "SSH-2.0-3.0.0 SSH Secure Shell") but for almost everyone else is
	   "SSH-x.y-vendorname*version" (e.g "SSH-2.0-OpenSSH_3.0").

	   This version info handling is rather ugly since it's a variable-length
	   string terminated with a newline, so we have to process it a character
	   at a time after the initial fixed data.

	   Unfortunately the SSH RFC further complicates this by allowing
	   implementations to send non-version-related text lines before the
	   version line.  The theory is that this will allow applications like
	   TCP wrappers to display a (human-readable) error message before
	   disconnecting, however some installations use it to display general
	   banners before the ID string.  Since the RFC doesn't provide any means
	   of distinguishing this banner information from arbitrary data, we
	   can't quickly reject attempts to connect to something that isn't an
	   SSH server.  In other words we have to sit here waiting for further
	   data in the hope that eventually an SSH ID turns up, until such time
	   as the connect timeout expires.  In order to provide a more useful
	   message than a somewhat confusing timeout error, we remember whether
	   we've already read any lines of text and if we have, report it as an
	   invalid ID error rather than a timeout error */
	do
		{
		status = readVersionLine( &sessionInfoPtr->stream,
								  sessionInfoPtr->receiveBuffer );
		if( cryptStatusError( status ) )
			{
			if( status == CRYPT_ERROR_BADDATA )
				retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
						"Invalid SSH version string length" );
			if( status == CRYPT_ERROR_UNDERFLOW )
				retExt( sessionInfoPtr, CRYPT_ERROR_UNDERFLOW,
						"SSH version string read timed out before all data "
						"could be read" );
			if( status == CRYPT_ERROR_TIMEOUT && linesRead > 0 )
				/* We timed out waiting for an ID to appear, this is an
				   invalid ID error rather than a true timeout */
				retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
						"Invalid SSH version string 0x%02X 0x%02X 0x%02X "
						"0x%02X",
						sessionInfoPtr->receiveBuffer[ 0 ],
						sessionInfoPtr->receiveBuffer[ 1 ],
						sessionInfoPtr->receiveBuffer[ 2 ],
						sessionInfoPtr->receiveBuffer[ 3 ] );
			sNetGetErrorInfo( &sessionInfoPtr->stream,
							  sessionInfoPtr->errorMessage,
							  &sessionInfoPtr->errorCode );
			return( status );
			}
		if( linesRead++ >= 100 )
			/* The peer shouldn't be throwing infinite amounts of junk at us,
			   if we don't get an SSH ID after reading 100 lines of input
			   there's a problem */
			retExt( sessionInfoPtr, CRYPT_ERROR_OVERFLOW,
					"Peer sent excessive amounts of text without sending "
					"any SSH version info" );
		}
	while( memcmp( sessionInfoPtr->receiveBuffer, SSH_ID, SSH_ID_SIZE ) );

	/* Determine which version we're talking to */
	if( *versionStringPtr == '1' )
		{
#ifdef USE_SSH2
		if( !memcmp( versionStringPtr, "1.99", 4 ) )
			/* SSHv2 server in backwards-compatibility mode */
			sessionInfoPtr->version = 2;
		else
#endif /* USE_SSH2 */
			{
#ifdef USE_SSH1
			/* If the caller has specifically asked for SSHv2 but all that
			   the server offers is SSHv1, we can't continue */
			if( sessionInfoPtr->version == 2 )
				retExt( sessionInfoPtr, CRYPT_ERROR_NOSECURE,
						"Server can only do SSHv1 when SSHv2 was requested" );
			sessionInfoPtr->version = 1;
#else
			retExt( sessionInfoPtr, CRYPT_ERROR_NOSECURE,
					"Server can only do SSHv1" );
#endif /* USE_SSH1 */
			}
		}
	else
#ifdef USE_SSH2
		if( *versionStringPtr == '2' )
			sessionInfoPtr->version = 2;
		else
#endif /* USE_SSH2 */
			retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
					"Invalid SSH version %c",
					sessionInfoPtr->receiveBuffer[ 0 ] );

	/* Find the end of the protocol version substring.  If there's no
	   software version info present this isn't really correct, but no major
	   reason for bailing out, so we just exit normally */
	while( *versionStringPtr && *versionStringPtr != '-' )
		versionStringPtr++;
	if( !versionStringPtr[ 0 ] || !versionStringPtr[ 1 ] )
		return( CRYPT_OK );
	versionStringPtr++;		/* Skip '-' */

	/* Check whether the peer is using cryptlib */
	if( !memcmp( versionStringPtr, SSH2_ID_STRING + SSH_ID_SIZE + SSH_VERSION_SIZE,
				 strlen( SSH2_ID_STRING + SSH_ID_SIZE + SSH_VERSION_SIZE ) ) )
		sessionInfoPtr->flags |= SESSION_ISCRYPTLIB;

	/* Check for various servers that require special-case handling.  The
	   versions that we check for are:

		CuteFTP:
			Drops the connection after seeing the server hello with no
			(usable) error indication.  This implementation is somewhat
			tricky to detect since it identifies itself using the dubious
			vendor ID string "1.0" (see the ssh.com note below), this
			problem hasn't been fixed more than a year after the vendor was
			notified of it, indicating that it's unlikely to ever be fixed.
			CuteFTP also uses the SSHv1 backwards-compatible version string
			"1.99" even though it can't actually do SSHv1, which means that
			it'll fail if it ever tries to connect to an SSHv1 peer.

		OpenSSH:
			Omits hashing the exchange hash length when creating the hash
			to be signed for client auth for version 2.0 (all subversions).

			Can't handle "password" as a PAM sub-method (meaning an
			authentication method hint), it responds with an authentication-
			failed response as soon as we send the PAM authentication
			request, for versions 3.8 - ? (currently 3.9).

		ssh.com:
			This implementation puts the version number first, so if we find
			something without a vendor name at the start we treat it as an
			ssh.com version.  However, Van Dyke's SSH server VShell also
			uses the ssh.com-style identification (fronti nulla fides), so
			when we check for the ssh.com implementation we make sure that
			it isn't really VShell.  In addition CuteFTP advertises its
			implementation as "1.0" (without any vendor name), which is
			going to cause problems in the future when they move to 2.x.

			Omits the DH-derived shared secret when hashing the keying
			material for versions identified as "2.0.0" (all
			sub-versions) and "2.0.10" .

			Uses an SSH2_FIXED_KEY_SIZE-sized key for HMAC instead of the de
			facto 160 bits for versions identified as "2.0.", "2.1 ", "2.1.",
			and "2.2." (i.e. all sub-versions of 2.0, 2.1, and 2.2), and
			specifically version "2.3.0".  This was fixed in 2.3.1.

			Omits the signature algorithm name for versions identified as
			"2.0" and "2.1" (all sub-versions).

			Requires a window adjust for every 32K sent even if the window is
			advertised as being (effectively) infinite in size for versions
			identified as "2.0" and "2.1" (all sub-versions).

			Omits hashing the exchange hash length when creating the hash
			to be signed for client auth for versions 2.1 and 2.2 (all
			subversions).

			Dumps text diagnostics (that is, raw text strings rather than
			SSH error packets) onto the connection if something unexpected
			occurs, for uncertain versions probably in the 2.x range.

		Van Dyke:
			Omits hashing the exchange hash length when creating the hash to
			be signed for client auth for version 3.0 (SecureCRT = SSH) and
			1.7 (SecureFX = SFTP).

	   Further quirks and peculiarities exist, but fortunately these are rare
	   enough (mostly for SSHv1) that we don't have to go out of our way to
	   handle them */
	if( !memcmp( versionStringPtr, "OpenSSH_", 8 ) )
		{
		const char *subVersionStringPtr = versionStringPtr + 8;

		if( !memcmp( subVersionStringPtr, "2.0", 3 ) )
			sessionInfoPtr->protocolFlags |= SSH_PFLAG_NOHASHLENGTH;
		if( !memcmp( subVersionStringPtr, "3.8", 3 ) || \
			!memcmp( subVersionStringPtr, "3.9", 3 ) || \
			!memcmp( subVersionStringPtr, "3.10", 4 ) )
			sessionInfoPtr->protocolFlags |= SSH_PFLAG_PAMPW;
		}
	if( *versionStringPtr == '2' && \
		strstr( versionStringPtr, "VShell" ) == NULL )
		{
		/* ssh.com 2.x versions have quite a number of bugs so we check for
		   them as a group */
		if( !memcmp( versionStringPtr, "2.0.0", 5 ) || \
			!memcmp( versionStringPtr, "2.0.10", 6 ) )
			sessionInfoPtr->protocolFlags |= SSH_PFLAG_NOHASHSECRET;
		if( !memcmp( versionStringPtr, "2.0", 3 ) || \
			!memcmp( versionStringPtr, "2.1", 3 ) )
			sessionInfoPtr->protocolFlags |= SSH_PFLAG_SIGFORMAT;
		if( !memcmp( versionStringPtr, "2.0", 3 ) || \
			!memcmp( versionStringPtr, "2.1", 3 ) )
			sessionInfoPtr->protocolFlags |= SSH_PFLAG_WINDOWBUG;
		if( !memcmp( versionStringPtr, "2.1", 3 ) || \
			!memcmp( versionStringPtr, "2.2", 3 ) )
			sessionInfoPtr->protocolFlags |= SSH_PFLAG_NOHASHLENGTH;
		if( !memcmp( versionStringPtr, "2.0", 3 ) || \
			!memcmp( versionStringPtr, "2.1", 3 ) || \
			!memcmp( versionStringPtr, "2.2", 3 ) || \
			!memcmp( versionStringPtr, "2.3.0", 5 ) )
			sessionInfoPtr->protocolFlags |= SSH_PFLAG_HMACKEYSIZE;
		if( !memcmp( versionStringPtr, "2.", 2 ) )
			/* Not sure of the exact versions where this occurs */

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -