📄 ssh1.c
字号:
}
if( expectedType == SSH1_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 == SSH1_SMSG_FAILURE )
retExt( sessionInfoPtr, CRYPT_ERROR_WRONGKEY,
"Server indicated incorrect password was used" );
expectedType = SSH1_SMSG_SUCCESS;
}
if( expectedType == SSH1_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 == SSH1_SMSG_FAILURE )
retExt( sessionInfoPtr, CRYPT_ERROR_WRONGKEY,
"Server indicated incorrect RSA key was used" );
expectedType = SSH1_SMSG_AUTH_RSA_CHALLENGE;
}
if( expectedType == SSH1_MSG_SPECIAL_ANY )
{
/* If we're expecting any kind of data, save the type at the start
of the buffer. The length increment means that we move one byte
of data too much down, but this isn't a big deal since the
returned data length excludes it, and it's not processed anyway -
this packet pseudo-type is only used to eat client or server
chattiness at the end of the handshake */
*bufPtr++ = packetType;
length += ID_SIZE;
}
else
if( packetType != expectedType )
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Invalid packet type 0x%02X, expected 0x%02X",
packetType, expectedType );
/* Move the data down in the buffer to get rid of the padding. This
isn't as inefficient as it seems since it's only used for short
handshake messages */
memmove( bufPtr, sessionInfoPtr->receiveBuffer + padLength + ID_SIZE,
length );
return( length );
}
/* Send an SSHv1 packet. SSHv1 uses an awkward variable-length padding at
the start, when we're sending short control packets we can pre-calculate
the data start position or memmove() it into place, with longer payload
data quantities we can no longer easily do this so we build the header
backwards from the start of the data in the buffer. Because of this we
perform all operations on the data relative to a variable start position
given by the parameter delta */
static int sendPacketSsh1( SESSION_INFO *sessionInfoPtr,
const int packetType, const int dataLength,
const int delta )
{
MESSAGE_DATA msgData;
BYTE *bufStartPtr = sessionInfoPtr->sendBuffer + \
( ( delta != CRYPT_UNUSED ) ? delta : 0 );
BYTE *bufPtr = bufStartPtr;
unsigned long crc32;
const int length = ID_SIZE + dataLength + SSH1_CRC_SIZE;
const int padLength = getPadLength( dataLength );
int status;
/* Add the SSH packet header:
uint32 length
byte[] padding, 8 - ( length & 7 ) bytes
byte type
byte[] data
uint32 crc32 - Calculated over padding, type, and data */
mputLong( bufPtr, ( long ) length );
setMessageData( &msgData, bufPtr, padLength );
krnlSendMessage( SYSTEM_OBJECT_HANDLE, IMESSAGE_GETATTRIBUTE_S,
&msgData, CRYPT_IATTRIBUTE_RANDOM_NONCE );
bufPtr[ padLength ] = packetType;
if( ( sessionInfoPtr->flags & ( SESSION_ISCRYPTLIB | SESSION_ISSECURE_WRITE ) ) == \
( SESSION_ISCRYPTLIB | SESSION_ISSECURE_WRITE ) )
crc32 = calculateTruncatedMAC( sessionInfoPtr->iAuthInContext,
bufPtr,
padLength + ID_SIZE + dataLength );
else
crc32 = calculateCRC( bufPtr, padLength + ID_SIZE + dataLength );
bufPtr += padLength + ID_SIZE + dataLength;
mputLong( bufPtr, crc32 );
if( sessionInfoPtr->flags & SESSION_ISSECURE_WRITE )
{
/* Encrypt the payload with handling for SSH's Blowfish
endianness bug */
if( sessionInfoPtr->cryptAlgo == CRYPT_ALGO_BLOWFISH )
longReverse( ( unsigned long * ) ( bufStartPtr + LENGTH_SIZE ),
padLength + length );
status = krnlSendMessage( sessionInfoPtr->iCryptOutContext,
IMESSAGE_CTX_ENCRYPT,
bufStartPtr + LENGTH_SIZE,
padLength + length );
if( sessionInfoPtr->cryptAlgo == CRYPT_ALGO_BLOWFISH )
longReverse( ( unsigned long * ) ( bufStartPtr + LENGTH_SIZE ),
padLength + length );
if( cryptStatusError( status ) )
return( status );
}
if( delta != CRYPT_UNUSED )
{
/* If we're sending payload data the send is interruptible, it's the
caller's responsibility to handle this. Since the caller expects
to write data from the start of the buffer, we have to move it
down to line it up correctly. This is somewhat inefficient, but
since SSHv1 is virtually extinct it's not worth going to a lot of
effort to work around this (the same applies to other SSHv1
issues like the Blowfish endianness problem and the weird 3DES
mode) */
if( delta )
memmove( sessionInfoPtr->sendBuffer, bufStartPtr,
LENGTH_SIZE + padLength + length );
return( LENGTH_SIZE + padLength + length );
}
status = swrite( &sessionInfoPtr->stream, bufStartPtr,
LENGTH_SIZE + padLength + length );
if( cryptStatusError( status ) )
{
sNetGetErrorInfo( &sessionInfoPtr->stream,
&sessionInfoPtr->errorInfo );
return( status );
}
return( CRYPT_OK ); /* swrite() returns a byte count */
}
/****************************************************************************
* *
* 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;
MESSAGE_DATA msgData;
BYTE *bufPtr;
const BOOLEAN hasPassword = \
( findSessionInfo( sessionInfoPtr->attributeList,
CRYPT_SESSINFO_PASSWORD ) != NULL ) ? \
TRUE : FALSE;
const BOOLEAN hasPrivkey = \
( findSessionInfo( sessionInfoPtr->attributeList,
CRYPT_SESSINFO_PRIVATEKEY ) != NULL ) ? \
TRUE : FALSE;
BOOLEAN rsaOK, pwOK;
int hostKeyLength, serverKeyLength, keyDataLength, length, value, status;
/* The higher-level code has already read the server session info, send
back our own version info (SSHv1 uses only a LF as terminator). For
SSHv1 we use the lowest common denominator of our version (1.5,
described in the only existing spec for SSHv1) and whatever the
server can handle */
strlcpy_s( sessionInfoPtr->sendBuffer, 128, SSH1_ID_STRING "\n" );
if( sessionInfoPtr->receiveBuffer[ 2 ] < \
SSH1_ID_STRING[ SSH_ID_SIZE + 2 ] )
sessionInfoPtr->sendBuffer[ SSH_ID_SIZE + 2 ] = \
sessionInfoPtr->receiveBuffer[ 2 ];
status = swrite( &sessionInfoPtr->stream, sessionInfoPtr->sendBuffer,
strlen( sessionInfoPtr->sendBuffer ) );
if( cryptStatusError( status ) )
{
sNetGetErrorInfo( &sessionInfoPtr->stream,
&sessionInfoPtr->errorInfo );
return( status );
}
/* Create the contexts to hold the server and host keys */
setMessageCreateObjectInfo( &createInfo, CRYPT_ALGO_RSA );
status = krnlSendMessage( SYSTEM_OBJECT_HANDLE, IMESSAGE_DEV_CREATEOBJECT,
&createInfo, OBJECT_TYPE_CONTEXT );
if( cryptStatusError( status ) )
return( status );
handshakeInfo->iServerCryptContext = createInfo.cryptHandle;
setMessageCreateObjectInfo( &createInfo, CRYPT_ALGO_RSA );
status = krnlSendMessage( SYSTEM_OBJECT_HANDLE, IMESSAGE_DEV_CREATEOBJECT,
&createInfo, OBJECT_TYPE_CONTEXT );
if( cryptStatusError( status ) )
return( status );
sessionInfoPtr->iKeyexCryptContext = createInfo.cryptHandle;
/* If the peer is using cryptlib, we use HMAC-SHA instead of CRC32 */
if( sessionInfoPtr->flags & SESSION_ISCRYPTLIB )
{
setMessageCreateObjectInfo( &createInfo, CRYPT_ALGO_HMAC_SHA );
status = krnlSendMessage( SYSTEM_OBJECT_HANDLE,
IMESSAGE_DEV_CREATEOBJECT, &createInfo,
OBJECT_TYPE_CONTEXT );
if( cryptStatusError( status ) )
return( status );
sessionInfoPtr->iAuthInContext = createInfo.cryptHandle;
}
/* Process the server public key packet:
byte[8] cookie
uint32 keysize_bits - Usually 768 bits
mpint serverkey_exponent
mpint serverkey_modulus
uint32 keysize_bits - Usually 1024 bits
mpint hostkey_exponent
mpint hostkey_modulus
uint32 protocol_flags - Not used
uint32 offered_ciphers
uint32 offered_authent */
length = readPacketSSH1( sessionInfoPtr, SSH1_SMSG_PUBLIC_KEY );
if( cryptStatusError( length ) )
return( length );
bufPtr = sessionInfoPtr->receiveBuffer;
memcpy( handshakeInfo->cookie, bufPtr, SSH1_COOKIE_SIZE );
bufPtr += SSH1_COOKIE_SIZE;
length -= SSH1_COOKIE_SIZE;
keyDataLength = status = processPublickeyData( handshakeInfo, bufPtr,
length, TRUE, NULL );
if( cryptStatusError( status ) )
return( status );
setMessageData( &msgData, bufPtr, keyDataLength );
status = krnlSendMessage( handshakeInfo->iServerCryptContext,
IMESSAGE_SETATTRIBUTE_S, &msgData,
CRYPT_IATTRIBUTE_KEY_SSH1 );
if( cryptStatusError( status ) )
return( status );
serverKeyLength = mgetLong( bufPtr );
bufPtr += keyDataLength - LENGTH_SIZE;
length -= keyDataLength;
keyDataLength = status = processPublickeyData( handshakeInfo, bufPtr,
length, FALSE,
sessionInfoPtr );
if( cryptStatusError( status ) )
return( status );
setMessageData( &msgData, bufPtr, keyDataLength );
status = krnlSendMessage( sessionInfoPtr->iKeyexCryptContext,
IMESSAGE_SETATTRIBUTE_S, &msgData,
CRYPT_IATTRIBUTE_KEY_SSH1 );
if( cryptStatusError( status ) )
return( status );
hostKeyLength = mgetLong( bufPtr );
bufPtr += keyDataLength - LENGTH_SIZE + UINT_SIZE; /* Skip protocol flags */
length -= keyDataLength + UINT_SIZE;
if( length != UINT_SIZE + UINT_SIZE )
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Invalid length %d, should be %d", length,
UINT_SIZE + UINT_SIZE );
value = ( int ) mgetLong( bufPtr ); /* Offered ciphers */
sessionInfoPtr->cryptAlgo = maskToAlgoID( value );
if( sessionInfoPtr->cryptAlgo == CRYPT_ALGO_NONE )
retExt( sessionInfoPtr, CRYPT_ERROR_NOTAVAIL,
"No crypto algorithm compatible with the remote system "
"could be found" );
value = ( int ) mgetLong( bufPtr ); /* Offered authentication */
pwOK = hasPassword && ( value & ( 1 << SSH1_AUTH_PASSWORD ) );
rsaOK = hasPrivkey && ( value & ( 1 << SSH1_AUTH_RSA ) );
if( !pwOK )
{
/* If neither RSA nor password authentication is possible, we can't
authenticate ourselves */
if( !rsaOK )
{
if( value & ( 1 << SSH1_AUTH_PASSWORD ) )
{
setErrorInfo( sessionInfoPtr, CRYPT_SESSINFO_PASSWORD,
CRYPT_ERRTYPE_ATTR_ABSENT );
retExt( sessionInfoPtr, CRYPT_ERROR_NOTINITED,
"Server requested password authentication but no "
"password was available" );
}
setErrorInfo( sessionInfoPtr, CRYPT_SESSINFO_PRIVATEKEY,
CRYPT_ERRTYPE_ATTR_ABSENT );
retExt( sessionInfoPtr, CRYPT_ERROR_NOTINITED,
"Server requested public-key authentication but no "
"key was available" );
}
/* Either the client or the server won't do passwords, turn it off
explicitly at the client in case it's the server */
if( hasPassword )
{
ATTRIBUTE_LIST *attributeListPtr = ( ATTRIBUTE_LIST * ) \
findSessionInfo( sessionInfoPtr->attributeList,
CRYPT_SESSINFO_PASSWORD );
deleteSessionInfo( &sessionInfoPtr->attributeList,
attributeListPtr );
}
}
/* Although in theory the server key has to fit inside the host key, the
spec is vague enough to allow either of the keys to be larger (and at
least one SSH implementation has them the wrong way around), requiring
that the two be swapped before they can be used (which makes you
wonder why there's any distinction, since the two must be
interchangeable in order for this to work) */
if( hostKeyLength < serverKeyLength )
{
int temp;
/* Swap the two keys around */
temp = sessionInfoPtr->iKeyexCryptContext;
sessionInfoPtr->iKeyexCryptContext = \
handshakeInfo->iServerCryptContext;
handshakeInfo->iServerCryptContext = temp;
temp = hostKeyLength;
hostKeyLength = serverKeyLength;
serverKeyLength = temp;
}
/* Make sure that the smaller of the two keys will fit inside the
larger */
if( hostKeyLength < serverKeyLength + 128 )
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Invalid host vs.server key lengths %d:%d bytes",
hostKeyLength, serverKeyLength );
return( CRYPT_OK );
}
/* Exchange keys with the server */
static int exchangeClientKeys( SESSION_INFO *sessionInfoPtr,
SSH_HANDSHAKE_INFO *handshakeInfo )
{
MECHANISM_WRAP_INFO mechanismInfo;
MESSAGE_DATA msgData;
BYTE buffer[ CRYPT_MAX_PKCSIZE + 8 ];
BYTE *bufPtr = sessionInfoPtr->sendBuffer;
int length, dataLength, value, i, status;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -