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

📄 ssh2_svr.c

📁 cryptlib安全工具包
💻 C
📖 第 1 页 / 共 3 页
字号:
/****************************************************************************
*																			*
*						cryptlib SSHv2 Server 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

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

/* SSHv2 algorithm names sent to the client, in preferred algorithm order.
   Since we have a fixed algorithm for our public key (determined by the key
   type), we only send a single value for this that's evaluated at runtime,
   so there's no list for this defined.

   Note that these lists must match the algoStringXXXTbl values in ssh2.c */

static const CRYPT_ALGO_TYPE FAR_BSS algoKeyexList[] = {
	CRYPT_PSEUDOALGO_DHE, CRYPT_ALGO_DH, 
	CRYPT_ALGO_NONE, CRYPT_ALGO_NONE };
static const CRYPT_ALGO_TYPE FAR_BSS algoEncrList[] = {
	/* We can't list AES as an option because the peer can pick up anything
	   it wants from the list as its preferred choice, which means that if
	   we're talking to any non-cryptlib implementation they always go for
	   AES even though it doesn't yet have the full provenance of 3DES.  
	   Once AES passes the five-year test this option can be enabled */
	CRYPT_ALGO_3DES, /*CRYPT_ALGO_AES,*/ CRYPT_ALGO_BLOWFISH,
	CRYPT_ALGO_CAST, CRYPT_ALGO_IDEA, CRYPT_ALGO_RC4, 
	CRYPT_ALGO_NONE, CRYPT_ALGO_NONE };
static const CRYPT_ALGO_TYPE FAR_BSS algoMACList[] = {
	CRYPT_ALGO_HMAC_SHA, CRYPT_ALGO_HMAC_MD5, 
	CRYPT_ALGO_NONE, CRYPT_ALGO_NONE };
static const CRYPT_ALGO_TYPE FAR_BSS algoStringUserauthentList[] = {
	CRYPT_PSEUDOALGO_PASSWORD, CRYPT_ALGO_NONE, CRYPT_ALGO_NONE };

/* Encode a list of available algorithms */

static int writeAlgoList( STREAM *stream, const CRYPT_ALGO_TYPE *algoList )
	{
	static const ALGO_STRING_INFO FAR_BSS algoStringMapTbl[] = {
		{ "ssh-rsa", 7, CRYPT_ALGO_RSA },
		{ "ssh-dss", 7, CRYPT_ALGO_DSA },
		{ "3des-cbc", 8, CRYPT_ALGO_3DES },
		{ "aes128-cbc", 10, CRYPT_ALGO_AES },
		{ "blowfish-cbc", 12, CRYPT_ALGO_BLOWFISH },
		{ "cast128-cbc", 11, CRYPT_ALGO_CAST },
		{ "idea-cbc", 8, CRYPT_ALGO_IDEA },
		{ "arcfour", 7, CRYPT_ALGO_RC4 },
		{ "diffie-hellman-group-exchange-sha1", 34, CRYPT_PSEUDOALGO_DHE },
		{ "diffie-hellman-group1-sha1", 26, CRYPT_ALGO_DH },
		{ "hmac-sha1", 9, CRYPT_ALGO_HMAC_SHA },
		{ "hmac-md5", 8, CRYPT_ALGO_HMAC_MD5 },
		{ "password", 8, CRYPT_PSEUDOALGO_PASSWORD },
		{ NULL, 0, CRYPT_ALGO_NONE }, { NULL, 0, CRYPT_ALGO_NONE }
		};
	const char *availableAlgos[ 16 + 8 ];
	int noAlgos = 0, length = 0, algoIndex, status;

	/* Walk down the list of algorithms remembering the encoded name of each
	   one that's available for use */
	for( algoIndex = 0; \
		 algoList[ algoIndex ] != CRYPT_ALGO_NONE && \
			algoIndex < FAILSAFE_ITERATIONS_SMALL; 
		 algoIndex++ )
		{
		if( algoAvailable( algoList[ algoIndex ] ) || \
			isPseudoAlgo( algoList[ algoIndex ] ) )
			{
			int i;

			for( i = 0; 
				 algoStringMapTbl[ i ].algo != CRYPT_ALGO_NONE && \
					algoStringMapTbl[ i ].algo != algoList[ algoIndex ] && \
					i < FAILSAFE_ARRAYSIZE( algoStringMapTbl, ALGO_STRING_INFO ); 
				 i++ );
			if( i >= FAILSAFE_ARRAYSIZE( algoStringMapTbl, ALGO_STRING_INFO ) )
				retIntError();
			assert( algoStringMapTbl[ i ].algo != CRYPT_ALGO_NONE );
			assert( noAlgos < 16 );
			availableAlgos[ noAlgos++ ] = algoStringMapTbl[ i ].name;
			length += strlen( algoStringMapTbl[ i ].name );
			if( noAlgos > 1 )
				length++;			/* Room for comma delimiter */
			}
		}
	if( algoIndex >= FAILSAFE_ITERATIONS_SMALL )
		retIntError();

	/* Encode the list of available algorithms into a comma-separated string */
	status = writeUint32( stream, length );
	for( algoIndex = 0; cryptStatusOK( status ) && algoIndex < noAlgos; 
		 algoIndex++ )
		{
		if( algoIndex > 0 )
			sputc( stream, ',' );	/* Add comma delimiter */
		status = swrite( stream, availableAlgos[ algoIndex ],
						 strlen( availableAlgos[ algoIndex ] ) );
		}
	return( status );
	}

/* Handle an ephemeral DH key exchange */

static int processDHE( SESSION_INFO *sessionInfoPtr,
					   SSH_HANDSHAKE_INFO *handshakeInfo )
	{
	STREAM stream;
	void *keyPtr = DUMMY_INIT_PTR;
	void *keyexInfoPtr = DUMMY_INIT_PTR;
	const int offset = LENGTH_SIZE + sizeofString32( "ssh-dh", 6 );
	int keyPos, keyLength, keyexInfoLength, length, type, status;

	/* Get the keyex key request from the client:

		byte	type = SSH2_MSG_KEXDH_GEX_REQUEST_OLD
		uint32	n (bits)

	   or:

		byte	type = SSH2_MSG_KEXDH_GEX_REQUEST_NEW
		uint32	min (bits)
		uint32	n (bits)
		uint32	max (bits)

	   Portions of the the request info are hashed later as part of the
	   exchange hash, so we have to save a copy for then.  We save the
	   original encoded form, because some clients send non-integral lengths
	   that don't survive the conversion from bits to bytes */
	status = length = \
		readHSPacketSSH2( sessionInfoPtr, SSH2_MSG_KEXDH_GEX_REQUEST_OLD,
						  ID_SIZE + UINT32_SIZE );
	if( cryptStatusError( status ) )
		return( status );
	sMemConnect( &stream, sessionInfoPtr->receiveBuffer, length );
	type = sgetc( &stream );
	streamBookmarkSet( &stream, keyexInfoLength );
	if( type == SSH2_MSG_KEXDH_GEX_REQUEST_NEW )
		{
		/* It's a { min_length, length, max_length } sequence, save a copy
		   and get the length value */
		readUint32( &stream );
		keyLength = readUint32( &stream );
		status = readUint32( &stream );
		}
	else
		{
		/* It's a straight length, save a copy and get the length value */
		status = keyLength = readUint32( &stream );
		}
	if( !cryptStatusError( status ) )
		status = streamBookmarkComplete( &stream, &keyexInfoPtr, 
										 &keyexInfoLength, keyexInfoLength );
	sMemDisconnect( &stream );
	if( cryptStatusError( status ) )
		{
		retExt( status,
				( status, SESSION_ERRINFO, 
				  "Invalid ephemeral DH key data request packet" ) );
		}
	if( keyLength < bytesToBits( MIN_PKCSIZE ) || \
		keyLength > bytesToBits( CRYPT_MAX_PKCSIZE ) )
		{
		retExt( CRYPT_ERROR_BADDATA, 
				( CRYPT_ERROR_BADDATA, SESSION_ERRINFO, 
				  "Client requested invalid ephemeral DH key size %d bits",
				  keyLength ) );
		}
	memcpy( handshakeInfo->encodedReqKeySizes, keyexInfoPtr,
			keyexInfoLength );
	handshakeInfo->encodedReqKeySizesLength = keyexInfoLength;
	handshakeInfo->requestedServerKeySize = bitsToBytes( keyLength );

	/* If the requested key size differs too much from the built-in default
	   one, destroy the existing default DH key and load a new one of the
	   appropriate size.  Things get quite confusing here because the spec
	   is a schizophrenic mix of two different documents, one that specifies
	   the behaviour for the original message format which uses a single
	   length value and a second one that specifies the behaviour for the
	   { min, n, max } combination.  The range option was added as an
	   attempted fix for implementations that couldn't handle the single
	   size option, but the real problem is that the server knows what key
	   sizes are appropriate but the client has to make the choice, without
	   any knowledge of what the server can actually handle.  Because of
	   this the spec (in its n-only mindset, which also applies to the
	   min/n/max version since it's the same document) contains assorted
	   weasel-words that allow the server to choose any key size it feels
	   like if the client sends a range indication that's inappropriate.
	   Although the spec ends up saying that the server can do anything it
	   feels like ("The server should return the smallest group it knows
	   that is larger than the size the client requested.  If the server
	   does not know a group that is larger than the client request, then it
	   SHOULD return the largest group it knows"), we use a least-upper-
	   bound interpretation of the above, mostly because we store a range of
	   fixed keys of different sizes and can always find something
	   reasonably close to any (sensible) requested length */
	if( handshakeInfo->requestedServerKeySize < \
										SSH2_DEFAULT_KEYSIZE - 16 || \
		handshakeInfo->requestedServerKeySize > \
										SSH2_DEFAULT_KEYSIZE + 16 )
		{
		krnlSendNotifier( handshakeInfo->iServerCryptContext,
						  IMESSAGE_DECREFCOUNT );
		status = initDHcontextSSH( &handshakeInfo->iServerCryptContext,
								   &handshakeInfo->serverKeySize, NULL, 0,
								   handshakeInfo->requestedServerKeySize );
		if( cryptStatusError( status ) )
			return( status );
		}

	/* Send the DH key values to the client:

		byte	type = SSH2_MSG_KEXDH_GEX_GROUP
		mpint	p
		mpint	g

	   Since this phase of the key negotiation exchanges raw key components
	   rather than the standard SSH public-key format, we have to rewrite
	   the public key before we can send it to the client.  What this 
	   involves is stripping the:

		uint32	length
		string	"ssh-dh"

	   header from the start of the key, which is accomplished by moving the
	   key data down offset (= LENGTH_SIZE + sizeofString32( "ssh-dh", 6 ))
	   bytes */
	status = openPacketStreamSSH( &stream, sessionInfoPtr, CRYPT_USE_DEFAULT,
								  SSH2_MSG_KEXDH_GEX_GROUP );
	if( cryptStatusError( status ) )
		return( status );
	streamBookmarkSet( &stream, keyPos );
	status = exportAttributeToStream( &stream,
									  handshakeInfo->iServerCryptContext,
									  CRYPT_IATTRIBUTE_KEY_SSH );
	if( cryptStatusOK( status ) )
		{
		keyLength = keyPos;
		status = streamBookmarkComplete( &stream, &keyPtr, &keyLength, 
										 keyLength );
		}
	if( cryptStatusError( status ) )
		return( status );
	ENSURES( keyPtr != NULL );
	memmove( keyPtr, ( BYTE * ) keyPtr + offset, keyLength - offset );
	status = sseek( &stream, keyPos + keyLength - offset );
	if( cryptStatusOK( status ) )
		status = sendPacketSSH2( sessionInfoPtr, &stream, FALSE );
	sMemDisconnect( &stream );
	return( status );
	}

/* Handle user authentication.  This can get a bit complicated because of 
   the way the multi-pass user auth.affects the handling of username and 
   password information.  If there's no caller-supplied list of { username, 
   password } pairs present then the first time around we remember the user 
   name but then get an auth.type of "none", which means we have to go for a 
   second iteration to get the password.  On the second iteration we have a 
   remembered user name present, but no password yet.  
   
   In addition we have to be careful about potential attacks, e.g. the 
   client entering a privileged user name the first time around and then 
   authenticating the second time round as an unprivileged user.  If the 
   calling app just grabs the first username it finds, it'll treat the 
   client as being an authenticated privileged user.
   
   To handle this, we record the name the first time that it's entered and 
   from then on treat it as a user-supplied name so that the client has to
   supply the same name on subsequent password attempts.  This is the
   standard client behaviour anyway, if the username + password are rejected
   the assumption is that the password is wrong and the user gets to retry
   the password.

   The handling of authentication information is as follows:

	Client		| Caller-supplied	| No caller-supplied
	  sends...	|	list			|	list
	------------+-------------------+-------------------
	Name, pw	| Match name, pw	| Add name, pw
	------------+-------------------+-------------------
	Name, none	| Match	name		| Add name
	Name, pw	| Match	name, pw	| Match name
				|					| Add pw
	------------+-------------------+-------------------
	Name, none	| Match	name		| Add name
	Name2, pw	| Match name2, fail	| Match name2, fail
	------------+-------------------+-------------------
	Retry		| Match name		| (See note below)
	 Name, pw2	| Match pw2			|
				|					|

   Handling password retries gets somewhat complicated because we need to
   record them for the caller to check but can't still have them hanging
   around at the next iteration because they'll prevent the entry of any
   further passwords.  On the other hand we can't just clear them before
   every (re-)activation attempt because on the final (re-)authentication
   they'll be valid and need to be retained in case the caller wants to
   examine them.  The way we handle this is:

	if( password present and supplied by caller )
		compare with client password;
	else
		// Password not present, or present but supplied by the client on
		// a previous iteration, denoted by ATTR_FLAG_EPHEMERAL being set
		add/replace with client password;

   Unlike SSHv1, SSHv2 properly identifies public keys, however because of
   its complexity (several more states added to the state machine because of
   SSHv2's propensity for carrying out any negotiation it performs in lots
   of little bits and pieces) we don't support this form of authentication
   until someone specifically requests it */

static int processUserAuth( SESSION_INFO *sessionInfoPtr,
							SSH_HANDSHAKE_INFO *handshakeInfo )
	{
	STREAM stream;
	const ATTRIBUTE_LIST *attributeListPtr;
	BYTE userNameBuffer[ CRYPT_MAX_TEXTSIZE + 8 ];
	BYTE stringBuffer[ CRYPT_MAX_TEXTSIZE + 8 ];
	BOOLEAN userNamePresent = FALSE;
	int length, userNameLength, stringLength, status;

	/* Get the userAuth packet from the client:

		byte	type = SSH2_MSG_USERAUTH_REQUEST
		string	user_name
		string	service_name = "ssh-connection"
		string	method_name = "none" | "password"
		[ boolean	FALSE ]
		[ string	password ]

	    The client can optionally send a method-type of "none" to indicate 
		that it'd like the server to return a list of allowed authentication 
		types, if we get a packet of this kind we return our allowed types 
		list */
	status = length = \
		readHSPacketSSH2( sessionInfoPtr, SSH2_MSG_USERAUTH_REQUEST,
						  ID_SIZE + sizeofString32( "", 1 ) + \
							sizeofString32( "", 8 ) + \
							sizeofString32( "", 4 ) );
	if( cryptStatusError( status ) )
		return( status );
	sMemConnect( &stream, sessionInfoPtr->receiveBuffer, length );
	sgetc( &stream );		/* Skip packet type */

	/* Process the user name */
	status = readString32( &stream, userNameBuffer, CRYPT_MAX_TEXTSIZE, 
						   &userNameLength );
	if( cryptStatusError( status ) || \
		userNameLength <= 0 || userNameLength > CRYPT_MAX_TEXTSIZE )
		{
		sMemDisconnect( &stream );
		retExt( CRYPT_ERROR_BADDATA,

⌨️ 快捷键说明

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