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

📄 ssh.c

📁 老外写的加密库cryptlib(版本3.1)
💻 C
📖 第 1 页 / 共 2 页
字号:
/****************************************************************************
*																			*
*						cryptlib SSH Session Management						*
*						Copyright Peter Gutmann 1998-2003					*
*																			*
****************************************************************************/

#include <stdlib.h>
#include <string.h>
#if defined( INC_ALL )
  #include "crypt.h"
  #include "session.h"
  #include "ssh.h"
#elif defined( INC_CHILD )
  #include "../crypt.h"
  #include "../session/session.h"
  #include "../session/ssh.h"
#else
  #include "crypt.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 ) );
	}

/* Initialise and destroy the security contexts */

static void destroySecurityContexts( SESSION_INFO *sessionInfoPtr )
	{
	/* Destroy any active contexts */
	if( sessionInfoPtr->iKeyexCryptContext != CRYPT_ERROR )
		{
		krnlSendNotifier( sessionInfoPtr->iKeyexCryptContext,
						  IMESSAGE_DECREFCOUNT );
		sessionInfoPtr->iKeyexCryptContext = CRYPT_ERROR;
		}
	if( sessionInfoPtr->iCryptInContext != CRYPT_ERROR )
		{
		krnlSendNotifier( sessionInfoPtr->iCryptInContext,
						  IMESSAGE_DECREFCOUNT );
		sessionInfoPtr->iCryptInContext = CRYPT_ERROR;
		}
	if( sessionInfoPtr->iCryptOutContext != CRYPT_ERROR )
		{
		krnlSendNotifier( sessionInfoPtr->iCryptOutContext,
						  IMESSAGE_DECREFCOUNT );
		sessionInfoPtr->iCryptOutContext = CRYPT_ERROR;
		}
	if( sessionInfoPtr->iAuthInContext != CRYPT_ERROR )
		{
		krnlSendNotifier( sessionInfoPtr->iAuthInContext,
						  IMESSAGE_DECREFCOUNT );
		sessionInfoPtr->iAuthInContext = CRYPT_ERROR;
		}
	if( sessionInfoPtr->iAuthOutContext != CRYPT_ERROR )
		{
		krnlSendNotifier( sessionInfoPtr->iAuthOutContext,
						  IMESSAGE_DECREFCOUNT );
		sessionInfoPtr->iAuthOutContext = CRYPT_ERROR;
		}
	}

int initSecurityContexts( SESSION_INFO *sessionInfoPtr )
	{
	MESSAGE_CREATEOBJECT_INFO createInfo;
	int status;

	setMessageCreateObjectInfo( &createInfo, sessionInfoPtr->cryptAlgo );
	status = krnlSendMessage( SYSTEM_OBJECT_HANDLE, IMESSAGE_DEV_CREATEOBJECT,
							  &createInfo, OBJECT_TYPE_CONTEXT );
	if( cryptStatusOK( status ) )
		{
		sessionInfoPtr->iCryptInContext = createInfo.cryptHandle;
		setMessageCreateObjectInfo( &createInfo, sessionInfoPtr->cryptAlgo );
		status = krnlSendMessage( SYSTEM_OBJECT_HANDLE,
								  IMESSAGE_DEV_CREATEOBJECT, &createInfo,
								  OBJECT_TYPE_CONTEXT );
		}
	if( cryptStatusOK( status ) )
		{
		sessionInfoPtr->iCryptOutContext = createInfo.cryptHandle;
		krnlSendMessage( sessionInfoPtr->iCryptInContext,
						 IMESSAGE_GETATTRIBUTE, &sessionInfoPtr->cryptBlocksize,
						 CRYPT_CTXINFO_BLOCKSIZE );
		}
	if( cryptStatusOK( status ) && sessionInfoPtr->version == 1 && \
		sessionInfoPtr->cryptAlgo == CRYPT_ALGO_IDEA )
		{
		const int cryptMode = CRYPT_MODE_CFB;

		/* SSHv1 uses stream ciphers in places, for which we have to set the
		   mode explicitly */
		status = krnlSendMessage( sessionInfoPtr->iCryptInContext,
								  IMESSAGE_SETATTRIBUTE,
								  ( void * ) &cryptMode,
								  CRYPT_CTXINFO_MODE );
		if( cryptStatusOK( status ) )
			status = krnlSendMessage( sessionInfoPtr->iCryptOutContext,
									  IMESSAGE_SETATTRIBUTE,
									  ( void * ) &cryptMode,
									  CRYPT_CTXINFO_MODE );
		}
	if( cryptStatusOK( status ) && sessionInfoPtr->version == 2 )
		{
		setMessageCreateObjectInfo( &createInfo, sessionInfoPtr->integrityAlgo );
		status = krnlSendMessage( SYSTEM_OBJECT_HANDLE,
								  IMESSAGE_DEV_CREATEOBJECT, &createInfo,
								  OBJECT_TYPE_CONTEXT );
		if( cryptStatusOK( status ) )
			{
			sessionInfoPtr->iAuthInContext = createInfo.cryptHandle;
			setMessageCreateObjectInfo( &createInfo,
										sessionInfoPtr->integrityAlgo );
			status = krnlSendMessage( SYSTEM_OBJECT_HANDLE,
									  IMESSAGE_DEV_CREATEOBJECT, &createInfo,
									  OBJECT_TYPE_CONTEXT );
			}
		if( cryptStatusOK( status ) )
			{
			sessionInfoPtr->iAuthOutContext = createInfo.cryptHandle;
			krnlSendMessage( sessionInfoPtr->iAuthInContext,
							 IMESSAGE_GETATTRIBUTE,
							 &sessionInfoPtr->authBlocksize,
							 CRYPT_CTXINFO_BLOCKSIZE );
			}
		}
	if( cryptStatusError( status ) )
		/* One or more of the contexts couldn't be created, destroy all the
		   contexts that have been created so far */
		destroySecurityContexts( sessionInfoPtr );
	return( status );
	}

/* 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 );
			}
		linesRead++;
		}
	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, SSH1_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:

⌨️ 快捷键说明

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