📄 ssh2.c
字号:
dataLength );
if( macType == MAC_END || macType == MAC_ALL )
{
RESOURCE_DATA msgData;
BYTE macBuffer[ CRYPT_MAX_HASHSIZE ];
krnlSendMessage( iMacContext, IMESSAGE_CTX_HASH, "", 0 );
setMessageData( &msgData, macBuffer, CRYPT_MAX_HASHSIZE );
status = krnlSendMessage( iMacContext, IMESSAGE_GETATTRIBUTE_S,
&msgData, CRYPT_CTXINFO_HASHVALUE );
if( cryptStatusError( status ) || \
memcmp( macBuffer, data + dataLength, msgData.length ) )
return( FALSE );
}
return( TRUE );
}
/* Get the reason why the peer closed the connection */
static int getDisconnectInfo( SESSION_INFO *sessionInfoPtr, BYTE *bufPtr )
{
static const FAR_BSS struct {
const int sshStatus, cryptlibStatus;
} errorMap[] = {
{ SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT, CRYPT_ERROR_PERMISSION },
{ SSH2_DISCONNECT_MAC_ERROR, CRYPT_ERROR_SIGNATURE },
{ SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE, CRYPT_ERROR_NOTAVAIL },
{ SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, CRYPT_ERROR_NOTAVAIL },
{ SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE, CRYPT_ERROR_WRONGKEY },
{ CRYPT_ERROR, CRYPT_ERROR_READ }
};
int length, i;
/* Server is disconnecting, find out why */
bufPtr++; /* Skip packet type */
sessionInfoPtr->errorCode = mgetLong( bufPtr );
length = mgetLong( bufPtr );
if( length < 0 || length > MAX_ERRMSG_SIZE - 32 )
retExt( sessionInfoPtr, CRYPT_ERROR_OVERFLOW,
"Invalid error information size %d", length );
strcpy( sessionInfoPtr->errorMessage, "Received SSHv2 server message: " );
if( length <= 0 )
strcat( sessionInfoPtr->errorMessage, "<None>" );
else
{
memcpy( sessionInfoPtr->errorMessage + 31, bufPtr, length );
sessionInfoPtr->errorMessage[ 31 + length ] = '\0';
}
/* Try and map the SSH status to an equivalent cryptlib code */
for( i = 0; errorMap[ i ].sshStatus != CRYPT_ERROR; i++ )
if( errorMap[ i ].sshStatus == sessionInfoPtr->errorCode )
break;
return( errorMap[ i ].cryptlibStatus );
}
/* Read an SSHv2 packet. This function is only used during the handshake
phase (the data transfer phase has its own read/write code) so we can
perform some special-case handling based on this */
int readPacketSSH2( 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
no-op messages, ranging from explicit no-ops (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 extraLength = 0, status;
/* Read the SSHv2 packet header:
uint32 length
byte padLen
[ byte type - checked but not removed ]
byte[] data
byte[] padding
byte[] MAC
The reason why the length and pad length precede the packet type
and other information is that these two fields are part of the
SSHv2 transport layer while the type and payload are seen as part
of the connection layer, although the different RFCs tend to mix
them up quite thoroughly.
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 */
assert( sessionInfoPtr->receiveBufEnd == 0 );
status = readFixedHeader( sessionInfoPtr, MIN_PACKET_SIZE );
if( cryptStatusError( status ) )
return( status );
assert( status == MIN_PACKET_SIZE );
if( ( sessionInfoPtr->protocolFlags & SSH_PFLAG_TEXTDIAGS ) && \
sessionInfoPtr->receiveBuffer[ 0 ] == 'F' && \
( !memcmp( sessionInfoPtr->receiveBuffer, "FATAL: ", 7 ) || \
!memcmp( sessionInfoPtr->receiveBuffer, "FATAL ERROR:", 12 ) ) )
{
/* Versions of SSH derived from the original SSH code base can
sometimes dump raw text strings (that is, strings not
encapsulated in SSH packets such as error packets) onto the
connection if something unexpected occurs. Normally this
would result in a bad data or MAC error since they decrypt to
garbage, so we try and catch them here */
dataStartPtr = sessionInfoPtr->receiveBuffer + MIN_PACKET_SIZE;
for( length = 0;
length < MAX_ERRMSG_SIZE - ( MIN_PACKET_SIZE + 64 );
length++ )
{
status = sread( &sessionInfoPtr->stream,
dataStartPtr + length, 1 );
if( cryptStatusError( status ) || \
dataStartPtr[ length ] == '\n' )
break;
}
while( length > 0 && \
( dataStartPtr[ length - 1 ] == '\r' || \
dataStartPtr[ length - 1 ] == '\n' ) )
length--;
dataStartPtr[ length ] = '\0';
/* Report the error as a problem with the remote software.
Since the other side has bailed out, we mark the channel as
closed to prevent any attempt to perform proper shutdown */
sessionInfoPtr->flags |= SESSION_SENDCLOSED;
sessionInfoPtr->protocolFlags |= SSH_PFLAG_CHANNELCLOSED;
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Remote SSH software has crashed, diagnostic was '%s'",
sessionInfoPtr->receiveBuffer );
}
if( sessionInfoPtr->flags & SESSION_ISSECURE )
{
status = krnlSendMessage( sessionInfoPtr->iCryptInContext,
IMESSAGE_CTX_DECRYPT,
sessionInfoPtr->receiveBuffer,
MIN_PACKET_SIZE );
if( cryptStatusError( status ) )
return( status );
}
length = mgetLong( bufPtr );
assert( SSH2_HEADER_REMAINDER_SIZE == MIN_PACKET_SIZE - LENGTH_SIZE );
if( sessionInfoPtr->flags & SESSION_ISSECURE )
/* The MAC size isn't included in the packet length so we have to
add it manually */
extraLength = sessionInfoPtr->authBlocksize;
if( length + extraLength < SSH2_HEADER_REMAINDER_SIZE || \
length + extraLength >= sessionInfoPtr->receiveBufSize )
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Invalid packet length %d, extra length %d", length,
extraLength );
memmove( sessionInfoPtr->receiveBuffer,
sessionInfoPtr->receiveBuffer + LENGTH_SIZE,
SSH2_HEADER_REMAINDER_SIZE );
if( length + extraLength > SSH2_HEADER_REMAINDER_SIZE )
{
const long remainingLength = length + extraLength - \
SSH2_HEADER_REMAINDER_SIZE;
/* The change cipherspec message has length 0, so we only
perform the read if there's packet data present. Because
this code is called conditionally, we can't make the read
part of the fixed-header read but have to do independent
handling of shortfalls due to read timeouts */
status = sread( &sessionInfoPtr->stream,
sessionInfoPtr->receiveBuffer + \
SSH2_HEADER_REMAINDER_SIZE,
remainingLength );
if( cryptStatusError( status ) )
{
sNetGetErrorInfo( &sessionInfoPtr->stream,
sessionInfoPtr->errorMessage,
&sessionInfoPtr->errorCode );
return( status );
}
if( status != remainingLength )
retExt( sessionInfoPtr, CRYPT_ERROR_TIMEOUT,
"Timeout during packet remainder read, only got %d "
"of %d bytes", status, remainingLength );
}
if( sessionInfoPtr->flags & SESSION_ISSECURE )
{
/* Decrypt the remainder of the packet except for the MAC */
status = krnlSendMessage( sessionInfoPtr->iCryptInContext,
IMESSAGE_CTX_DECRYPT,
sessionInfoPtr->receiveBuffer + \
SSH2_HEADER_REMAINDER_SIZE,
length - SSH2_HEADER_REMAINDER_SIZE );
if( cryptStatusError( status ) )
return( status );
/* MAC the decrypted payload */
if( !macPayload( sessionInfoPtr->iAuthInContext,
sessionInfoPtr->readSeqNo,
sessionInfoPtr->receiveBuffer, length, 0,
MAC_ALL ) )
{
/* If we're expecting a service control 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 */
if( expectedType == SSH2_MSG_SERVICE_REQUEST || \
expectedType == SSH2_MSG_SERVICE_ACCEPT )
retExt( sessionInfoPtr, CRYPT_ERROR_WRONGKEY,
"Bad message MAC, probably due to an incorrect "
"key being used to generate the MAC" );
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Bad message MAC" );
}
}
padLength = sessionInfoPtr->receiveBuffer[ 0 ];
packetType = sessionInfoPtr->receiveBuffer[ 1 ];
sessionInfoPtr->readSeqNo++;
}
while( packetType == SSH2_MSG_IGNORE || packetType == SSH2_MSG_DEBUG || \
packetType == SSH2_MSG_USERAUTH_BANNER );
sessionInfoPtr->sshPacketType = packetType;
/* Adjust the length to account for the fixed-size fields and remember
where the data starts */
dataStartPtr = sessionInfoPtr->receiveBuffer + PADLENGTH_SIZE;
length -= PADLENGTH_SIZE + padLength;
/* Make sure that we either got what we asked for or one of the allowed
special-case packets */
if( packetType == SSH2_MSG_DISCONNECT )
return( getDisconnectInfo( sessionInfoPtr, dataStartPtr ) );
if( expectedType == SSH2_MSG_SPECIAL_USERAUTH )
{
/* If we're reading a response to a user authentication message 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:
byte type = SSH2_MSG_USERAUTH_FAILURE
string allowed_authent
boolean partial_success = FALSE */
if( packetType == SSH2_MSG_USERAUTH_FAILURE )
{
BYTE *bufPtr = dataStartPtr;
long stringLength;
if( length < ID_SIZE + ( LENGTH_SIZE + 1 ) + BOOLEAN_SIZE )
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Invalid user auth response length %d", length );
bufPtr++; /* Skip packet type */
stringLength = mgetLong( bufPtr );
if( length != ID_SIZE + LENGTH_SIZE + stringLength + BOOLEAN_SIZE )
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Invalid user auth response length %d, string length "
"%d", length, stringLength );
/* If the returned information can fit into an error message,
return it to the caller */
if( stringLength < MAX_ERRMSG_SIZE - 70 )
{
strcpy( sessionInfoPtr->errorMessage,
"Received SSHv2 server message: Permitted "
"authentication types are " );
memcpy( sessionInfoPtr->errorMessage + 66, bufPtr,
stringLength );
sessionInfoPtr->errorMessage[ 66 + stringLength ] = '\0';
}
memmove( sessionInfoPtr->receiveBuffer, dataStartPtr, length );
return( CRYPT_ERROR_WRONGKEY );
}
expectedType = SSH2_MSG_USERAUTH_SUCCESS;
}
if( expectedType == SSH2_MSG_SPECIAL_REQUEST )
{
/* If we're at the end of the handshake phase we can get either a
global or a channel request to tell us what to do next */
if( packetType != SSH2_MSG_GLOBAL_REQUEST && \
packetType != SSH2_MSG_CHANNEL_REQUEST )
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Invalid packet type %d, expected global or channel "
"request", packetType );
expectedType = packetType;
}
if( expectedType == SSH2_MSG_KEXDH_GEX_REQUEST && \
packetType == SSH2_MSG_KEXDH_GEX_REQUEST_NEW )
/* The ephemeral DH key exchange spec was changed halfway through to
try and work around problems with key negotiation, because of this
we can see two different types of ephemeral DH request, although
they're functionally identical */
expectedType = packetType;
if( packetType != expectedType )
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Invalid packet type %d, expected %d", packetType,
expectedType );
/* Move the data down in the buffer to get rid of the header info,
and discard the padding. This isn't as inefficient as it seems
since it's only used for the short handshake messages */
memmove( sessionInfoPtr->receiveBuffer, dataStartPtr, length );
return( length );
}
/* Send an SSHv2 packet. During the handshake phase we may be sending
multiple packets at once, however unlike SSL, SSH requires that each
packet in a multi-packet group be individually wrapped so we have to
provide a facility for separately wrapping and sending packets to handle
this */
int wrapPacket( SESSION_INFO *sessionInfoPtr, BYTE *bufPtr,
const int dataLength )
{
const BYTE *bufStartPtr = bufPtr;
const int length = LENGTH_SIZE + PADLENGTH_SIZE + dataLength;
const int padBlockSize = max( sessionInfoPtr->cryptBlocksize, 8 );
int padLength, status;
/* Evaludate the number of padding bytes that we need to add to a packet
to make it a multiple of the cipher block size long, with a minimum
padding size of SSH2_MIN_PADLENGTH_SIZE bytes. Note that this padding
is required even when there's no encryption being applied, although we
set the padding to all zeroes in this case */
if( bufPtr[ LENGTH_SIZE + PADLENGTH_SIZE ] == SSH2_MSG_USERAUTH_REQUEST )
{
/* It's a user-authentication packet that (probably) contains a
password, make it fixed-length to hide the length information */
for( padLength = 256;
( length + SSH2_MIN_PADLENGTH_SIZE ) > padLength;
padLength += 256 );
padLength -= length;
}
else
padLength = roundUp( length + SSH2_MIN_PADLENGTH_SIZE,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -