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

📄 ssh2_cli.c

📁 cryptlib是功能强大的安全工具集。允许开发人员快速在自己的软件中集成加密和认证服务。
💻 C
📖 第 1 页 / 共 4 页
字号:
/****************************************************************************
*																			*
*						cryptlib SSHv2 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 */

#ifdef USE_SSH2

/* Tables mapping SSHv2 algorithm names to cryptlib algorithm IDs, in 
   preferred algorithm order.  There are two of these, one that favours
   password-based authentication and one that favours PKC-based
   authentication, depending on whether the user has specified a password
   or PKC as their authentication choice */

static const FAR_BSS ALGO_STRING_INFO algoStringUserauthentPWTbl[] = {
	{ "password", CRYPT_PSEUDOALGO_PASSWORD },
	{ "keyboard-interactive", CRYPT_PSEUDOALGO_PAM },
	{ "publickey", CRYPT_ALGO_RSA },
	{ NULL, CRYPT_ALGO_NONE }
	};
static const FAR_BSS ALGO_STRING_INFO algoStringUserauthentPKCTbl[] = {
	{ "publickey", CRYPT_ALGO_RSA },
	{ "password", CRYPT_PSEUDOALGO_PASSWORD },
	{ "keyboard-interactive", CRYPT_PSEUDOALGO_PAM },
	{ NULL, CRYPT_ALGO_NONE }
	};

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

/* Generate/check an SSHv2 key fingerprint.  This is simply an MD5 hash of
   the server's key/certificate data */

static int processKeyFingerprint( SESSION_INFO *sessionInfoPtr,
								  const void *keyData,
								  const int keyDataLength )
	{
	HASHFUNCTION hashFunction;
	const ATTRIBUTE_LIST *attributeListPtr = \
				findSessionAttribute( sessionInfoPtr->attributeList,
									  CRYPT_SESSINFO_SERVER_FINGERPRINT );
	BYTE fingerPrint[ CRYPT_MAX_HASHSIZE ];
	int hashSize;

	getHashParameters( CRYPT_ALGO_MD5, &hashFunction, &hashSize );
	hashFunction( NULL, fingerPrint, keyData, keyDataLength, HASH_ALL );
	if( attributeListPtr == NULL )
		/* Remember the value for the caller */
		return( addSessionAttribute( &sessionInfoPtr->attributeList,
									 CRYPT_SESSINFO_SERVER_FINGERPRINT, 
									 fingerPrint, hashSize ) );

	/* In the unlikely event that the user has passed us a SHA-1 fingerprint 
	   (which isn't allowed by the spec, but no doubt someone out there's 
	   using it based on the fact that the SSH architecture draft suggests 
	   an SHA-1 fingerprint while the SSH fingerprint draft requires an MD5 
	   fingerprint), calculate that instead */
	if( attributeListPtr->valueLength == 20 )
		{
		getHashParameters( CRYPT_ALGO_SHA, &hashFunction, &hashSize );
		hashFunction( NULL, fingerPrint, keyData, keyDataLength, HASH_ALL );
		}

	/* There's an existing fingerprint value, make sure that it matches what 
	   we just calculated */
	if( attributeListPtr->valueLength != hashSize || \
		memcmp( attributeListPtr->value, fingerPrint, hashSize ) )
		retExt( sessionInfoPtr, CRYPT_ERROR_WRONGKEY,
				"Server key fingerprint doesn't match requested "
				"fingerprint" );

	return( CRYPT_OK );
	}

/* Report specific details on an authentication failure to the caller */

static int processPamAuthentication( SESSION_INFO *sessionInfoPtr );	/* Fwd.dec for fn.*/

static int reportAuthFailure( SESSION_INFO *sessionInfoPtr, 
							  const int length, const BOOLEAN isPamAuth )
	{
	STREAM stream;
	CRYPT_ALGO_TYPE authentAlgo;
	const BOOLEAN hasPassword = \
			( findSessionAttribute( sessionInfoPtr->attributeList,
								    CRYPT_SESSINFO_PASSWORD ) != NULL ) ? \
			TRUE : FALSE;
	int status;

	/* The authentication failed, pick apart the response to see if we can 
	   return more meaningful error info:

		byte	type = SSH2_MSG_USERAUTH_FAILURE
		string	available_auth_types
		boolean	partial_success

	  We decode the response to favour password- or PKC-based 
	  authentication depending on whether the user specified a password 
	  or PKC as their authentication choice.

	  God knows how the partial_success flag is really meant to be applied 
	  (there are a whole pile of odd conditions surrounding changed 
	  passwords and similar issues), according to the spec it means that the 
	  authentication was successful, however the packet type indicates that 
	  the authentication failed and something else is needed.  This whole 
	  section of the protocol winds up in an extremely complex state machine 
	  with all sorts of special-case conditions, several of which require 
	  manual intervention by the user.  It's easiest to not even try and 
	  handle this stuff */
	sMemConnect( &stream, sessionInfoPtr->receiveBuffer, length );
	sgetc( &stream );		/* Skip packet type */
	status = readAlgoString( &stream, hasPassword ? \
								algoStringUserauthentPWTbl : \
								algoStringUserauthentPKCTbl, 
							  &authentAlgo, FALSE, sessionInfoPtr );
	sMemDisconnect( &stream );
	if( cryptStatusError( status ) )
		{
		/* If the problem is due to lack of a compatible algorithm, make the 
		   error message a bit more specific to tell the user that we got 
		   through most of the handshake but failed at the authentication 
		   stage */
		if( status == CRYPT_ERROR_NOTAVAIL )
			retExt( sessionInfoPtr, CRYPT_ERROR_NOTAVAIL,
					"Remote system supports neither password nor "
					"public-key authentication" );

		/* There was some other problem with the returned information, we 
		   still report it as a failed-authentication error but leave the
		   extended error info in place to let the caller see what the 
		   underlying cause was */
		return( CRYPT_ERROR_WRONGKEY );
		}

	/* SSH reports authentication failures in a somewhat bizarre way, 
	   instead of saying "authentication failed" it returns a list of 
	   allowed authentication methods, one of which may be the one that we 
	   just used.  To figure out whether we used the wrong auth method or 
	   the wrong auth value, we have to perform a complex decode and match 
	   of the info in the returned packet with what we sent */
	if( !hasPassword )
		{
		/* If we used a PKC and the server wants a password, report the 
		   error as a missing password */
		if( authentAlgo == CRYPT_PSEUDOALGO_PASSWORD || \
			authentAlgo == CRYPT_PSEUDOALGO_PAM )
			{
			setErrorInfo( sessionInfoPtr, CRYPT_SESSINFO_PASSWORD,
						  CRYPT_ERRTYPE_ATTR_ABSENT );
			retExt( sessionInfoPtr, CRYPT_ERROR_NOTINITED,
					"Server requested password authentication but only a "
					"public/private key was available" );
			}

		retExt( sessionInfoPtr, CRYPT_ERROR_WRONGKEY,
				"Server reported: Invalid public-key authentication" );
		}

	/* If the server requested keyboard-interactive (== misnamed PAM) 
	   authentication, try again using PAM authentication unless we've 
	   already been called as a result of failed PAM authentication */
	if( authentAlgo == CRYPT_PSEUDOALGO_PAM && !isPamAuth )
		return( processPamAuthentication( sessionInfoPtr ) );

	/* If we used a password and the server wants a PKC, report the error 
	   as a missing private key.  RSA in this case is a placeholder that 
	   means "any public-key algorithm", it could just as well have been
	   DSA */
	if( authentAlgo == CRYPT_ALGO_RSA )
		{
		setErrorInfo( sessionInfoPtr, CRYPT_SESSINFO_PRIVATEKEY,
					  CRYPT_ERRTYPE_ATTR_ABSENT );
		retExt( sessionInfoPtr, CRYPT_ERROR_NOTINITED,
				"Server requested public-key authentication but only a "
				"password was available" );
		}

	retExt( sessionInfoPtr, CRYPT_ERROR_WRONGKEY,
			"Server reported: Invalid password" );
	}

/* Handle an ephemeral DH key exchange */

static int processDHE( SESSION_INFO *sessionInfoPtr,
					   SSH_HANDSHAKE_INFO *handshakeInfo,
					   STREAM *stream, KEYAGREE_PARAMS *keyAgreeParams )
	{
	const int offset = LENGTH_SIZE + sizeofString32( "ssh-dh", 6 );
	BYTE *keyexInfoPtr;
	int keyexInfoLength, length, packetOffset, status;

	/*	...
		byte	type = SSH2_MSG_KEXDH_GEX_REQUEST_OLD
		uint32	n = 1024 bits
	
	   There's an alternative format that allows the client to specify a 
	   range of key sizes:
		   
		byte	type = SSH2_MSG_KEXDH_GEX_REQUEST_NEW
		uint32	min = 1024 bits
		uint32	n = SSH2_DEFAULT_KEYSIZE (as bits)
		uint32	max = CRYPT_MAX_PKCSIZE (as bits)

	   but a number of implementations don't support this yet, with some 
	   servers just dropping the connection without any error response if 
	   they encounter the newer packet type */
#if 1
	packetOffset = continuePacketStreamSSH( stream, 
											SSH2_MSG_KEXDH_GEX_REQUEST_OLD );
	streamBookmarkSet( stream, keyexInfoPtr, keyexInfoLength );
	writeUint32( stream, bytesToBits( SSH2_DEFAULT_KEYSIZE ) );
#else
	packetOffset = continuePacketStreamSSH( stream, 
											SSH2_MSG_KEXDH_GEX_REQUEST_NEW );
	streamBookmarkSet( stream, keyexInfoPtr, keyexInfoLength );
	writeUint32( stream, 1024 );
	writeUint32( stream, bytesToBits( SSH2_DEFAULT_KEYSIZE ) );
	writeUint32( stream, bytesToBits( CRYPT_MAX_PKCSIZE ) );
#endif /* 1 */
	streamBookmarkComplete( stream, keyexInfoLength );
	status = wrapPacketSSH2( sessionInfoPtr, stream, packetOffset );
	if( cryptStatusOK( status ) )
		status = sendPacketSSH2( sessionInfoPtr, stream, TRUE );
	sMemDisconnect( stream );
	if( cryptStatusError( status ) )
		return( status );

	/* Remember the encoded key size info for later when we generate the 
	   exchange hash */
	memcpy( handshakeInfo->encodedReqKeySizes, keyexInfoPtr, 
			keyexInfoLength );
	handshakeInfo->encodedReqKeySizesLength = keyexInfoLength;

	/* Process the ephemeral DH key:

		byte	type = SSH2_MSG_KEXDH_GEX_GROUP
		mpint	p
		mpint	g */
	length = readPacketSSH2( sessionInfoPtr, SSH2_MSG_KEXDH_GEX_GROUP,
					ID_SIZE + \
					sizeofString32( "", bitsToBytes( MIN_PKCSIZE_BITS ) ) + \
					sizeofString32( "", 1 ) );
	if( cryptStatusError( length ) )
		return( length );
	sMemConnect( stream, sessionInfoPtr->receiveBuffer, length );
	sgetc( stream );		/* Skip packet type */
	streamBookmarkSet( stream, keyexInfoPtr, keyexInfoLength );
	readInteger32( stream, NULL, NULL, bitsToBytes( MIN_PKCSIZE_BITS ), 
				   CRYPT_MAX_PKCSIZE );
	status = readInteger32( stream, NULL, NULL, 1, CRYPT_MAX_PKCSIZE );
	streamBookmarkComplete( stream, keyexInfoLength );
	sMemDisconnect( stream );
	if( cryptStatusError( status ) )
		retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
				"Invalid DH ephemeral key data packet" );

	/* Since this phase of the key negotiation exchanges raw key components 
	   rather than the standard SSH public-key format, we have to rewrite 
	   the raw key components into a standard SSH key so that we can import 
	   it:

			From:					To:

								string		[ key/certificate ]
									string	"ssh-dh"
			mpint	p				mpint	p
			mpint	g				mpint	g */
	memmove( keyexInfoPtr + offset, keyexInfoPtr, keyexInfoLength );
	sMemOpen( stream, keyexInfoPtr, offset );
	writeUint32( stream, ( offset - LENGTH_SIZE ) + keyexInfoLength );
	writeString32( stream, "ssh-dh", 0 );
	sMemDisconnect( stream );

	/* Destroy the existing static DH key, load the new one, and re-perform 
	   phase 1 of the DH key agreement process */
	krnlSendNotifier( handshakeInfo->iServerCryptContext, 
					  IMESSAGE_DECREFCOUNT );
	status = initDHcontextSSH( &handshakeInfo->iServerCryptContext,

⌨️ 快捷键说明

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