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

📄 ssh2_cli.c

📁 cryptlib安全工具包
💻 C
📖 第 1 页 / 共 4 页
字号:
/****************************************************************************
*																			*
*						cryptlib SSHv2 Client Management					*
*						Copyright Peter Gutmann 1998-2008					*
*																			*
****************************************************************************/

#if defined( INC_ALL )
  #include "crypt.h"
  #include "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_SSH

/* 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 ALGO_STRING_INFO FAR_BSS algoStringUserauthentPWTbl[] = {
	{ "password", 8, CRYPT_PSEUDOALGO_PASSWORD },
	{ "keyboard-interactive", 20, CRYPT_PSEUDOALGO_PAM },
	{ "publickey", 9, CRYPT_ALGO_RSA },
	{ NULL, 0, CRYPT_ALGO_NONE }, { NULL, 0, CRYPT_ALGO_NONE }
	};
static const ALGO_STRING_INFO FAR_BSS algoStringUserauthentPKCTbl[] = {
	{ "publickey", 9, CRYPT_ALGO_RSA },
	{ "password", 8, CRYPT_PSEUDOALGO_PASSWORD },
	{ "keyboard-interactive", 20, CRYPT_PSEUDOALGO_PAM },
	{ NULL, 0, CRYPT_ALGO_NONE }, { 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_ATOMIC hashFunctionAtomic;
	const ATTRIBUTE_LIST *attributeListPtr = \
				findSessionInfo( sessionInfoPtr->attributeList,
								 CRYPT_SESSINFO_SERVER_FINGERPRINT );
	BYTE fingerPrint[ CRYPT_MAX_HASHSIZE + 8 ];
	int hashSize;

	getHashAtomicParameters( CRYPT_ALGO_MD5, &hashFunctionAtomic, &hashSize );
	hashFunctionAtomic( fingerPrint, CRYPT_MAX_HASHSIZE, 
						keyData, keyDataLength );
	if( attributeListPtr == NULL )
		{
		/* Remember the value for the caller */
		return( addSessionInfo( &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 )
		{
		getHashAtomicParameters( CRYPT_ALGO_SHA1, &hashFunctionAtomic, 
								 &hashSize );
		hashFunctionAtomic( fingerPrint, CRYPT_MAX_HASHSIZE, 
							keyData, keyDataLength );
		}

	/* 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( CRYPT_ERROR_WRONGKEY,
				( CRYPT_ERROR_WRONGKEY, SESSION_ERRINFO, 
				  "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 = \
			( findSessionInfo( 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 */
	if( hasPassword )
		{
		status = readAlgoString( &stream, algoStringUserauthentPWTbl,
								 FAILSAFE_ARRAYSIZE( algoStringUserauthentPWTbl, \
													 ALGO_STRING_INFO ),
								 &authentAlgo, FALSE, SESSION_ERRINFO );
		}
	else
		{
		status = readAlgoString( &stream, algoStringUserauthentPKCTbl,
								 FAILSAFE_ARRAYSIZE( algoStringUserauthentPKCTbl, \
													 ALGO_STRING_INFO ),
								 &authentAlgo, FALSE, SESSION_ERRINFO );
		}
	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( CRYPT_ERROR_NOTAVAIL,
					( CRYPT_ERROR_NOTAVAIL, SESSION_ERRINFO, 
					  "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( CRYPT_ERROR_NOTINITED,
					( CRYPT_ERROR_NOTINITED, SESSION_ERRINFO, 
					  "Server requested password authentication but only a "
					  "public/private key was available" ) );
			}

		retExt( CRYPT_ERROR_WRONGKEY,
				( CRYPT_ERROR_WRONGKEY, SESSION_ERRINFO, 
				  "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( CRYPT_ERROR_NOTINITED,
				( CRYPT_ERROR_NOTINITED, SESSION_ERRINFO, 
				  "Server requested public-key authentication but only a "
				  "password was available" ) );
		}

	retExt( CRYPT_ERROR_WRONGKEY,
			( CRYPT_ERROR_WRONGKEY, SESSION_ERRINFO, 
			  "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 );
	void *keyexInfoPtr = DUMMY_INIT_PTR;
	int keyexInfoLength = DUMMY_INIT, length, packetOffset, dummy, 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
	status = continuePacketStreamSSH( stream, SSH2_MSG_KEXDH_GEX_REQUEST_OLD,
									  &packetOffset );
	if( cryptStatusOK( status ) )
		{
		streamBookmarkSet( stream, keyexInfoLength );
		status = writeUint32( stream, bytesToBits( SSH2_DEFAULT_KEYSIZE ) );
		}
#else
	status = continuePacketStreamSSH( stream, SSH2_MSG_KEXDH_GEX_REQUEST_NEW,
									  &packetOffset );
	if( cryptStatusOK( status ) )
		{
		streamBookmarkSet( stream, keyexInfoLength );
		writeUint32( stream, 1024 );
		writeUint32( stream, bytesToBits( SSH2_DEFAULT_KEYSIZE ) );
		status = writeUint32( stream, bytesToBits( CRYPT_MAX_PKCSIZE ) );
		}
#endif /* 1 */
	if( cryptStatusOK( status ) )
		status = streamBookmarkComplete( stream, &keyexInfoPtr, 
										 &keyexInfoLength, keyexInfoLength );
	if( cryptStatusOK( status ) )
		status = wrapPacketSSH2( sessionInfoPtr, stream, packetOffset, 
								 FALSE, TRUE );
	if( cryptStatusOK( status ) )
		status = sendPacketSSH2( sessionInfoPtr, stream, TRUE );
	sMemDisconnect( stream );
	if( cryptStatusError( status ) )
		return( status );
	ENSURES( keyexInfoPtr != NULL );

	/* 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 */
	status = length = \
		readHSPacketSSH2( sessionInfoPtr, SSH2_MSG_KEXDH_GEX_GROUP,
						  ID_SIZE + sizeofString32( "", MIN_PKCSIZE ) + \
							sizeofString32( "", 1 ) );
	if( cryptStatusError( status ) )
		return( status );
	sMemConnect( stream, sessionInfoPtr->receiveBuffer, length );
	status = sgetc( stream );		/* Skip packet type */
	if( !cryptStatusError( status ) )
		{
		streamBookmarkSet( stream, keyexInfoLength );
		status = readInteger32Checked( stream, NULL, &dummy, MIN_PKCSIZE, 
									   CRYPT_MAX_PKCSIZE );
		}
	if( cryptStatusOK( status ) )
		status = readInteger32( stream, NULL, &dummy, 1, 
								CRYPT_MAX_PKCSIZE );
	if( cryptStatusOK( status ) )
		{
		status = streamBookmarkComplete( stream, &keyexInfoPtr, 
										 &keyexInfoLength, 
										 keyexInfoLength );
		}
	sMemDisconnect( stream );
	if( cryptStatusError( status ) )
		{
		/* Some misconfigured servers may use very short keys, we perform
		   a special-case check for these and return a more specific message
		   than the generic bad-data */
		if( status == CRYPT_ERROR_NOSECURE )
			{
			retExt( CRYPT_ERROR_NOSECURE,
					( CRYPT_ERROR_NOSECURE, SESSION_ERRINFO, 
					  "Insecure key used in key exchange" ) );
			}

		retExt( CRYPT_ERROR_BADDATA,
				( CRYPT_ERROR_BADDATA, SESSION_ERRINFO, 
				  "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( ( BYTE * ) keyexInfoPtr + offset, keyexInfoPtr, 
			 keyexInfoLength );
	sMemOpen( stream, keyexInfoPtr, offset );
	writeUint32( stream, ( offset - LENGTH_SIZE ) + keyexInfoLength );
	writeString32( stream, "ssh-dh", 6 );
	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,
							   &handshakeInfo->serverKeySize, keyexInfoPtr,
							   offset + keyexInfoLength,
							   CRYPT_UNUSED );
	if( cryptStatusOK( status ) )

⌨️ 快捷键说明

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