📄 ssh.c
字号:
/****************************************************************************
* *
* cryptlib SSH Session Management *
* Copyright Peter Gutmann 1998-2004 *
* *
****************************************************************************/
#include <stdlib.h>
#include <string.h>
#if defined( INC_ALL )
#include "crypt.h"
#include "misc_rw.h"
#include "session.h"
#include "ssh.h"
#elif defined( INC_CHILD )
#include "../crypt.h"
#include "../misc/misc_rw.h"
#include "session.h"
#include "ssh.h"
#else
#include "crypt.h"
#include "misc/misc_rw.h"
#include "session/session.h"
#include "session/ssh.h"
#endif /* Compiler-specific includes */
#if defined( USE_SSH1 ) || defined( USE_SSH2 )
/****************************************************************************
* *
* Utility Functions *
* *
****************************************************************************/
/* Initialise and destroy the handshake state information */
static int initHandshakeInfo( SSH_HANDSHAKE_INFO *handshakeInfo )
{
/* Initialise the handshake state info values */
memset( handshakeInfo, 0, sizeof( SSH_HANDSHAKE_INFO ) );
handshakeInfo->iExchangeHashcontext = \
handshakeInfo->iServerCryptContext = CRYPT_ERROR;
return( CRYPT_OK );
}
static void destroyHandshakeInfo( SSH_HANDSHAKE_INFO *handshakeInfo )
{
/* Destroy any active contexts. We need to do this here (even though
it's also done in the general session code) to provide a clean exit in
case the session activation fails, so that a second activation attempt
doesn't overwrite still-active contexts */
if( handshakeInfo->iExchangeHashcontext != CRYPT_ERROR )
krnlSendNotifier( handshakeInfo->iExchangeHashcontext,
IMESSAGE_DECREFCOUNT );
if( handshakeInfo->iServerCryptContext != CRYPT_ERROR )
krnlSendNotifier( handshakeInfo->iServerCryptContext,
IMESSAGE_DECREFCOUNT );
zeroise( handshakeInfo, sizeof( SSH_HANDSHAKE_INFO ) );
}
/* Read the SSH version information string */
static int readVersionLine( STREAM *stream, BYTE *buffer )
{
int length, status;
/* Try and read the initial ID string data */
status = sread( stream, buffer, SSH_ID_SIZE );
if( cryptStatusError( status ) )
return( status );
if( status < SSH_ID_SIZE )
/* This can happen if the caller sets a very short read timeout */
return( CRYPT_ERROR_UNDERFLOW );
/* Read the remainder of the text line, one character at a time. If
this was an HTTP stream we could use speculative read-ahead buffering,
but there's no easy way to communicate this requirement to the stream-
handling code */
for( length = SSH_ID_SIZE; length < SSH_ID_MAX_SIZE; length++ )
{
status = sread( stream, buffer + length, 1 );
if( cryptStatusError( status ) )
return( status );
if( status <= 0 )
return( CRYPT_ERROR_UNDERFLOW );
if( !buffer[ length ] )
/* The spec doesn't really say what is and isn't valid in the ID
strings, although it does say that nuls shouldn't be used.
In any case we can't allow these because they'd cause
problems for the string-handling functions */
return( CRYPT_ERROR_BADDATA );
if( buffer[ length ] == '\n' )
break;
}
if( ( length < SSH_ID_SIZE + 3 ) || ( length >= SSH_ID_MAX_SIZE ) )
return( CRYPT_ERROR_BADDATA );
/* Null-terminate the string so that we can hash it to create the SSHv2
exchange hash */
while( length > 0 && \
( buffer[ length - 1 ] == '\r' || buffer[ length - 1 ] == '\n' ) )
length--;
buffer[ length ] = '\0';
return( CRYPT_OK );
}
static int readVersionString( SESSION_INFO *sessionInfoPtr )
{
const char *versionStringPtr = sessionInfoPtr->receiveBuffer + SSH_ID_SIZE;
int linesRead = 0, status;
/* Read the server version info, with the format for the ID string being
"SSH-protocolversion-softwareversion comments", which (in the original
ssh.com interpretation) was "SSH-x.y-x.y vendorname" (e.g.
"SSH-2.0-3.0.0 SSH Secure Shell") but for almost everyone else is
"SSH-x.y-vendorname*version" (e.g "SSH-2.0-OpenSSH_3.0").
This version info handling is rather ugly since it's a variable-length
string terminated with a newline, so we have to process it a character
at a time after the initial fixed data.
Unfortunately the SSH RFC further complicates this by allowing
implementations to send non-version-related text lines before the
version line. The theory is that this will allow applications like
TCP wrappers to display a (human-readable) error message before
disconnecting, however some installations use it to display general
banners before the ID string. Since the RFC doesn't provide any means
of distinguishing this banner information from arbitrary data, we
can't quickly reject attempts to connect to something that isn't an
SSH server. In other words we have to sit here waiting for further
data in the hope that eventually an SSH ID turns up, until such time
as the connect timeout expires. In order to provide a more useful
message than a somewhat confusing timeout error, we remember whether
we've already read any lines of text and if we have, report it as an
invalid ID error rather than a timeout error */
do
{
status = readVersionLine( &sessionInfoPtr->stream,
sessionInfoPtr->receiveBuffer );
if( cryptStatusError( status ) )
{
if( status == CRYPT_ERROR_BADDATA )
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Invalid SSH version string length" );
if( status == CRYPT_ERROR_UNDERFLOW )
retExt( sessionInfoPtr, CRYPT_ERROR_UNDERFLOW,
"SSH version string read timed out before all data "
"could be read" );
if( status == CRYPT_ERROR_TIMEOUT && linesRead > 0 )
/* We timed out waiting for an ID to appear, this is an
invalid ID error rather than a true timeout */
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Invalid SSH version string 0x%02X 0x%02X 0x%02X "
"0x%02X",
sessionInfoPtr->receiveBuffer[ 0 ],
sessionInfoPtr->receiveBuffer[ 1 ],
sessionInfoPtr->receiveBuffer[ 2 ],
sessionInfoPtr->receiveBuffer[ 3 ] );
sNetGetErrorInfo( &sessionInfoPtr->stream,
sessionInfoPtr->errorMessage,
&sessionInfoPtr->errorCode );
return( status );
}
if( linesRead++ >= 100 )
/* The peer shouldn't be throwing infinite amounts of junk at us,
if we don't get an SSH ID after reading 100 lines of input
there's a problem */
retExt( sessionInfoPtr, CRYPT_ERROR_OVERFLOW,
"Peer sent excessive amounts of text without sending "
"any SSH version info" );
}
while( memcmp( sessionInfoPtr->receiveBuffer, SSH_ID, SSH_ID_SIZE ) );
/* Determine which version we're talking to */
if( *versionStringPtr == '1' )
{
#ifdef USE_SSH2
if( !memcmp( versionStringPtr, "1.99", 4 ) )
/* SSHv2 server in backwards-compatibility mode */
sessionInfoPtr->version = 2;
else
#endif /* USE_SSH2 */
{
#ifdef USE_SSH1
/* If the caller has specifically asked for SSHv2 but all that
the server offers is SSHv1, we can't continue */
if( sessionInfoPtr->version == 2 )
retExt( sessionInfoPtr, CRYPT_ERROR_NOSECURE,
"Server can only do SSHv1 when SSHv2 was requested" );
sessionInfoPtr->version = 1;
#else
retExt( sessionInfoPtr, CRYPT_ERROR_NOSECURE,
"Server can only do SSHv1" );
#endif /* USE_SSH1 */
}
}
else
#ifdef USE_SSH2
if( *versionStringPtr == '2' )
sessionInfoPtr->version = 2;
else
#endif /* USE_SSH2 */
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Invalid SSH version %c",
sessionInfoPtr->receiveBuffer[ 0 ] );
/* Find the end of the protocol version substring. If there's no
software version info present this isn't really correct, but no major
reason for bailing out, so we just exit normally */
while( *versionStringPtr && *versionStringPtr != '-' )
versionStringPtr++;
if( !versionStringPtr[ 0 ] || !versionStringPtr[ 1 ] )
return( CRYPT_OK );
versionStringPtr++; /* Skip '-' */
/* Check whether the peer is using cryptlib */
if( !memcmp( versionStringPtr, SSH2_ID_STRING + SSH_ID_SIZE + SSH_VERSION_SIZE,
strlen( SSH2_ID_STRING + SSH_ID_SIZE + SSH_VERSION_SIZE ) ) )
sessionInfoPtr->flags |= SESSION_ISCRYPTLIB;
/* Check for various servers that require special-case handling. The
versions that we check for are:
CuteFTP:
Drops the connection after seeing the server hello with no
(usable) error indication. This implementation is somewhat
tricky to detect since it identifies itself using the dubious
vendor ID string "1.0" (see the ssh.com note below), this
problem hasn't been fixed more than a year after the vendor was
notified of it, indicating that it's unlikely to ever be fixed.
CuteFTP also uses the SSHv1 backwards-compatible version string
"1.99" even though it can't actually do SSHv1, which means that
it'll fail if it ever tries to connect to an SSHv1 peer.
OpenSSH:
Omits hashing the exchange hash length when creating the hash
to be signed for client auth for version 2.0 (all subversions).
Can't handle "password" as a PAM sub-method (meaning an
authentication method hint), it responds with an authentication-
failed response as soon as we send the PAM authentication
request, for versions 3.8 - ? (currently 3.9).
ssh.com:
This implementation puts the version number first, so if we find
something without a vendor name at the start we treat it as an
ssh.com version. However, Van Dyke's SSH server VShell also
uses the ssh.com-style identification (fronti nulla fides), so
when we check for the ssh.com implementation we make sure that
it isn't really VShell. In addition CuteFTP advertises its
implementation as "1.0" (without any vendor name), which is
going to cause problems in the future when they move to 2.x.
Omits the DH-derived shared secret when hashing the keying
material for versions identified as "2.0.0" (all
sub-versions) and "2.0.10" .
Uses an SSH2_FIXED_KEY_SIZE-sized key for HMAC instead of the de
facto 160 bits for versions identified as "2.0.", "2.1 ", "2.1.",
and "2.2." (i.e. all sub-versions of 2.0, 2.1, and 2.2), and
specifically version "2.3.0". This was fixed in 2.3.1.
Omits the signature algorithm name for versions identified as
"2.0" and "2.1" (all sub-versions).
Requires a window adjust for every 32K sent even if the window is
advertised as being (effectively) infinite in size for versions
identified as "2.0" and "2.1" (all sub-versions).
Omits hashing the exchange hash length when creating the hash
to be signed for client auth for versions 2.1 and 2.2 (all
subversions).
Dumps text diagnostics (that is, raw text strings rather than
SSH error packets) onto the connection if something unexpected
occurs, for uncertain versions probably in the 2.x range.
Van Dyke:
Omits hashing the exchange hash length when creating the hash to
be signed for client auth for version 3.0 (SecureCRT = SSH) and
1.7 (SecureFX = SFTP).
Further quirks and peculiarities exist, but fortunately these are rare
enough (mostly for SSHv1) that we don't have to go out of our way to
handle them */
if( !memcmp( versionStringPtr, "OpenSSH_", 8 ) )
{
const char *subVersionStringPtr = versionStringPtr + 8;
if( !memcmp( subVersionStringPtr, "2.0", 3 ) )
sessionInfoPtr->protocolFlags |= SSH_PFLAG_NOHASHLENGTH;
if( !memcmp( subVersionStringPtr, "3.8", 3 ) || \
!memcmp( subVersionStringPtr, "3.9", 3 ) || \
!memcmp( subVersionStringPtr, "3.10", 4 ) )
sessionInfoPtr->protocolFlags |= SSH_PFLAG_PAMPW;
}
if( *versionStringPtr == '2' && \
strstr( versionStringPtr, "VShell" ) == NULL )
{
/* ssh.com 2.x versions have quite a number of bugs so we check for
them as a group */
if( !memcmp( versionStringPtr, "2.0.0", 5 ) || \
!memcmp( versionStringPtr, "2.0.10", 6 ) )
sessionInfoPtr->protocolFlags |= SSH_PFLAG_NOHASHSECRET;
if( !memcmp( versionStringPtr, "2.0", 3 ) || \
!memcmp( versionStringPtr, "2.1", 3 ) )
sessionInfoPtr->protocolFlags |= SSH_PFLAG_SIGFORMAT;
if( !memcmp( versionStringPtr, "2.0", 3 ) || \
!memcmp( versionStringPtr, "2.1", 3 ) )
sessionInfoPtr->protocolFlags |= SSH_PFLAG_WINDOWBUG;
if( !memcmp( versionStringPtr, "2.1", 3 ) || \
!memcmp( versionStringPtr, "2.2", 3 ) )
sessionInfoPtr->protocolFlags |= SSH_PFLAG_NOHASHLENGTH;
if( !memcmp( versionStringPtr, "2.0", 3 ) || \
!memcmp( versionStringPtr, "2.1", 3 ) || \
!memcmp( versionStringPtr, "2.2", 3 ) || \
!memcmp( versionStringPtr, "2.3.0", 5 ) )
sessionInfoPtr->protocolFlags |= SSH_PFLAG_HMACKEYSIZE;
if( !memcmp( versionStringPtr, "2.", 2 ) )
/* Not sure of the exact versions where this occurs */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -