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

📄 ssh2_cli.c

📁 cryptlib安全工具包
💻 C
📖 第 1 页 / 共 4 页
字号:
		{
		memset( keyAgreeParams, 0, sizeof( KEYAGREE_PARAMS ) );
		status = krnlSendMessage( handshakeInfo->iServerCryptContext,
								  IMESSAGE_CTX_ENCRYPT, keyAgreeParams,
								  sizeof( KEYAGREE_PARAMS ) );
		}
	if( cryptStatusError( status ) )
		return( status );

	/* We've already sent the client hello as part of the keyex negotiation
	   so there's no need to bundle it with the client keyex, reset the
	   start position in the send buffer */
	sMemOpen( stream, sessionInfoPtr->sendBuffer,
			  sessionInfoPtr->sendBufSize - EXTRA_PACKET_SIZE );

	return( CRYPT_OK );
	}

/* Handle PAM authentication */

static int processPamAuthentication( SESSION_INFO *sessionInfoPtr )
	{
	const ATTRIBUTE_LIST *userNamePtr = \
				findSessionInfo( sessionInfoPtr->attributeList,
								 CRYPT_SESSINFO_USERNAME );
	const ATTRIBUTE_LIST *passwordPtr = \
				findSessionInfo( sessionInfoPtr->attributeList,
								 CRYPT_SESSINFO_PASSWORD );
	STREAM stream;
	int length, pamIteration, status;

	/* Send a user-auth request asking for PAM authentication:

		byte	type = SSH2_MSG_USERAUTH_REQUEST
		string	user_name
		string	service_name = "ssh-connection"
		string	method_name = "keyboard-interactive"
		string	language = ""
		string	sub_methods = "password"

	   The sub-methods are implementation-dependent and the spec suggests an
	   implementation strategy in which the server ignores them so
	   specifying anything here is mostly wishful thinking, but we ask for
	   password auth. anyway in case it helps */
	status = openPacketStreamSSH( &stream, sessionInfoPtr, CRYPT_USE_DEFAULT,
								  SSH2_MSG_USERAUTH_REQUEST );
	if( cryptStatusError( status ) )
		return( status );
	writeString32( &stream, userNamePtr->value, userNamePtr->valueLength );
	writeString32( &stream, "ssh-connection", 14 );
	writeString32( &stream, "keyboard-interactive", 20 );
	writeUint32( &stream, 0 );		/* No language tag */
	if( sessionInfoPtr->protocolFlags & SSH_PFLAG_PAMPW )
		{
		/* Some servers choke if we supply a sub-method hint for the
		   authentication */
		status = writeUint32( &stream, 0 );
		}
	else
		status = writeString32( &stream, "password", 8 );
	if( cryptStatusOK( status ) )
		status = sendPacketSSH2( sessionInfoPtr, &stream, FALSE );
	sMemDisconnect( &stream );
	if( cryptStatusError( status ) )
		return( status );

	/* Handle the PAM negotiation.  This can (in theory) go on indefinitely,
	   to avoid potential DoS problems we limit it to five iterations.  In
	   general we'll go for two iterations (or three for OpenSSH's empty-
	   message bug), so we shouldn't ever get to five */
	for( pamIteration = 0; pamIteration < 5; pamIteration++ )
		{
		BYTE nameBuffer[ CRYPT_MAX_TEXTSIZE + 8 ];
		BYTE promptBuffer[ CRYPT_MAX_TEXTSIZE + 8 ];
		int nameLength, promptLength = -1, noPrompts = -1, type;

		/* Read back the response to our last message.  Although the spec
		   requires that the server not respond with a SSH2_MSG_USERAUTH_-
		   FAILURE message if the request fails because of an invalid user
		   name (to prevent an attacker from being able to determine valid
		   user names by checking for error responses), some servers can
		   return a failure indication at this point so we have to allow for
		   a failure response as well as the expected SSH2_MSG_USERAUTH_-
		   INFO_REQUEST */
		status = length = \
			readHSPacketSSH2( sessionInfoPtr, SSH2_MSG_SPECIAL_USERAUTH_PAM,
							  ID_SIZE );
		if( cryptStatusError( status ) )
			return( status );

		/* See what we got.  If it's not a PAM info request, we're done */
		sMemConnect( &stream, sessionInfoPtr->receiveBuffer, length );
		type = sgetc( &stream );
		if( type != SSH2_MSG_USERAUTH_INFO_REQUEST )
			sMemDisconnect( &stream );

		/* If it's a success status, we're done */
		if( type == SSH2_MSG_USERAUTH_SUCCESS )
			return( CRYPT_OK );

		/* If the authentication failed, provide more specific details to
		   the caller */
		if( type == SSH2_MSG_USERAUTH_FAILURE )
			{
			/* If we failed on the first attempt (before we even tried to
			   send a password), it's probably because the user name is
			   invalid (or the server has the SSH_PFLAG_PAMPW bug).  Having
			   the server return a failure due to an invalid user name
			   shouldn't happen (see the comment above), but we handle it
			   just in case */
			if( pamIteration == 0 )
				{
				char userNameBuffer[ CRYPT_MAX_TEXTSIZE + 8 ];

				memcpy( userNameBuffer, userNamePtr->value,
						userNamePtr->valueLength );
				retExt( CRYPT_ERROR_WRONGKEY,
						( CRYPT_ERROR_WRONGKEY, SESSION_ERRINFO, 
						  "Server reported: Invalid user name '%s'",
						  sanitiseString( userNameBuffer,
										  CRYPT_MAX_TEXTSIZE,
									      userNamePtr->valueLength ) ) );
				}

			/* It's a failure after we've tried to authenticate ourselves,
			   report the details to the caller */
			return( reportAuthFailure( sessionInfoPtr, length, TRUE ) );
			}

		/* Process the PAM user-auth request:

			byte	type = SSH2_MSG_USERAUTH_INFO_REQUEST
			string	name
			string	instruction
			string	language = {}
			int		num_prompts
				string	prompt[ n ]
				boolean	echo[ n ]

		   Exactly whose name is supplied or what the instruction field is
		   for is left unspecified by the RFC (and they may indeed be left
		   empty), so we just skip it.  Many implementations feel similarly
		   about this and leave the fields empty.

		   If the PAM authentication (from a previous iteration) fails or
		   succeeds, the server is supposed to send back a standard user-
		   auth success or failure status, but could also send another
		   SSH2_MSG_USERAUTH_INFO_REQUEST even if it contains no payload (an
		   OpenSSH bug), so we have to handle this as a special case */
		status = readString32( &stream, nameBuffer, CRYPT_MAX_TEXTSIZE,
							   &nameLength );			/* Name */
		if( cryptStatusOK( status ) )
			{
			nameBuffer[ nameLength ] = '\0';
			status = readUniversal32( &stream );		/* Instruction */
			}
		if( cryptStatusOK( status ) )
			status = readUniversal32( &stream );		/* Language */
		if( cryptStatusOK( status ) )
			{
			status = noPrompts = readUint32( &stream );	/* No.prompts */
			if( !cryptStatusError( status ) && noPrompts > 8 )
				{
				/* Requesting more than a small number of prompts is 
				   suspicious */
				status = CRYPT_ERROR_BADDATA;
				}
			}
		if( !cryptStatusError( status ) && noPrompts > 0 )
			{
			status = readString32( &stream, promptBuffer, 
								   CRYPT_MAX_TEXTSIZE, &promptLength );
			if( cryptStatusOK( status ) )
				promptBuffer[ promptLength ] = '\0';
			}
		sMemDisconnect( &stream );
		if( cryptStatusError( status ) )
			{
			retExt( status,
					( status, SESSION_ERRINFO, 
					  "Invalid PAM authentication request packet" ) );
			}

		/* If we got a prompt, make sure that we're being asked for some
		   form of password authentication.  This assumes that the prompt
		   string begins with the word "password" (which always seems to be
		   the case), if this isn't the case then it may be necessary to do
		   a substring search */
		if( noPrompts > 0 && \
			( promptLength < 8 || \
			  strCompare( promptBuffer, "Password", 8 ) ) )
			{
			retExt( CRYPT_ERROR_BADDATA,
					( CRYPT_ERROR_BADDATA, SESSION_ERRINFO, 
					  "Server requested unknown PAM authentication type "
					  "'%s'", ( nameLength > 0 ) ? \
						sanitiseString( nameBuffer, CRYPT_MAX_TEXTSIZE, \
										nameLength ) : \
						sanitiseString( promptBuffer, CRYPT_MAX_TEXTSIZE, \
										promptLength ) ) );
			}

		/* Send back the PAM user-auth response:

			byte	type = SSH2_MSG_USERAUTH_INFO_RESPONSE
			int		num_responses = num_prompts
			string	response

		   What to do if there's more than one prompt is a bit tricky,
		   usually PAM is used as a form of (awkward) password
		   authentication and there's only a single prompt, if we ever
		   encounter a situation where there's more than one prompt, it's
		   probably a request to confirm the password, so we just send it
		   again for successive prompts */
		status = openPacketStreamSSH( &stream, sessionInfoPtr, 
									  CRYPT_USE_DEFAULT,
									  SSH2_MSG_USERAUTH_INFO_RESPONSE );
		if( cryptStatusError( status ) )
			return( status );
		status = writeUint32( &stream, noPrompts );
		while( cryptStatusOK( status ) && noPrompts-- > 0 )
			{
			status = writeString32( &stream, passwordPtr->value,
									passwordPtr->valueLength );
			}
		if( cryptStatusOK( status ) )
			status = sendPacketSSH2( sessionInfoPtr, &stream, FALSE );
		sMemDisconnect( &stream );
		if( cryptStatusError( status ) )
			return( status );
		}

	retExt( CRYPT_ERROR_BADDATA,
			( CRYPT_ERROR_BADDATA, SESSION_ERRINFO, 
			  "Too many iterations of negotiation during PAM "
			  "authentication" ) );
	}

/****************************************************************************
*																			*
*						Client-side Connect Functions						*
*																			*
****************************************************************************/

/* Perform the initial part of the handshake with the server */

static int beginClientHandshake( SESSION_INFO *sessionInfoPtr,
								 SSH_HANDSHAKE_INFO *handshakeInfo )
	{
	MESSAGE_CREATEOBJECT_INFO createInfo;
	KEYAGREE_PARAMS keyAgreeParams;
	STREAM stream;
	void *clientHelloPtr = DUMMY_INIT_PTR, *keyexPtr = DUMMY_INIT_PTR;
	int serverHelloLength, clientHelloLength, keyexLength = DUMMY_INIT;
	int packetOffset, status;

	/* The higher-level code has already read the server version info, send
	   back our own version info (SSHv2 sends a CR and LF as terminator,
	   but this isn't hashed) */
	status = swrite( &sessionInfoPtr->stream, SSH2_ID_STRING "\r\n",
					 SSH_ID_STRING_SIZE + 2 );
	if( cryptStatusError( status ) )
		{
		sNetGetErrorInfo( &sessionInfoPtr->stream,
						  &sessionInfoPtr->errorInfo );
		return( status );
		}

	/* SSHv2 hashes parts of the handshake messages for integrity-protection
	   purposes, so we hash the ID strings (first our client string, then the
	   server string that we read previously) encoded as SSH string values */
	status = hashAsString( handshakeInfo->iExchangeHashcontext, 
						   SSH2_ID_STRING, SSH_ID_STRING_SIZE );
	if( cryptStatusOK( status ) )
		status = hashAsString( handshakeInfo->iExchangeHashcontext,
							   sessionInfoPtr->receiveBuffer,
							   strlen( sessionInfoPtr->receiveBuffer ) );
	if( cryptStatusError( status ) )
		return( status );

	/* While we wait for the server to digest our version info and send
	   back its response, we can create the context with the DH key and
	   perform phase 1 of the DH key agreement process */
	status = initDHcontextSSH( &handshakeInfo->iServerCryptContext,
							   &handshakeInfo->serverKeySize, NULL, 0,
							   CRYPT_USE_DEFAULT );
	if( cryptStatusError( status ) )
		return( status );
	memset( &keyAgreeParams, 0, sizeof( KEYAGREE_PARAMS ) );
	status = krnlSendMessage( handshakeInfo->iServerCryptContext,
							  IMESSAGE_CTX_ENCRYPT, &keyAgreeParams,
							  sizeof( KEYAGREE_PARAMS ) );
	if( cryptStatusError( status ) )
		return( status );

	/* Process the server hello */
	status = processHelloSSH( sessionInfoPtr, handshakeInfo,
							  &serverHelloLength, FALSE );
	if( cryptStatusError( status ) )
		return( status );

	/* Build the client hello and DH phase 1 keyex packet:

		byte		type = SSH2_MSG_KEXINIT
		byte[16]	cookie
		string		keyex algorithms = DH
		string		pubkey algorithms
		string		client_crypto algorithms
		string		server_crypto algorithms
		string		client_mac algorithms
		string		server_mac algorithms
		string		client_compression algorithms = "none"
		string		server_compression algorithms = "none"
		string		client_language = ""
		string		server_language = ""
		boolean		first_keyex_packet_follows = FALSE
		uint32		reserved = 0
		...

	   The SSH spec leaves the order in which things happen ambiguous, in
	   order to save a whole round trip it has provisions for both sides
	   shouting at each other and then a complex interlock process where
	   bits of the initial exchange can be discarded and retried if necessary.
	   This is ugly and error-prone, so what we do is wait for the server
	   hello (already done earlier), choose known-good algorithms, and then
	   send the client hello immediately followed by the client keyex.
	   Since we wait for the server to speak first, we can choose parameters
	   that are accepted the first time.  In theory this means that we can
	   set keyex_follows to true (since a correct keyex packet always
	   follows the hello), however because of the nondeterministic initial
	   exchange the spec requires that a (guessed) keyex be discarded by the
	   server if the hello doesn't match (even if the keyex does):

		svr: hello
		client: matched hello, keyex
		svr: (discard keyex)

	   To avoid this problem, we set keyex_follows to false to make it clear
	   to the server that the keyex is the real thing and shouldn't be
	   discarded */
	status = openPacketStreamSSH( &stream, sessionInfoPtr, CRYPT_USE_DEFAULT,
								  SSH2_MSG_KEXINIT );
	if( cryptStatusError( status ) )
		return( status );
	streamBookmarkSetFullPacket( &stream, clientHelloLength );
	status = exportVarsizeAttributeToStream( &stream, SYSTEM_OBJECT_HANDLE,
											 CRYPT_IATTRIBUTE_RANDOM_NONCE,
											 SSH2_COOKIE_SIZE );
	if( cryptStatusError( status ) )
		{
		sMemDisconnect( &stream );
		return( status );
		}
	writeAlgoString( &stream,  ( handshakeInfo->requestedServerKeySize > 0 ) ? \
					 CRYPT_PSEUDOALGO_DHE : CRYPT_ALGO_DH );
	writeAlgoString( &stream, handshakeInfo->pubkeyAlgo );

⌨️ 快捷键说明

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