📄 ssh2_rw.c
字号:
&sessionInfoPtr->errorCode );
return( status );
}
if( status != remainingLength )
retExt( sessionInfoPtr, CRYPT_ERROR_TIMEOUT,
"Timeout during handshake packet remainder read, "
"only got %d of %ld bytes", status,
remainingLength );
}
/* Decrypt and MAC the packet if required */
if( sessionInfoPtr->flags & SESSION_ISSECURE_READ )
{
/* Decrypt the remainder of the packet except for the MAC.
Sometimes the payload can be zero-length, so we have to check
for this before we try the decrypt */
if( length - SSH2_HEADER_REMAINDER_SIZE > 0 )
{
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 */
status = macPayload( sessionInfoPtr->iAuthInContext,
sshInfo->readSeqNo,
sessionInfoPtr->receiveBuffer, length, 0,
MAC_ALL, sessionInfoPtr->authBlocksize, TRUE );
if( cryptStatusError( status ) )
{
/* 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 for handshake packet type %d, "
"length %d, probably due to an incorrect key "
"being used to generate the MAC",
sessionInfoPtr->receiveBuffer[ 1 ], length );
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Bad message MAC for handshake packet type %d, "
"length %d", sessionInfoPtr->receiveBuffer[ 1 ],
length );
}
}
padLength = sessionInfoPtr->receiveBuffer[ 0 ];
packetType = sessionInfoPtr->receiveBuffer[ 1 ];
sshInfo->readSeqNo++;
}
while( ( packetType == SSH2_MSG_IGNORE || \
packetType == SSH2_MSG_DEBUG || \
packetType == SSH2_MSG_USERAUTH_BANNER ) && \
( iterationCount++ < 1000 ) );
if( iterationCount >= 1000 )
retExt( sessionInfoPtr, CRYPT_ERROR_OVERFLOW,
"Peer sent excessive number of no-op packets" );
sshInfo->packetType = packetType;
/* Adjust the length to account for the fixed-size fields, remember
where the data starts, and make sure that there's some payload
present (there should always be at least one byte, the packet type) */
dataStartPtr = sessionInfoPtr->receiveBuffer + PADLENGTH_SIZE;
length -= PADLENGTH_SIZE + padLength;
if( length < minPacketSize )
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Invalid length %ld for handshake packet type %d, should "
"be at least %d", length, packetType, minPacketSize );
/* If the other side has gone away, report the details */
if( packetType == SSH2_MSG_DISCONNECT )
{
STREAM stream;
sMemConnect( &stream, dataStartPtr, length );
assert( sPeek( &stream ) == SSH2_MSG_DISCONNECT );
sgetc( &stream ); /* Skip packet type */
status = getDisconnectInfo( sessionInfoPtr, &stream );
sMemDisconnect( &stream );
return( status );
}
/* Make sure that we either got what we asked for or one of the allowed
special-case packets */
switch( expectedType )
{
case 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 */
expectedType = ( packetType == SSH2_MSG_USERAUTH_FAILURE ) ? \
SSH2_MSG_USERAUTH_FAILURE : \
SSH2_MSG_USERAUTH_SUCCESS;
break;
case SSH2_MSG_SPECIAL_USERAUTH_PAM:
/* PAM authentication can go through multiple iterations of back-
and-forth negotiation, for this case an info-request is also
a valid response, otherwise the responses are as for
SSH2_MSG_SPECIAL_USERAUTH */
expectedType = ( packetType == SSH2_MSG_USERAUTH_INFO_REQUEST ) ? \
SSH2_MSG_USERAUTH_INFO_REQUEST : \
( packetType == SSH2_MSG_USERAUTH_FAILURE ) ? \
SSH2_MSG_USERAUTH_FAILURE : \
SSH2_MSG_USERAUTH_SUCCESS;
break;
case SSH2_MSG_SPECIAL_CHANNEL:
/* If we're reading a response to a channel open message then
getting a failure response is valid (even if it's not what
we're expecting) since it's an indication that the channel
open (for example a port-forwarding operation) failed rather
than that there was some general type of failure */
expectedType = ( packetType == SSH2_MSG_CHANNEL_OPEN_FAILURE ) ? \
SSH2_MSG_CHANNEL_OPEN_FAILURE : \
SSH2_MSG_CHANNEL_OPEN_CONFIRMATION;
break;
case 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 handshake packet type %d, expected global "
"or channel request", packetType );
expectedType = packetType;
break;
case SSH2_MSG_KEXDH_GEX_REQUEST_OLD:
/* 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 */
if( packetType == SSH2_MSG_KEXDH_GEX_REQUEST_NEW )
expectedType = packetType;
break;
}
if( packetType != expectedType )
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Invalid handshake packet type %d, expected %d", packetType,
expectedType );
/* Move the data down in the buffer to get rid of the header info.
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 );
}
/****************************************************************************
* *
* Write/Wrap a Packet *
* *
****************************************************************************/
/* Open a stream to write an SSH2 packet or continue an existing stream to
write further packets. This opens the stream (if it's an open), skips
the storage for the packet header, and writes the packet type */
void openPacketStreamSSH( STREAM *stream, const SESSION_INFO *sessionInfoPtr,
const int bufferSize, const int packetType )
{
const int streamSize = ( bufferSize == CRYPT_USE_DEFAULT ) ? \
sessionInfoPtr->sendBufSize - EXTRA_PACKET_SIZE : \
bufferSize + SSH2_HEADER_SIZE;
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( isReadPtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
assert( isWritePtr( sessionInfoPtr->sendBuffer, streamSize ) );
assert( streamSize > SSH2_HEADER_SIZE );
sMemOpen( stream, sessionInfoPtr->sendBuffer, streamSize );
swrite( stream, "\x00\x00\x00\x00\x00", SSH2_HEADER_SIZE );
sputc( stream, packetType );
}
int continuePacketStreamSSH( STREAM *stream, const int packetType )
{
const int packetOffset = stell( stream );
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( stell( stream ) == 0 || stell( stream ) > SSH2_HEADER_SIZE + 1 );
swrite( stream, "\x00\x00\x00\x00\x00", SSH2_HEADER_SIZE );
sputc( stream, packetType );
return( packetOffset );
}
/* 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 gift-wrapped so we have to
provide a facility for separately wrapping and sending packets to handle
this */
int wrapPacketSSH2( SESSION_INFO *sessionInfoPtr, STREAM *stream,
const int offset )
{
SSH_INFO *sshInfo = sessionInfoPtr->sessionSSH;
const int length = stell( stream ) - offset;
const int payloadLength = length - SSH2_HEADER_SIZE;
const int padBlockSize = max( sessionInfoPtr->cryptBlocksize, 8 );
BYTE *bufPtr = sMemBufPtr( stream ) - length;
const BYTE *bufStartPtr = bufPtr;
int padLength, extraLength, status;
assert( isReadPtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( sStatusOK( stream ) );
assert( offset >= 0 );
assert( length >= SSH2_HEADER_SIZE );
assert( payloadLength >= 0 );
/* Safety check to make sure that the stream is OK */
if( !sStatusOK( stream ) )
{
assert( NOTREACHED );
return( sGetStatus( stream ) );
}
/* Evaluate 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,
padBlockSize ) - length;
assert( padLength >= SSH2_MIN_PADLENGTH_SIZE && padLength < 256 );
/* Make sure that there's enough room for the padding and MAC */
extraLength = padLength + \
( ( sessionInfoPtr->flags & SESSION_ISSECURE_WRITE ) ? \
sessionInfoPtr->authBlocksize : 0 );
if( sMemDataLeft( stream ) < extraLength )
return( CRYPT_ERROR_OVERFLOW );
/* Add the SSH packet header:
uint32 length (excluding MAC size)
byte padLen
byte[] data
byte[] padding
byte[] MAC
Because of the ad-hoc handling that this requires, we use the direct
memory manipulation routines rather than the stream functions */
mputLong( bufPtr, ( long ) ( length - LENGTH_SIZE ) + padLength );
*bufPtr++ = padLength;
bufPtr += payloadLength;
if( sessionInfoPtr->flags & SESSION_ISSECURE_WRITE )
{
RESOURCE_DATA msgData;
const int totalLength = SSH2_HEADER_SIZE + payloadLength + padLength;
/* Append the padding */
setMessageData( &msgData, bufPtr, padLength );
krnlSendMessage( SYSTEM_OBJECT_HANDLE, IMESSAGE_GETATTRIBUTE_S,
&msgData, CRYPT_IATTRIBUTE_RANDOM_NONCE );
assert( bufPtr + padLength == bufStartPtr + totalLength );
/* MAC the data. We skip the length value at the start since this
is computed by the MAC'ing code */
status = macPayload( sessionInfoPtr->iAuthOutContext,
sshInfo->writeSeqNo, bufStartPtr + LENGTH_SIZE,
totalLength - LENGTH_SIZE, 0,
MAC_ALL, sessionInfoPtr->authBlocksize, FALSE );
if( cryptStatusError( status ) )
return( status );
/* Encrypt the entire packet except for the MAC */
status = krnlSendMessage( sessionInfoPtr->iCryptOutContext,
IMESSAGE_CTX_ENCRYPT, ( void * ) bufStartPtr,
totalLength );
if( cryptStatusError( status ) )
return( status );
}
else
/* If there's no security in effect yet, the padding is all zeroes */
memset( bufPtr, 0, padLength );
sshInfo->writeSeqNo++;
/* Sync the stream info to match the new payload size */
return( sSkip( stream, extraLength ) );
}
int sendPacketSSH2( SESSION_INFO *sessionInfoPtr, STREAM *stream,
const BOOLEAN sendOnly )
{
int status;
if( !sendOnly )
{
status = wrapPacketSSH2( sessionInfoPtr, stream, 0 );
if( cryptStatusError( status ) )
return( status );
}
status = swrite( &sessionInfoPtr->stream,
sMemBufPtr( stream ) - stell( stream ),
stell( stream ) );
if( cryptStatusError( status ) && \
!( sessionInfoPtr->flags & SESSION_NOREPORTERROR ) )
{
sNetGetErrorInfo( &sessionInfoPtr->stream,
sessionInfoPtr->errorMessage,
&sessionInfoPtr->errorCode );
return( status );
}
return( CRYPT_OK ); /* swrite() returns a byte count */
}
#endif /* USE_SSH2 */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -