📄 ssh.c
字号:
case SSH_CIPHER_BLOWFISH:
if( algoAvailable( CRYPT_ALGO_BLOWFISH ) )
return( CRYPT_ALGO_BLOWFISH );
break;
}
return( CRYPT_ALGO_NONE );
}
static long getAlgorithmMask( void )
{
long value = CRYPT_ALGO_DES;
if( algoAvailable( CRYPT_ALGO_BLOWFISH ) )
value |= 1 << SSH_CIPHER_BLOWFISH;
if( algoAvailable( CRYPT_ALGO_IDEA ) )
value |= 1 << SSH_CIPHER_IDEA;
if( algoAvailable( CRYPT_ALGO_RC4 ) )
value |= 1 << SSH_CIPHER_RC4;
return( value );
}
/* Generate an SSH session ID */
static void generateSessionID( SSH_HANDSHAKE_INFO *handshakeInfo )
{
HASHFUNCTION hashFunction;
BYTE hashInfo[ MAX_HASHINFO_SIZE ];
int hashSize;
/* Get the hash algorithm information and hash the server key modulus,
host key modulus, and cookie. The SSH documentation and source code
are quite confusing on this issue, giving the key components to be
hashed multiple names (server key, host key, session key, public key,
etc etc). The correct order is:
hash( host modulus || server modulus || cookie ) */
getHashParameters( CRYPT_ALGO_MD5, &hashFunction, &hashSize );
hashFunction( hashInfo, NULL, handshakeInfo->hostModulus,
handshakeInfo->hostModulusLength, HASH_START );
hashFunction( hashInfo, NULL, handshakeInfo->serverModulus,
handshakeInfo->serverModulusLength, HASH_CONTINUE );
hashFunction( hashInfo, handshakeInfo->sessionID,
handshakeInfo->cookie, SSH_COOKIE_SIZE, HASH_END );
handshakeInfo->sessionIDlength = SSH_SESSIONID_SIZE;
}
/* Generate a response to an RSA authentication challenge */
static void generateChallengeResponse( BYTE *response,
const SSH_HANDSHAKE_INFO *handshakeInfo,
const BYTE *challenge )
{
HASHFUNCTION hashFunction;
BYTE hashInfo[ MAX_HASHINFO_SIZE ];
int hashSize;
/* Hash the session ID and challenge:
hash( sessionID || challenge ) */
getHashParameters( CRYPT_ALGO_MD5, &hashFunction, &hashSize );
hashFunction( hashInfo, NULL, ( BYTE * ) handshakeInfo->sessionID,
handshakeInfo->sessionIDlength, HASH_START );
hashFunction( hashInfo, response, ( BYTE * ) challenge,
SSH_CHALLENGE_SIZE, HASH_END );
}
/* Process the public key data. The preceding key length value isn't useful
because it contains the nominal key size in bits rather than the size of
the following data, so we have to poke into the data to find out how much
there is. In addition we need to take a copy of the key modulus since
it's needed later for calculating the session ID */
static int processPublickeyData( const void *data,
SSH_HANDSHAKE_INFO *handshakeInfo,
const BOOLEAN isServerKey )
{
BYTE *dataPtr = ( BYTE * ) data;
int eLength, nLength;
eLength = mgetBWord( dataPtr );
dataPtr += bitsToBytes( eLength );
nLength = mgetBWord( dataPtr );
nLength = bitsToBytes( nLength );
if( isServerKey )
{
memcpy( handshakeInfo->serverModulus, dataPtr, nLength );
handshakeInfo->serverModulusLength = nLength;
}
else
{
memcpy( handshakeInfo->hostModulus, dataPtr, nLength );
handshakeInfo->hostModulusLength = nLength;
}
return( MPI_LENGTH_SIZE + bitsToBytes( eLength ) + \
MPI_LENGTH_SIZE + nLength );
}
/* Set up the security information required for the session */
static int initSecurityInfo1( SESSION_INFO *sessionInfoPtr,
SSH_HANDSHAKE_INFO *handshakeInfo )
{
RESOURCE_DATA msgData;
int keySize, ivSize, status;
/* Create the security contexts required for the session */
status = initSecurityContexts( sessionInfoPtr );
if( cryptStatusError( status ) )
return( status );
if( sessionInfoPtr->cryptAlgo == CRYPT_ALGO_BLOWFISH )
/* For Blowfish the session key size doesn't match the default
Blowfish key size so we explicitly specify its length */
keySize = SSH_SECRET_SIZE;
else
krnlSendMessage( sessionInfoPtr->iCryptInContext,
RESOURCE_IMESSAGE_GETATTRIBUTE, &keySize,
CRYPT_CTXINFO_KEYSIZE );
if( krnlSendMessage( sessionInfoPtr->iCryptInContext,
RESOURCE_IMESSAGE_GETATTRIBUTE, &ivSize,
CRYPT_CTXINFO_IVSIZE ) == CRYPT_ERROR_NOTAVAIL )
/* It's a stream cipher */
ivSize = 0;
/* Load the keys. For RC4, which is IV-less, the session key is split
into two parts, with the first part being the receive key and the
second part being the send key. For other algorithms, the entire
session key is used for both send and receive contexts */
setResourceData( &msgData, ( sessionInfoPtr->cryptAlgo == CRYPT_ALGO_RC4 ) ? \
handshakeInfo->secretValue + 16 : handshakeInfo->secretValue,
keySize );
status = krnlSendMessage( sessionInfoPtr->iCryptOutContext,
RESOURCE_IMESSAGE_SETATTRIBUTE_S,
&msgData, CRYPT_CTXINFO_KEY );
if( cryptStatusOK( status ) )
{
setResourceData( &msgData, handshakeInfo->secretValue, keySize );
status = krnlSendMessage( sessionInfoPtr->iCryptInContext,
RESOURCE_IMESSAGE_SETATTRIBUTE_S,
&msgData, CRYPT_CTXINFO_KEY );
}
if( cryptStatusOK( status ) && ivSize )
{
static const char iv[ CRYPT_MAX_IVSIZE ] = { 0 };
setResourceData( &msgData, ( void * ) iv, ivSize );
krnlSendMessage( sessionInfoPtr->iCryptOutContext,
RESOURCE_IMESSAGE_SETATTRIBUTE_S,
&msgData, CRYPT_CTXINFO_IV );
setResourceData( &msgData, ( void * ) iv, ivSize );
krnlSendMessage( sessionInfoPtr->iCryptInContext,
RESOURCE_IMESSAGE_SETATTRIBUTE_S,
&msgData, CRYPT_CTXINFO_IV );
}
if( cryptStatusError( status ) )
return( status );
/* If we're talking to a cryptlib peer, set up the MAC context which is
used instead of a CRC32. The key we use for this is taken from the
end of the SSH secret data, which isn't used for any cipher except
Blowfish */
if( sessionInfoPtr->flags & SESSION_ISCRYPTLIB )
{
setResourceData( &msgData,
handshakeInfo->secretValue + ( SSH_SECRET_SIZE - 16 ), 16 );
status = krnlSendMessage( sessionInfoPtr->iAuthInContext,
RESOURCE_IMESSAGE_SETATTRIBUTE_S,
&msgData, CRYPT_CTXINFO_KEY );
if( cryptStatusError( status ) )
return( status );
}
/* We've set up the security info, from now on all data is encrypted */
sessionInfoPtr->flags |= SESSION_ISSECURE;
return( CRYPT_OK );
}
/* Read an SSH packet */
static int decryptPayload( SESSION_INFO *sessionInfoPtr, BYTE *buffer,
const int length )
{
int status;
/* Decrypt the payload with handling for SSH's Blowfish endianness bug */
if( sessionInfoPtr->cryptAlgo == CRYPT_ALGO_BLOWFISH )
longReverse( ( LONG * ) buffer, length );
status = krnlSendMessage( sessionInfoPtr->iCryptInContext,
RESOURCE_IMESSAGE_CTX_DECRYPT,
buffer, length );
if( sessionInfoPtr->cryptAlgo == CRYPT_ALGO_BLOWFISH )
longReverse( ( LONG * ) buffer, length );
return( status );
}
static BOOLEAN checksumPayload( SESSION_INFO *sessionInfoPtr,
const BYTE *buffer, const int length )
{
const int dataLength = length - CRC_SIZE; /* CRC isn't part of payload */
BYTE *bufPtr = ( BYTE * ) buffer + dataLength;
LONG crc32, storedCrc32;
/* Calculate the CRC-32 over the padding, type, and data and make sure
it matches the transmitted value */
if( ( sessionInfoPtr->flags & ( SESSION_ISCRYPTLIB | SESSION_ISSECURE ) ) == \
( SESSION_ISCRYPTLIB | SESSION_ISSECURE ) )
crc32 = calculateTruncatedMAC( sessionInfoPtr->iAuthInContext,
buffer, dataLength );
else
crc32 = calculateCRC( buffer, dataLength );
storedCrc32 = mgetBLong( bufPtr );
return( ( crc32 == storedCrc32 ) ? TRUE : FALSE );
}
static int getDisconnectInfo1( SESSION_INFO *sessionInfoPtr, BYTE *bufPtr )
{
int length;
/* Server is disconnecting, find out why */
length = mgetBLong( bufPtr );
if( length > MAX_ERRMSG_SIZE - 30 )
return( CRYPT_ERROR_OVERFLOW );
strcpy( sessionInfoPtr->errorMessage, "Received SSH server message: " );
memcpy( sessionInfoPtr->errorMessage + 29, bufPtr, length );
sessionInfoPtr->errorMessage[ 29 + length ] = '\0';
return( CRYPT_ERROR_READ );
}
static int readPacket1( SESSION_INFO *sessionInfoPtr, int expectedType )
{
long length;
int padLength, packetType;
/* Alongside the expected packets the server can send us all sorts of nop
messages, ranging from explicit nops (SSH_MSG_IGNORE) through to
general chattiness (SSH_MSG_DEBUG). Because we can receive any
quantity of these at any time, we have to run the receive code in a
loop to strip them out */
do
{
BYTE *bufPtr = sessionInfoPtr->receiveBuffer;
int status;
/* Read the SSHv1 packet header:
uint32 length
byte[] padding
byte type
byte[] data
uint32 crc32
The padding length is implicitly calculated as
8 - ( length & 7 ) bytes, and the CRC is calculated over the
padding, type, and data */
status = sread( &sessionInfoPtr->stream,
sessionInfoPtr->receiveBuffer, LENGTH_SIZE );
if( cryptStatusError( status ) )
{
sNetGetErrorInfo( &sessionInfoPtr->stream,
sessionInfoPtr->errorMessage,
&sessionInfoPtr->errorCode );
return( status );
}
length = mgetBLong( bufPtr );
if( length < SSH_HEADER_SIZE || \
length > sessionInfoPtr->receiveBufSize - 8 )
return( CRYPT_ERROR_BADDATA );
padLength = 8 - ( length & 7 );
status = sread( &sessionInfoPtr->stream,
sessionInfoPtr->receiveBuffer, padLength + length );
if( cryptStatusError( status ) )
{
sNetGetErrorInfo( &sessionInfoPtr->stream,
sessionInfoPtr->errorMessage,
&sessionInfoPtr->errorCode );
return( status );
}
if( sessionInfoPtr->flags & SESSION_ISSECURE )
{
status = decryptPayload( sessionInfoPtr,
sessionInfoPtr->receiveBuffer,
padLength + length );
if( cryptStatusError( status ) )
return( status );
}
if( !checksumPayload( sessionInfoPtr, sessionInfoPtr->receiveBuffer,
padLength + length ) )
/* If we're expecting a success packet after a key exchange or an
immediate post-key-exchange packet and don't get it then it's
more likely that the problem is due to the wrong key being
used than data corruption, so we return a wrong key error
instead of bad data */
return( ( expectedType == SSH_SMSG_SUCCESS ) ?
CRYPT_ERROR_WRONGKEY : CRYPT_ERROR_BADDATA );
packetType = sessionInfoPtr->receiveBuffer[ padLength ];
}
while( packetType == SSH_MSG_IGNORE || packetType == SSH_MSG_DEBUG );
/* Make sure we either got what we asked for or one of the allowed
special-case packets */
if( packetType == SSH_MSG_DISCONNECT )
return( getDisconnectInfo1( sessionInfoPtr,
sessionInfoPtr->receiveBuffer + padLength + ID_SIZE ) );
if( expectedType == SSH_MSG_SPECIAL_USEROPT )
{
/* Sending an SSH_CMSG_USER can result in an SSH_SMSG_FAILURE if the
user needs some form of authentiction to log on, so we have to
filter this and convert it into a TRUE/FALSE value to let the
caller know whether they have to send a password or not */
if( packetType == SSH_SMSG_SUCCESS )
return( FALSE );
if( packetType == SSH_SMSG_FAILURE )
return( TRUE );
}
if( expectedType == SSH_MSG_SPECIAL_PWOPT )
{
/* If we're reading a response to a password then getting a failure
response is valid (even if it's not what we're expecting) since
it's an indication that an incorrect password was used rather than
that there was some general type of failure */
if( packetType == SSH_SMSG_FAILURE )
return( CRYPT_ERROR_WRONGKEY );
expectedType = SSH_SMSG_SUCCESS;
}
if( expectedType == SSH_MSG_SPECIAL_RSAOPT )
{
/* If we're reading a response to an RSA key ID then getting a
failure response is valid (even if it's not what we're expecting)
since it's an indication that an incorrect key was used rather
than that there was some general type of failure */
if( packetType == SSH_SMSG_FAILURE )
return( CRYPT_ERROR_WRONGKEY );
expectedType = SSH_SMSG_AUTH_RSA_CHALLENGE;
}
if( expectedType == SSH_MSG_SPECIAL_ANY )
{
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -