📄 ssh.c
字号:
{
krnlSendMessage( iHashContext, RESOURCE_IMESSAGE_CTX_HASH,
buffer, LENGTH_SIZE );
status = krnlSendMessage( iHashContext, RESOURCE_IMESSAGE_CTX_HASH,
( void * ) data, dataLength );
}
zeroise( buffer, 64 );
return( status );
}
static int hashAsMPI( const CRYPT_CONTEXT iHashContext,
const BYTE *data, const int dataLength )
{
BYTE buffer[ 8 ], *bufPtr = buffer;
const int length = ( data[ 0 ] & 0x80 ) ? dataLength + 1 : dataLength;
int headerLength, status;
/* Prepend the MPI length to the data and hash it. Since this is
probably sensitive data, we don't take a local copy but hash it
in two parts */
mputBLong( bufPtr, length );
if( data[ 0 ] & 0x80 )
*bufPtr++ = 0;
headerLength = ( int ) ( bufPtr - buffer );
krnlSendMessage( iHashContext, RESOURCE_IMESSAGE_CTX_HASH,
buffer, headerLength );
status = krnlSendMessage( iHashContext, RESOURCE_IMESSAGE_CTX_HASH,
( void * ) data, dataLength );
return( status );
}
/* Complete the hashing necessary to generate a cryptovariable and send it
to a context */
static int loadCryptovariable( const CRYPT_CONTEXT iCryptContext,
const CRYPT_ATTRIBUTE_TYPE attribute,
const int attributeSize, HASHFUNCTION hashFunction,
void *initialHashInfo, const BYTE *nonce,
const BYTE *data, const int dataLen )
{
RESOURCE_DATA msgData;
BYTE hashInfo[ MAX_HASHINFO_SIZE ], buffer[ CRYPT_MAX_KEYSIZE ];
int status;
/* Complete the hashing */
memcpy( hashInfo, initialHashInfo, MAX_HASHINFO_SIZE );
if( nonce != NULL )
hashFunction( hashInfo, NULL, nonce, 1, HASH_CONTINUE );
hashFunction( hashInfo, buffer, data, dataLen, HASH_END );
zeroise( hashInfo, MAX_HASHINFO_SIZE );
/* If we need more data than the hashing will provide in one go,
generate a second block as:
hash( shared_secret || exchange_hash || data )
where the shared secret and exchange hash are present as the
precomputed data and the data is the output of the initial
hashing */
if( attributeSize > 20 )
{
memcpy( hashInfo, initialHashInfo, MAX_HASHINFO_SIZE );
hashFunction( hashInfo, buffer + 20, buffer, 20, HASH_END );
}
/* Send the data to the context */
setResourceData( &msgData, buffer, attributeSize );
status = krnlSendMessage( iCryptContext,
RESOURCE_IMESSAGE_SETATTRIBUTE_S,
&msgData, attribute );
zeroise( buffer, CRYPT_MAX_KEYSIZE );
return( status );
}
/* Set up the security information required for the session */
static int initSecurityInfo2( SESSION_INFO *sessionInfoPtr,
SSH_HANDSHAKE_INFO *handshakeInfo )
{
HASHFUNCTION hashFunction;
BYTE initialHashInfo[ MAX_HASHINFO_SIZE ];
BYTE header[ 8 ], *headerPtr = header;
const int mpiLength = ( handshakeInfo->secretValue[ 0 ] & 0x80 ) ? \
handshakeInfo->secretValueLength + 1 : \
handshakeInfo->secretValueLength;
int hashSize, keySize, ivSize, status;
/* Get the hash algorithm information and pre-hash the shared secret and
exchange hash, which are reused for all cryptovariables. The overall
hashing is:
hash( MPI( shared_secret ) || exchange_hash || \
nonce || exchange_hash )
Before we can hash the shared secret we have to convert it into MPI
form, which we do by generating a pseudo-header and hashing that
separately. The nonce is "A", "B", "C", ... */
getHashParameters( CRYPT_ALGO_SHA, &hashFunction, &hashSize );
mputBLong( headerPtr, mpiLength );
if( handshakeInfo->secretValue[ 0 ] & 0x80 )
*headerPtr++ = 0;
hashFunction( initialHashInfo, NULL, header, headerPtr - header,
HASH_START );
hashFunction( initialHashInfo, NULL, handshakeInfo->secretValue,
handshakeInfo->secretValueLength, HASH_CONTINUE );
hashFunction( initialHashInfo, NULL, handshakeInfo->sessionID,
handshakeInfo->sessionIDlength, HASH_CONTINUE );
/* Create the security contexts required for the session */
status = initSecurityContexts( sessionInfoPtr );
if( cryptStatusError( status ) )
return( status );
if( sessionInfoPtr->cryptAlgo == CRYPT_ALGO_BLOWFISH )
/* Blowfish has a variable-length key so we have to explicitly
specify its length */
keySize = SSH2_FIXED_KEY_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 cryptovariables. The order is:
client_write_iv, server_write_iv
client_write_key, server_write_key
client_write_mac, server_write_mac
Although HMAC has a variable-length key and should therefore follow
the SSH2_FIXED_KEY_SIZE rule, the key size is in fact set to the
HMAC block size (the ssh.com SSHv2 software up to to version
v2.3.x in fact used the fixed-size key, but later versions (and
everything else) used the HMAC block size for the key size) */
status = loadCryptovariable( sessionInfoPtr->iCryptOutContext,
CRYPT_CTXINFO_IV, ivSize,
hashFunction, initialHashInfo, "A",
handshakeInfo->sessionID,
handshakeInfo->sessionIDlength );
if( cryptStatusOK( status ) )
status = loadCryptovariable( sessionInfoPtr->iCryptInContext,
CRYPT_CTXINFO_IV, ivSize,
hashFunction, initialHashInfo, "B",
handshakeInfo->sessionID,
handshakeInfo->sessionIDlength );
if( cryptStatusOK( status ) )
status = loadCryptovariable( sessionInfoPtr->iCryptOutContext,
CRYPT_CTXINFO_KEY, keySize,
hashFunction, initialHashInfo, "C",
handshakeInfo->sessionID,
handshakeInfo->sessionIDlength );
if( cryptStatusOK( status ) )
status = loadCryptovariable( sessionInfoPtr->iCryptInContext,
CRYPT_CTXINFO_KEY, keySize,
hashFunction, initialHashInfo, "D",
handshakeInfo->sessionID,
handshakeInfo->sessionIDlength );
if( cryptStatusOK( status ) )
status = loadCryptovariable( sessionInfoPtr->iAuthOutContext,
CRYPT_CTXINFO_KEY,
sessionInfoPtr->authBlocksize,
hashFunction, initialHashInfo, "E",
handshakeInfo->sessionID,
handshakeInfo->sessionIDlength );
if( cryptStatusOK( status ) )
status = loadCryptovariable( sessionInfoPtr->iAuthInContext,
CRYPT_CTXINFO_KEY,
sessionInfoPtr->authBlocksize,
hashFunction, initialHashInfo, "F",
handshakeInfo->sessionID,
handshakeInfo->sessionIDlength );
return( status );
}
/* Read an SSHv2 packet */
typedef enum { MAC_START, MAC_END, MAC_ALL } MAC_TYPE;
static BOOLEAN macPayload( const CRYPT_CONTEXT iMacContext,
const long seqNo, const BYTE *data,
const int dataLength, const int packetDataLength,
const MAC_TYPE macType )
{
int status;
/* MAC the data and compare the result to the stored MAC:
HMAC( seqNo || payload )
During the hansdshake process we have the entire packet at hand and
can process it at once, when we're processing payload data we have
to process the header separately in order to determine how much more
we have to read, so we have to MAC the packet in two parts */
if( macType == MAC_START || macType == MAC_ALL )
{
BYTE buffer[ 16 ], *bufPtr = buffer;
int length = ( macType == MAC_ALL ) ? dataLength : packetDataLength;
assert( ( macType == MAC_ALL && packetDataLength == 0 ) || \
( macType == MAC_START && packetDataLength > dataLength ) );
/* Since the payload had the length stripped during the speculative
read, we have to reconstruct it and hash it separately before we
hash the data. If we're doing the hash in parts, the amount of
data being hashed won't match the overall length so the caller
needs to supply the overall packet as well as current data
lengths */
mputBLong( bufPtr, seqNo );
mputBLong( bufPtr, length );
krnlSendMessage( iMacContext, RESOURCE_IMESSAGE_DELETEATTRIBUTE, NULL,
CRYPT_CTXINFO_HASHVALUE );
krnlSendMessage( iMacContext, RESOURCE_IMESSAGE_CTX_HASH,
buffer, LENGTH_SIZE + LENGTH_SIZE );
}
krnlSendMessage( iMacContext, RESOURCE_IMESSAGE_CTX_HASH,
( void * ) data, dataLength );
if( macType == MAC_END || macType == MAC_ALL )
{
RESOURCE_DATA msgData;
BYTE macBuffer[ CRYPT_MAX_HASHSIZE ];
krnlSendMessage( iMacContext, RESOURCE_IMESSAGE_CTX_HASH, "", 0 );
setResourceData( &msgData, macBuffer, CRYPT_MAX_HASHSIZE );
status = krnlSendMessage( iMacContext, RESOURCE_IMESSAGE_GETATTRIBUTE_S,
&msgData, CRYPT_CTXINFO_HASHVALUE );
if( cryptStatusError( status ) || \
memcmp( macBuffer, data + dataLength, msgData.length ) )
return( FALSE );
}
return( TRUE );
}
static int getDisconnectInfo2( SESSION_INFO *sessionInfoPtr, BYTE *bufPtr )
{
int length;
/* Server is disconnecting, find out why */
bufPtr++; /* Skip packet type */
mgetBLong( bufPtr ); /* Reason code */
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 readPacket2( SESSION_INFO *sessionInfoPtr, int expectedType )
{
BYTE *dataStartPtr;
long length;
int padLength = 0, packetType;
/* Alongside the expected packets the server can send us all sorts of nop
messages, ranging from explicit nops (SSH2_MSG_IGNORE) through to
general chattiness (SSH2_MSG_DEBUG, SSH2_MSG_USERAUTH_BANNER).
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 SSHv2 packet header:
uint32 length
byte padLen
[ byte type - checked but not removed ]
byte[] data
byte[] padding
byte[] MAC
SSHv2 encrypts everything (including the length) so we need to
speculatively read ahead for the minimum packet size and decrypt
that in order to figure out what to do */
status = sread( &sessionInfoPtr->stream,
sessionInfoPtr->receiveBuffer, MIN_PACKET_SIZE );
if( cryptStatusError( status ) )
{
sNetGetErrorInfo( &sessionInfoPtr->stream,
sessionInfoPtr->errorMessage,
&sessionInfoPtr->errorCode );
return( status );
}
if( sessionInfoPtr->flags & SESSION_ISSECURE )
{
status = krnlSendMessage( sessionInfoPtr->iCryptInContext,
RESOURCE_IMESSAGE_CTX_DECRYPT,
sessionInfoPtr->receiveBuffer,
MIN_PACKET_SIZE );
if( cryptStatusError( status ) )
return( status );
}
length = mgetBLong( bufPtr );
if( sessionInfoPtr->flags & SESSION_ISSECURE )
/* The MAC size isn't included in the packet length */
length += sessionInfoPtr->authBlocksize;
if( length < MIN_PACKET_SIZE - LENGTH_SIZE || \
length > sessionInfoPtr->receiveBufSize - ( MIN_PACKET_SIZE + 8 ) )
return( CRYPT_ERROR_BADDATA );
memmove( sessionInfoPtr->receiveBuffer,
sessionInfoPtr->receiveBuffer + LENGTH_SIZE,
PACKET_REMAINDER_SIZE );
if( length > PACKET_REMAINDER_SIZE ) /* Change cipherspec = 0-len */
status = \
sread( &sessionInfoPtr->stream,
sessionInfoPtr->receiveBuffer + PACKET_REMAINDER_SIZE,
length - PACKET_REMAINDER_SIZE );
if( cryptStatusError( status ) )
{
sNetGetErrorInfo( &sessionInfoPtr->stream,
sessionInfoPtr->errorMessage,
&sessionInfoPtr->errorCode );
return( status );
}
if( sessionInfoPtr->flags & SESSION_ISSECURE )
{
/* Decrypt the remainder of the packet except for the MAC */
status = krnlSendMessage( sessionInfoPtr->iCryptInContext,
RESOURCE_IMESSAGE_CTX_DECRYPT,
sessionInfoPtr->receiveBuffer + PACKET_REMAINDER_SIZE,
length - ( PACKET_REMAINDER_SIZE + \
sessionInfoPtr->authBlocksize ) );
if( cryptStatusError( status ) )
return( status );
/* MAC the decrypted payload */
if( !macPayload( sessionInfoPtr->iAuthInContext,
sessionInfoPtr->readSeqNo, sessionInfoPtr->receiveBuffer,
length - sessionInfoPtr->authBlocksize, 0, MAC_ALL ) )
/* If we're expecting a service accept packet after a change
cipherspec 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 == SSH2_MSG_SERVICE_ACCEPT ) ?
CRYPT_ERROR_WRONGKEY : CRYPT_ERROR
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -