/****************************************************************************
*																			*
*						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

/****************************************************************************
*																			*
*								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 FAR_BSS CRYPT_ALGO_TYPE algoKeyexList[] = {
	CRYPT_PSEUDOALGO_DHE, CRYPT_ALGO_DH, CRYPT_ALGO_NONE };
static const FAR_BSS CRYPT_ALGO_TYPE 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 currently have the 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 };
static const FAR_BSS CRYPT_ALGO_TYPE algoMACList[] = { 
	CRYPT_ALGO_HMAC_SHA, CRYPT_ALGO_HMAC_MD5, CRYPT_ALGO_NONE };
static const FAR_BSS CRYPT_ALGO_TYPE algoStringUserauthentList[] = {
	CRYPT_PSEUDOALGO_PASSWORD, CRYPT_ALGO_NONE };

/* Encode a list of available algorithms */

static int writeAlgoList( STREAM *stream, const CRYPT_ALGO_TYPE *algoList )
	{
	static const FAR_BSS ALGO_STRING_INFO algoStringMapTbl[] = {
		{ "ssh-rsa", CRYPT_ALGO_RSA },
		{ "ssh-dss", CRYPT_ALGO_DSA },
		{ "3des-cbc", CRYPT_ALGO_3DES },
		{ "aes128-cbc", CRYPT_ALGO_AES },
		{ "blowfish-cbc", CRYPT_ALGO_BLOWFISH },
		{ "cast128-cbc", CRYPT_ALGO_CAST },
		{ "idea-cbc", CRYPT_ALGO_IDEA },
		{ "arcfour", CRYPT_ALGO_RC4 },
		{ "diffie-hellman-group-exchange-sha1", CRYPT_PSEUDOALGO_DHE },
		{ "diffie-hellman-group1-sha1", CRYPT_ALGO_DH },
		{ "hmac-sha1", CRYPT_ALGO_HMAC_SHA },
		{ "hmac-md5", CRYPT_ALGO_HMAC_MD5 },
		{ "password", CRYPT_PSEUDOALGO_PASSWORD },
		{ "none", CRYPT_ALGO_NONE },
		};
	const char *availableAlgos[ 16 ];
	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 < 16; 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++ );
			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 */
			}

	/* 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;
	BYTE *keyPtr;
	void *keyexInfoPtr;
	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 */
	length = readPacketSSH2( sessionInfoPtr, 
							 SSH2_MSG_KEXDH_GEX_REQUEST_OLD, 
							 ID_SIZE + UINT32_SIZE );
	if( cryptStatusError( length ) )
		return( length );
	sMemConnect( &stream, sessionInfoPtr->receiveBuffer, length );
	type = sgetc( &stream );
	streamBookmarkSet( &stream, keyexInfoPtr, 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 );
	streamBookmarkComplete( &stream, keyexInfoLength );
	sMemDisconnect( &stream );
	if( cryptStatusError( status ) )
		retExt( sessionInfoPtr, status,
				"Invalid ephemeral DH key data request packet" );
	if( keyLength < MIN_PKCSIZE_BITS || \
		keyLength > bytesToBits( CRYPT_MAX_PKCSIZE ) )
		retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
				"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 */
	openPacketStreamSSH( &stream, sessionInfoPtr, CRYPT_USE_DEFAULT, 
						 SSH2_MSG_KEXDH_GEX_GROUP );
	streamBookmarkSet( &stream, keyPtr, keyPos );
	status = exportAttributeToStream( &stream, 
									  handshakeInfo->iServerCryptContext,
									  CRYPT_IATTRIBUTE_KEY_SSH2,
									  CRYPT_USE_DEFAULT );
	if( cryptStatusOK( status ) )
		{
		keyLength = keyPos;
		streamBookmarkComplete( &stream, keyLength );
		}
	if( cryptStatusError( status ) )
		return( status );
	memmove( keyPtr, keyPtr + offset, keyLength - offset );
	sseek( &stream, keyPos + keyLength );
	status = sendPacketSSH2( sessionInfoPtr, &stream, FALSE );
	sMemDisconnect( &stream );
	return( status );
	}


/* Handle user authentication:

	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 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.

   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;
	BYTE stringBuffer[ CRYPT_MAX_TEXTSIZE + 8 ];
	int length, stringLength, status;

	/* Get the userAuth packet from the client */
	length = readPacketSSH2( sessionInfoPtr, 
							 SSH2_MSG_USERAUTH_REQUEST, 
							 ID_SIZE + sizeofString32( "", 1 ) + \
							 sizeofString32( "", 8 ) + \
							 sizeofString32( "", 4 ) );
	if( cryptStatusError( length ) )
		return( length );
	sMemConnect( &stream, sessionInfoPtr->receiveBuffer, length );
	sgetc( &stream );		/* Skip packet type */

	/* Process the user name */
	status = readString32( &stream, stringBuffer, &stringLength,
						   CRYPT_MAX_TEXTSIZE );
	if( cryptStatusError( status ) || \
		stringLength <= 0 || stringLength > CRYPT_MAX_TEXTSIZE )
		{
		sMemDisconnect( &stream );
		retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
				"Invalid user auth user name" );
		}
	updateSessionAttribute( &sessionInfoPtr->attributeList,
							CRYPT_SESSINFO_USERNAME, stringBuffer, 
							stringLength, CRYPT_MAX_TEXTSIZE,
							ATTR_FLAG_NONE );

	/* Get the service name and authentication method name, either
	   "password" or "none" */
	status = readString32( &stream, stringBuffer, &stringLength,
						   CRYPT_MAX_TEXTSIZE );
	if( cryptStatusError( status ) || \
		stringLength != 14 || memcmp( stringBuffer, "ssh-connection", 14 ) )
		{
		sMemDisconnect( &stream );
		retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
				"Invalid user auth service name" );
		}
	status = readString32( &stream, stringBuffer, &stringLength,
						   CRYPT_MAX_TEXTSIZE );
	if( cryptStatusError( status ) || \
		stringLength <= 0 || stringLength > CRYPT_MAX_TEXTSIZE )
		{
		sMemDisconnect( &stream );
		retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
				"Invalid user auth method name" );
		}
	if( !( ( stringLength == 4 && \
			 !memcmp( stringBuffer, "none", 4 ) ) || \
		   ( stringLength == 8 && \
			 !memcmp( stringBuffer, "password", 8 ) ) ) )
		{
		sMemDisconnect( &stream );
		stringBuffer[ stringLength ] = '\0';
		retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
				"Unknown user auth method name '%s'", 
				sanitiseString( stringBuffer ) );
		}
	sgetc( &stream );	/* Skip boolean flag */

	/* If the client wants a list of supported authentication mechanisms
	   (indicated by sending the method name "none" of length 4), tell them 
	   what we allow and await further input:

		byte	type = SSH2_MSG_USERAUTH_FAILURE
		string	allowed_authent
		boolean	partial_success = FALSE */
	if( stringLength == 4 )
		{
		sMemDisconnect( &stream );
		openPacketStreamSSH( &stream, sessionInfoPtr, CRYPT_USE_DEFAULT,
							 SSH2_MSG_USERAUTH_FAILURE );
		writeAlgoList( &stream, algoStringUserauthentList );
		sputc( &stream, 0 );
		status = sendPacketSSH2( sessionInfoPtr, &stream, FALSE );
		sMemDisconnect( &stream );

		return( CRYPT_OK );
		}

	/* The client has asked for password auth, save the authentication info 
	   for the caller to check */
	status = readString32( &stream, stringBuffer, &stringLength,
						   CRYPT_MAX_TEXTSIZE );
	sMemDisconnect( &stream );
	if( cryptStatusError( status ) || \
		stringLength <= 0 || stringLength > CRYPT_MAX_TEXTSIZE )
		retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
				"Invalid user auth payload" );
	updateSessionAttribute( &sessionInfoPtr->attributeList,
							CRYPT_SESSINFO_PASSWORD, stringBuffer, 
							stringLength, CRYPT_MAX_TEXTSIZE,
							ATTR_FLAG_NONE );

	return( OK_SPECIAL );
	}

/****************************************************************************
*																			*
*							Server-side Connect Functions					*
*																			*
****************************************************************************/

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

static int beginServerHandshake( SESSION_INFO *sessionInfoPtr,
								 SSH_HANDSHAKE_INFO *handshakeInfo )
	{
	static const FAR_BSS ALGO_STRING_INFO algoStringPubkeyRSATbl[] = {
		{ "ssh-rsa", CRYPT_ALGO_RSA },
		{ NULL, CRYPT_ALGO_NONE }
		};
	static const FAR_BSS ALGO_STRING_INFO algoStringPubkeyDSATbl[] = {
		{ "ssh-dss", CRYPT_ALGO_DSA },
		{ NULL, CRYPT_ALGO_NONE }
		};
	STREAM stream;
	void *serverHelloPtr;
	int length, serverHelloLength, clientHelloLength, status;

	/* Get the public-key algorithm that we'll be advertising to the client 
	   and set the algorithm table used for processing the client hello to 
	   only match the one that we're offering */
	status = krnlSendMessage( sessionInfoPtr->privateKey,
							  IMESSAGE_GETATTRIBUTE,
							  &handshakeInfo->pubkeyAlgo,
							  CRYPT_CTXINFO_ALGO );
	if( cryptStatusError( status ) )
		return( status );
	switch( handshakeInfo->pubkeyAlgo )
		{
		case CRYPT_ALGO_RSA:
			handshakeInfo->algoStringPubkeyTbl = algoStringPubkeyRSATbl;
			break;

		case CRYPT_ALGO_DSA:
			handshakeInfo->algoStringPubkeyTbl = algoStringPubkeyDSATbl;
			break;

		default:
			assert( NOTREACHED );
			return( CRYPT_ERROR_NOTAVAIL );
		}

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

	/* Send the server hello packet:

		byte		type = SSH2_MSG_KEXINIT
		byte[16]	cookie
		string		keyex algorithms
		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 while 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.  The client code solves this by waiting
	   for the server hello, choosing known-good algorithms, and then sending
	   the client hello immediately followed by the client key exchange data.
	   Since it waits for the server to speak first, it can choose parameters
	   that are accepted the first time.

	   Unfortunately, this doesn't work if we're the server, since we'd end
	   up waiting for the client to speak first while it waits for us to
	   speak first, so we have to send the server hello in order to prevent
	   deadlock.  This works fine with most clients, which take the same 
	   approach and wait for the server to speak first.  The message flow is 
	   then:

		server hello;
		client hello;
		client keyex;
		server keyex;
	   
	   There are one or two exceptions to this, the worst of which is the 
	   F-Secure client, which has the client speak first choosing as its 
	   preference the incompletely specified "x509v3-sign-dss" format (see 
	   the comment in exchangeServerKeys() below) that we can't use since no-
	   one's quite sure what the format is (this was fixed in mid-2004 when
	   the x509v3-* schemes were removed from the spec, since no-one could
	   figure out what they were.  F-Secure still specifies them, but after 
	   the standard ssh-* schemes).  In this case the message flow is:

		server hello;
		client hello;
		client keyex1;
		client keyex2;
		server keyex;

	   This is handled by having the code that reads the client hello return 
	   OK_SPECIAL to indicate that the next packet should be skipped.  An 
	   alternative (and simpler) strategy would be to always throw away the 
	   F-Secure client's first keyex, since it's using an algorithm choice 
	   that's impossible to use */
	openPacketStreamSSH( &stream, sessionInfoPtr, CRYPT_USE_DEFAULT, 
						 SSH2_MSG_KEXINIT );
	streamBookmarkSetFullPacket( &stream, serverHelloPtr, 
								 serverHelloLength );
	exportAttributeToStream( &stream, SYSTEM_OBJECT_HANDLE,
							 CRYPT_IATTRIBUTE_RANDOM_NONCE, 
							 SSH2_COOKIE_SIZE );
	writeAlgoList( &stream, algoKeyexList );
	writeAlgoString( &stream, handshakeInfo->pubkeyAlgo );
	writeAlgoList( &stream, algoEncrList );
	writeAlgoList( &stream, algoEncrList );
	writeAlgoList( &stream, algoMACList );
	writeAlgoList( &stream, algoMACList );
	writeAlgoString( &stream, CRYPT_PSEUDOALGO_COPR );
	writeAlgoString( &stream, CRYPT_PSEUDOALGO_COPR );
	writeUint32( &stream, 0 );			/* No language tag */
	writeUint32( &stream, 0 );
	sputc( &stream, 0 );				/* Don't try and guess the keyex */
	writeUint32( &stream, 0 );			/* Reserved */
	streamBookmarkComplete( &stream, serverHelloLength );
	status = sendPacketSSH2( sessionInfoPtr, &stream, FALSE );
	sMemDisconnect( &stream );
	if( cryptStatusError( status ) )
		return( status );

	/* While we wait for the client to digest our hello and send back its
	   response, create the context with the DH key */
	status = initDHcontextSSH( &handshakeInfo->iServerCryptContext,
							   &handshakeInfo->serverKeySize, NULL, 0,
							   CRYPT_USE_DEFAULT );
	if( cryptStatusError( status ) )
		return( status );

	/* Process the client hello packet and hash the client and server 
	   hello */
	status = processHelloSSH( sessionInfoPtr, handshakeInfo, 
							  &clientHelloLength, TRUE );
	if( cryptStatusOK( status ) )
		status = hashAsString( handshakeInfo->iExchangeHashcontext,
							   sessionInfoPtr->receiveBuffer, 
							   clientHelloLength );
	else
		if( status == OK_SPECIAL )
			{
			/* There's an incorrectly-guessed keyex following the client 
			   hello, skip it */
			hashAsString( handshakeInfo->iExchangeHashcontext,
						  sessionInfoPtr->receiveBuffer, clientHelloLength );
			status = readPacketSSH2( sessionInfoPtr, 
						( handshakeInfo->requestedServerKeySize > 0 ) ? \
							SSH2_MSG_KEXDH_GEX_INIT : SSH2_MSG_KEXDH_INIT, 
						ID_SIZE + sizeofString32( "", bitsToBytes( MIN_PKCSIZE_BITS ) ) );
			}
	if( !cryptStatusError( status ) )	/* rPSSH2() returns a byte count */
		status = hashAsString( handshakeInfo->iExchangeHashcontext,
							   serverHelloPtr, serverHelloLength );
	if( cryptStatusError( status ) )
		return( status );

	/* If we're using a nonstandard DH key value, negotiate a new key with 
	   the client */
	if( handshakeInfo->requestedServerKeySize > 0 )
		{
		status = processDHE( sessionInfoPtr, handshakeInfo );
		if( cryptStatusError( status ) )
			return( status );
		}

	/* Process the client keyex:

		byte	type = SSH2_MSG_KEXDH_INIT / SSH2_MSG_KEXDH_GEX_INIT
		mpint	y */
	length = readPacketSSH2( sessionInfoPtr, 
					( handshakeInfo->requestedServerKeySize > 0 ) ? \
					  SSH2_MSG_KEXDH_GEX_INIT : SSH2_MSG_KEXDH_INIT, 
					ID_SIZE + sizeofString32( "", bitsToBytes( MIN_PKCSIZE_BITS ) ) );
	if( cryptStatusError( length ) )
		return( length );
	sMemConnect( &stream, sessionInfoPtr->receiveBuffer, length );
	sgetc( &stream );		/* Skip packet type */
	status = readRawObject32( &stream, handshakeInfo->clientKeyexValue,
							  &handshakeInfo->clientKeyexValueLength,
							  sizeof( handshakeInfo->clientKeyexValue ) );
	sMemDisconnect( &stream );
	if( cryptStatusError( status ) || \
		!isValidDHsize( handshakeInfo->clientKeyexValueLength, 
						handshakeInfo->serverKeySize, LENGTH_SIZE ) )
		retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
				"Invalid DH phase 1 keyex value" );
	return( CRYPT_OK );
	}

/* Exchange keys with the client */

static int exchangeServerKeys( SESSION_INFO *sessionInfoPtr,
							   SSH_HANDSHAKE_INFO *handshakeInfo )
	{
	KEYAGREE_PARAMS keyAgreeParams;
	STREAM stream;
	void *keyPtr;
	int keyLength, sigLength, packetOffset, status;

	/* Create the server DH value */
	memset( &keyAgreeParams, 0, sizeof( KEYAGREE_PARAMS ) );
	status = krnlSendMessage( handshakeInfo->iServerCryptContext,
							  IMESSAGE_CTX_ENCRYPT, &keyAgreeParams,
							  sizeof( KEYAGREE_PARAMS ) );
	if( cryptStatusError( status ) )
		return( status );
	sMemOpen( &stream, handshakeInfo->serverKeyexValue, 
			  sizeof( handshakeInfo->serverKeyexValue ) );
	status = writeInteger32( &stream, keyAgreeParams.publicValue,
							 keyAgreeParams.publicValueLen );
	if( cryptStatusOK( status ) )
		handshakeInfo->serverKeyexValueLength = stell( &stream );
	sMemDisconnect( &stream );
	if( cryptStatusError( status ) )
		return( status );

	/* Build the DH phase 2 keyex packet:

		byte		type = SSH2_MSG_KEXDH_REPLY / SSH2_MSG_KEXDH_GEX_REPLY
		string		[ server key/certificate ]
			string	"ssh-rsa"	"ssh-dss"
			mpint	e			p
			mpint	n			q
			mpint				g
			mpint				y
		mpint		y'
		string		[ signature of handshake data ]
			string	"ssh-rsa"	"ssh-dss"
			string	signature	signature
		...

	   The specification also makes provision for using X.509 and PGP keys,
	   but only so far as to say that keys and signatures are in "X.509 DER"
	   and "PGP" formats, neither of which actually explain what it is
	   that's sent or signed (and no-one on the SSH list can agree on what
	   they're supposed to look like), so we can't use either of them */
	openPacketStreamSSH( &stream, sessionInfoPtr, CRYPT_USE_DEFAULT,
						 handshakeInfo->requestedServerKeySize ? \
							SSH2_MSG_KEXDH_GEX_REPLY : SSH2_MSG_KEXDH_REPLY );
	streamBookmarkSet( &stream, keyPtr, keyLength );
	status = exportAttributeToStream( &stream, sessionInfoPtr->privateKey,
									  CRYPT_IATTRIBUTE_KEY_SSH2,
									  CRYPT_USE_DEFAULT );
	if( cryptStatusError( status ) )
		{
		sMemDisconnect( &stream );
		return( status );
		}
	streamBookmarkComplete( &stream, keyLength );
	status = krnlSendMessage( handshakeInfo->iExchangeHashcontext, 
							  IMESSAGE_CTX_HASH, keyPtr, keyLength );
	if( cryptStatusError( status ) )
		{
		sMemDisconnect( &stream );
		return( status );
		}
	swrite( &stream, handshakeInfo->serverKeyexValue, 
			handshakeInfo->serverKeyexValueLength );

	/* Complete phase 2 of the DH key agreement process to obtain the shared
	   secret value */
	status = completeKeyex( sessionInfoPtr, handshakeInfo, TRUE );
	if( cryptStatusError( status ) )
		return( status );

	/* Sign the hash.  The reason for the min() part of the expression is
	   that iCryptCreateSignatureEx() gets suspicious of very large buffer
	   sizes, for example when the user has specified the use of a 1MB send
	   buffer */
	status = iCryptCreateSignatureEx( sMemBufPtr( &stream ), &sigLength,
						min( sMemDataLeft( &stream ), 16384 ),
						CRYPT_IFORMAT_SSH, sessionInfoPtr->privateKey,
						handshakeInfo->iExchangeHashcontext,
						CRYPT_UNUSED, CRYPT_UNUSED );
	krnlSendNotifier( handshakeInfo->iExchangeHashcontext,
					  IMESSAGE_DECREFCOUNT );
	handshakeInfo->iExchangeHashcontext = CRYPT_ERROR;
	if( cryptStatusOK( status ) )
		status = sSkip( &stream, sigLength );
	if( cryptStatusOK( status ) )
		status = wrapPacketSSH2( sessionInfoPtr, &stream, 0 );
	if( cryptStatusError( status ) )
		{
		sMemDisconnect( &stream );
		return( status );
		}

	/* Build our change cipherspec message and send the whole mess through
	   to the client:
		...
		byte	type = SSH2_MSG_NEWKEYS.

	   After this point the write channel is in the secure state */
	packetOffset = continuePacketStreamSSH( &stream, SSH2_MSG_NEWKEYS );
	status = wrapPacketSSH2( sessionInfoPtr, &stream, packetOffset );
	if( cryptStatusOK( status ) )
		status = sendPacketSSH2( sessionInfoPtr, &stream, TRUE );
	sMemDisconnect( &stream );
	if( cryptStatusError( status ) )
		return( status );
	sessionInfoPtr->flags |= SESSION_ISSECURE_WRITE;
	return( CRYPT_OK );
	}

/* Complete the handshake with the client */

static int completeServerHandshake( SESSION_INFO *sessionInfoPtr,
									SSH_HANDSHAKE_INFO *handshakeInfo )
	{
	STREAM stream;
	int length, status;

	/* If this is the first time through, set up the security info and wait
	   for the first part of the authentication */
	if( !( sessionInfoPtr->flags & SESSION_PARTIALOPEN ) )
		{
		BYTE stringBuffer[ CRYPT_MAX_TEXTSIZE + 8 ];
		int stringLength;

		/* Set up the security information required for the session */
		status = initSecurityInfo( sessionInfoPtr, handshakeInfo );
		if( cryptStatusError( status ) )
			return( status );

		/* Wait for the client's change cipherspec message.  From this point
		   on the read channel is in the secure state */
		status = readPacketSSH2( sessionInfoPtr, SSH2_MSG_NEWKEYS, ID_SIZE );
		if( cryptStatusError( status ) )
			return( status );
		sessionInfoPtr->flags |= SESSION_ISSECURE_READ;

		/* Wait for the client's authentication packets.  For some reason 
		   SSHv2 requires the use of two authentication messages, an "I'm 
		   about to authenticate" packet and an "I'm authenticating" packet.  
		   First we  handle the "I'm about to authenticate":

			byte	type = SSH2_MSG_SERVICE_REQUEST
			string	service_name = "ssh-userauth"

			byte	type = SSH2_MSG_SERVICE_ACCEPT
			string	service_name = "ssh-userauth" */
		length = readPacketSSH2( sessionInfoPtr, SSH2_MSG_SERVICE_REQUEST, 
								 ID_SIZE + sizeofString32( "", 8 ) );
		if( cryptStatusError( length ) )
			return( length );
		sMemConnect( &stream, sessionInfoPtr->receiveBuffer, length );
		sgetc( &stream );		/* Skip packet type */
		status = readString32( &stream, stringBuffer, &stringLength,
							   CRYPT_MAX_TEXTSIZE );
		sMemDisconnect( &stream );
		if( cryptStatusError( status ) || \
			stringLength != 12 || memcmp( stringBuffer, "ssh-userauth", 12 ) )
			retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
						"Invalid service request packet" );
		openPacketStreamSSH( &stream, sessionInfoPtr, CRYPT_USE_DEFAULT, 
							 SSH2_MSG_SERVICE_ACCEPT );
		writeString32( &stream, "ssh-userauth", 0 );
		status = sendPacketSSH2( sessionInfoPtr, &stream, FALSE );
		sMemDisconnect( &stream );
		if( cryptStatusError( status ) )
			return( status );
		}

	/* Wait for the second part of the authentication, optionally letting the
	   caller determine whether to allow the authentication or not */
	do
		{
		SSH_INFO *sshInfo = sessionInfoPtr->sessionSSH;

		/* If we don't have authentication info ready to act upon,
		   read it now */
		if( !sshInfo->authRead )
			{
			int retryCount;

			/* Since the userAuth negotiation can (in theory) go on 
			   indefinitely, we limit it to three iterations to avoid 
			   potential DoS problems */
			for( retryCount = 0; status != OK_SPECIAL && \
								 retryCount < 3; retryCount++ )
				{
				status = processUserAuth( sessionInfoPtr, handshakeInfo );
				if( cryptStatusError( status ) && status != OK_SPECIAL )
					return( status );
				}
			if( retryCount >= 3 )
				retExt( sessionInfoPtr, CRYPT_ERROR_PERMISSION,
						"Too many iterations of negotiation during user "
						"auth request processing" );

			/* We got a userAuth request, if the caller will handle it, let
			   them know that they have to react on it */
			sshInfo->authRead = TRUE;
			if( sessionInfoPtr->authResponse == CRYPT_UNUSED )
				return( CRYPT_ENVELOPE_RESOURCE );
			}

		/* Acknowledge the authentication:
	
			byte	type = SSH2_MSG_USERAUTH_SUCCESS / 
						   SSH2_MSG_USERAUTH_FAILURE */
		openPacketStreamSSH( &stream, sessionInfoPtr, CRYPT_USE_DEFAULT, 
							 sessionInfoPtr->authResponse ? \
								SSH2_MSG_USERAUTH_SUCCESS : \
								SSH2_MSG_USERAUTH_FAILURE );
		status = sendPacketSSH2( sessionInfoPtr, &stream, FALSE );
		sMemDisconnect( &stream );
		if( cryptStatusError( status ) )
			return( status );

		/* If the caller denied the authentication, go back to asking what 
		   to do on the next authentication attempt */
		if( sessionInfoPtr->authResponse == FALSE )
			sessionInfoPtr->authResponse = CRYPT_UNUSED;
		sshInfo->authRead = FALSE;
		}
	while( sessionInfoPtr->authResponse != TRUE );

	/* Handle the channel open */
	length = readPacketSSH2( sessionInfoPtr, SSH2_MSG_CHANNEL_OPEN, 
							 ID_SIZE + sizeofString32( "", 4 ) + \
							 UINT32_SIZE + UINT32_SIZE + UINT32_SIZE );
	if( cryptStatusError( length ) )
		return( length );
	sMemConnect( &stream, sessionInfoPtr->receiveBuffer, length );
	assert( sPeek( &stream ) == SSH2_MSG_CHANNEL_OPEN );
	sgetc( &stream );		/* Skip packet type */
	status = processChannelOpen( sessionInfoPtr, &stream );
	sMemDisconnect( &stream );
#if 1
	return( status );
#else	/* If we handle the following inline as part of the general read code 
		   it requires that the user try and read some data (with a non-zero 
		   timeout) right after the connect completes.  Because it's awkward 
		   to have to rely on this, we provide optional code to explicitly 
		   clear the pipe here.  This code stops as soon as the first data 
		   channel-opening request is received, with further requests being 
		   handled inline as part of the standard data-read handling.  The
		   reason why this isn't enabled by default is that it's possible to
		   encounter a client that doesn't send anything beyond the initial
		   channel open, which means that we'd hang around waiting for a 
		   control message until we time out */
	if( cryptStatusError( status ) )
		return( status );

	/* Process any further junk that the caller may throw at us until we get 
	   a request that we can handle, indicated by an OK_SPECIAL response */
	do
		{
		status = length = readPacketSSH2( sessionInfoPtr, 
										  SSH2_MSG_SPECIAL_REQUEST, 8 );
		if( !cryptStatusError( status ) )
			{
			sMemConnect( &stream, sessionInfoPtr->receiveBuffer, length );
			sgetc( &stream );
			status = processChannelControlMessage( sessionInfoPtr, &stream );
			sMemDisconnect( &stream );
			}
		}
	while( cryptStatusOK( status ) );
	return( ( status == OK_SPECIAL ) ? CRYPT_OK : status );
#endif /* 1 */
	}

/****************************************************************************
*																			*
*							Session Access Routines							*
*																			*
****************************************************************************/

void initSSH2serverProcessing( SESSION_INFO *sessionInfoPtr,
							   SSH_HANDSHAKE_INFO *handshakeInfo )
	{
	UNUSED( sessionInfoPtr );

	handshakeInfo->beginHandshake = beginServerHandshake;
	handshakeInfo->exchangeKeys = exchangeServerKeys;
	handshakeInfo->completeHandshake = completeServerHandshake;
	}
#endif /* USE_SSH2 */
