📄 ssh2.c
字号:
/****************************************************************************
* *
* cryptlib SSHv2 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 */
#ifdef USE_SSH2
/* Tables mapping SSHv2 algorithm names to cryptlib algorithm IDs, in
preferred algorithm order. See the comment in ssh2_svr.c for the reason
behind the difference in encryption algorithm tables for client and
server */
static const FAR_BSS ALGO_STRING_INFO algoStringKeyexTbl[] = {
{ "diffie-hellman-group-exchange-sha1", CRYPT_PSEUDOALGO_DHE },
{ "diffie-hellman-group1-sha1", CRYPT_ALGO_DH },
{ NULL, CRYPT_ALGO_NONE }
};
static const FAR_BSS ALGO_STRING_INFO algoStringCoprTbl[] = {
{ "none", CRYPT_PSEUDOALGO_COPR },
{ NULL, CRYPT_ALGO_NONE }
};
static const FAR_BSS ALGO_STRING_INFO algoStringPubkeyTbl[] = {
{ "ssh-rsa", CRYPT_ALGO_RSA },
{ "ssh-dss", CRYPT_ALGO_DSA },
{ NULL, CRYPT_ALGO_NONE }
};
static const FAR_BSS ALGO_STRING_INFO algoStringEncrTblClient[] = {
{ "3des-cbc", CRYPT_ALGO_3DES },
{ "aes128-cbc", CRYPT_ALGO_AES },
{ "blowfish-cbc", CRYPT_ALGO_BLOWFISH },
{ "cast128-cbc", CRYPT_ALGO_CAST },
{ "idea-cbc", CRYPT_ALGO_IDEA },
{ "arcfour", CRYPT_ALGO_RC4 },
{ NULL, CRYPT_ALGO_NONE }
};
static const FAR_BSS ALGO_STRING_INFO algoStringEncrTblServer[] = {
{ "3des-cbc", CRYPT_ALGO_3DES },
{ "blowfish-cbc", CRYPT_ALGO_BLOWFISH },
{ "cast128-cbc", CRYPT_ALGO_CAST },
{ "idea-cbc", CRYPT_ALGO_IDEA },
{ "arcfour", CRYPT_ALGO_RC4 },
{ NULL, CRYPT_ALGO_NONE }
};
static const FAR_BSS ALGO_STRING_INFO algoStringMACTbl[] = {
{ "hmac-sha1", CRYPT_ALGO_HMAC_SHA },
{ "hmac-md5", CRYPT_ALGO_HMAC_MD5 },
{ NULL, CRYPT_ALGO_NONE }
};
/****************************************************************************
* *
* Utility Functions *
* *
****************************************************************************/
/* Convert an SSHv2 algorithm list to a cryptlib ID in preferred-algorithm
order. For some bizarre reason the algorithm information is communicated
as a comma-delimited list (in an otherwise binary protocol), so we have
to unpack and pack them into this cumbersome format alongside just
choosing which algorithm to use. In addition, the algorithm selection
mechanism differs depending on whether we're the client or server, and
what set of algorithms we're matching. Unlike SSL, which uses the
offered-suites/chosen-suites mechanism, in SSHv2 both sides offer a
selection of cipher suites and the server chooses the first one that
appears on both it and the client's list, with special-case handling for
the keyex and signature algorithms if the match isn't the first one on
the list. This means that the client can choose as it pleases from the
server's list if it waits for the server hello (see the comment in the
client/server hello handling code on the annoying nature of this portion
of the SSHv2 handshake), but the server has to perform a complex double-
match of its own vs.the client's list. The cases that we need to handle
are:
get the first matching algorithm, used by the server to match the client.
get the first matching algorithm and warn if it isn't the first one on
the list of possible algorithms, used by the server to match the
client for the keyex and public-key algorithms.
get the best matching algorithm (that is, the one corresponding to the
strongest crypto mechanism), used by the client to match the server.
This is a sufficiently complex and screwball function that we need to
define a composite structure to pass all of the control information in
and out */
typedef enum {
GETALGO_NONE, /* No match action */
GETALGO_FIRST_MATCH, /* Get first matching algorithm */
GETALGO_FIRST_MATCH_WARN,/* Get first matching algo, warn if not first */
GETALGO_BEST_MATCH, /* Get best matching algorithm */
GETALGO_LAST /* Last possible match action */
} GETALGO_TYPE;
typedef struct {
const ALGO_STRING_INFO *algoInfo;/* Algorithm selection info */
CRYPT_ALGO_TYPE preferredAlgo; /* Preferred algo for first-match */
GETALGO_TYPE getAlgoType; /* Type of match to perform */
CRYPT_ALGO_TYPE algo; /* Matched algorithm */
BOOLEAN prefAlgoMismatch; /* First match != preferredAlgo */
} ALGOID_INFO;
#define setAlgoIDInfo( algoIDInfo, algoStrInfo, prefAlgo, getType ) \
{ \
memset( ( algoIDInfo ), 0, sizeof( ALGOID_INFO ) ); \
( algoIDInfo )->algoInfo = ( algoStrInfo ); \
( algoIDInfo )->preferredAlgo = ( prefAlgo ); \
( algoIDInfo )->getAlgoType = ( getType ); \
}
static int readAlgoStringEx( STREAM *stream, ALGOID_INFO *algoIDInfo,
void *errorInfo )
{
BOOLEAN foundMatch = FALSE;
const char *string;
int stringPos, stringLen, substringLen, algoIndex = 999, status;
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( isWritePtr( algoIDInfo, sizeof( ALGOID_INFO ) ) );
assert( isReadPtr( algoIDInfo->algoInfo, sizeof( ALGO_STRING_INFO ) ) );
assert( ( algoIDInfo->getAlgoType == GETALGO_BEST_MATCH && \
algoIDInfo->preferredAlgo == CRYPT_ALGO_NONE ) || \
( algoIDInfo->getAlgoType == GETALGO_FIRST_MATCH ) ||
( algoIDInfo->getAlgoType == GETALGO_FIRST_MATCH_WARN && \
( algoIDInfo->preferredAlgo > CRYPT_ALGO_NONE && \
algoIDInfo->preferredAlgo < CRYPT_ALGO_LAST ) ) );
/* Get the string length and make sure that it's valid */
status = stringLen = readUint32( stream );
if( !cryptStatusError( status ) )
{
string = sMemBufPtr( stream );
status = sSkip( stream, stringLen );
if( cryptStatusOK( status ) && stringLen < SSH2_MIN_ALGOID_SIZE )
/* Quick-reject for too-short strings */
status = CRYPT_ERROR_BADDATA;
}
if( cryptStatusError( status ) )
retExt( errorInfo, CRYPT_ERROR_BADDATA,
"Invalid algorithm ID string" );
/* Walk down the string looking for a recognised algorithm. Since our
preference may not match the other side's preferences, we have to walk
down the entire list to find our preferred choice:
"algo1,algo2,algo3,algoN"
^ ^ ^
|substrLen |
stringPos stringLen */
for( stringPos = 0; stringPos < stringLen && !foundMatch; \
stringPos += substringLen + 1 )
{
int currentAlgoIndex;
/* Find the length of the next algorithm name */
for( substringLen = stringPos; \
substringLen < stringLen && string[ substringLen ] != ','; \
substringLen++ );
substringLen -= stringPos;
if( substringLen < SSH2_MIN_ALGOID_SIZE )
continue; /* Empty or too-short algorithm name, continue */
/* Check whether it's something that we can handle */
for( currentAlgoIndex = 0; \
algoIDInfo->algoInfo[ currentAlgoIndex ].name != NULL; \
currentAlgoIndex++ )
if( substringLen == strlen( algoIDInfo->algoInfo[ currentAlgoIndex ].name ) && \
!memcmp( algoIDInfo->algoInfo[ currentAlgoIndex ].name,
string + stringPos, substringLen ) )
break;
if( algoIDInfo->algoInfo[ currentAlgoIndex ].name == NULL || \
( !isPseudoAlgo( algoIDInfo->algoInfo[ currentAlgoIndex ].algo ) && \
!algoAvailable( algoIDInfo->algoInfo[ currentAlgoIndex ].algo ) ) )
{
/* No match or the matched algorithm isn't available in this
build, if we have to match the first algorithm on the list
remember to warn the caller, then move on to the next name */
if( algoIDInfo->getAlgoType == GETALGO_FIRST_MATCH_WARN )
algoIDInfo->prefAlgoMismatch = TRUE;
continue;
}
switch( algoIDInfo->getAlgoType )
{
case GETALGO_BEST_MATCH:
/* If we're looking for the best (highest-ranked algorithm)
match, see whether the current match ranks higher than
the existing one */
if( currentAlgoIndex < algoIndex )
{
algoIndex = currentAlgoIndex;
if( algoIndex <= 0 )
foundMatch = TRUE; /* Gruener werd's net */
}
break;
case GETALGO_FIRST_MATCH:
/* If we've found an acceptable algorithm, remember it and
exit */
if( algoIDInfo->preferredAlgo == CRYPT_ALGO_NONE || \
algoIDInfo->preferredAlgo == \
algoIDInfo->algoInfo[ currentAlgoIndex ].algo )
{
algoIndex = currentAlgoIndex;
foundMatch = TRUE;
}
break;
case GETALGO_FIRST_MATCH_WARN:
/* If we found the algorithm that we're after, remember it
and exit */
if( algoIDInfo->preferredAlgo != \
algoIDInfo->algoInfo[ currentAlgoIndex ].algo )
/* We didn't match the first algorithm on the list, warn
the caller */
algoIDInfo->prefAlgoMismatch = TRUE;
algoIndex = currentAlgoIndex;
foundMatch = TRUE;
break;
default:
assert( NOTREACHED );
}
}
if( algoIndex > 50 )
{
char algoString[ 256 ];
/* We couldn't find anything to use, tell the caller what was
available */
if( stringLen > min( MAX_ERRMSG_SIZE - 80, 255 ) )
stringLen = min( MAX_ERRMSG_SIZE - 80, 255 );
memcpy( algoString, string, stringLen );
algoString[ stringLen ] = '\0';
retExt( errorInfo, CRYPT_ERROR_NOTAVAIL,
"No algorithm compatible with the remote system's selection "
"was found : %s", sanitiseString( algoString ) );
}
/* We found a more-preferred algorithm than the default, go with that */
algoIDInfo->algo = algoIDInfo->algoInfo[ algoIndex ].algo;
return( CRYPT_OK );
}
int readAlgoString( STREAM *stream, const ALGO_STRING_INFO *algoInfo,
CRYPT_ALGO_TYPE *algo, const BOOLEAN useFirstMatch,
void *errorInfo )
{
ALGOID_INFO algoIDInfo;
int status;
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( isReadPtr( algoInfo, sizeof( ALGO_STRING_INFO ) ) );
assert( isWritePtr( algo, sizeof( CRYPT_ALGO_TYPE ) ) );
/* Clear return value */
*algo = CRYPT_ALGO_NONE;
setAlgoIDInfo( &algoIDInfo, algoInfo, CRYPT_ALGO_NONE,
useFirstMatch ? GETALGO_FIRST_MATCH : \
GETALGO_BEST_MATCH );
status = readAlgoStringEx( stream, &algoIDInfo, errorInfo );
if( cryptStatusOK( status ) )
*algo = algoIDInfo.algo;
return( status );
}
/* Algorithms used to protect data packets are used in pairs, one for
incoming and the other for outgoing data. To keep things simple we
always force these to be the same, first reading the algorithm for one
direction and then making sure that the one for the other direction
matches this. All implementations seem to do this anyway, many aren't
even capable of supporting asymmetric algorithm choices */
static int readAlgoStringPair( STREAM *stream, const ALGO_STRING_INFO *algoInfo,
CRYPT_ALGO_TYPE *algo, const BOOLEAN isServer,
void *errorInfo )
{
CRYPT_ALGO_TYPE pairPreferredAlgo;
ALGOID_INFO algoIDInfo;
int status;
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( isReadPtr( algoInfo, sizeof( ALGO_STRING_INFO ) ) );
/* Clear return value */
if( algo != NULL )
*algo = CRYPT_ALGO_NONE;
/* Get the first algorithm */
setAlgoIDInfo( &algoIDInfo, algoInfo, CRYPT_ALGO_NONE,
isServer ? GETALGO_FIRST_MATCH : GETALGO_BEST_MATCH );
status = readAlgoStringEx( stream, &algoIDInfo, errorInfo );
if( cryptStatusError( status ) )
return( status );
pairPreferredAlgo = algoIDInfo.algo;
/* Get the matched second algorithm */
setAlgoIDInfo( &algoIDInfo, algoInfo, pairPreferredAlgo,
GETALGO_FIRST_MATCH );
status = readAlgoStringEx( stream, &algoIDInfo, errorInfo );
if( cryptStatusError( status ) )
return( status );
if( pairPreferredAlgo != algoIDInfo.algo )
retExt( errorInfo, CRYPT_ERROR_BADDATA,
"Client algorithm %d doesn't match server algorithm %d in "
"algorithm pair", pairPreferredAlgo, algoIDInfo.algo );
if( algo != NULL )
*algo = algoIDInfo.algo;
return( status );
}
/* Convert a cryptlib algorithm ID to an SSHv2 algorithm name */
int writeAlgoString( STREAM *stream, const CRYPT_ALGO_TYPE algo )
{
static const FAR_BSS ALGO_STRING_INFO algoStringMapTbl[] = {
{ "ssh-rsa", CRYPT_ALGO_RSA },
{ "ssh-dss", CRYPT_ALGO_DSA },
{ "3des-cbc", CRYPT_ALGO_3DES },
{ "aes128-cbc", CRYPT_ALGO_AES },
{ "blowfish-cbc", CRYPT_ALGO_BLOWFISH },
{ "cast128-cbc", CRYPT_ALGO_CAST },
{ "idea-cbc", CRYPT_ALGO_IDEA },
{ "arcfour", CRYPT_ALGO_RC4 },
{ "diffie-hellman-group-exchange-sha1", CRYPT_PSEUDOALGO_DHE },
{ "diffie-hellman-group1-sha1", CRYPT_ALGO_DH },
{ "hmac-sha1", CRYPT_ALGO_HMAC_SHA },
{ "hmac-md5", CRYPT_ALGO_HMAC_MD5 },
{ "none", CRYPT_PSEUDOALGO_COPR },
{ "none", CRYPT_ALGO_LAST } /* Catch-all */
};
int i;
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( algo >= CRYPT_ALGO_NONE && algo < CRYPT_ALGO_LAST );
/* Locate the name for this algorithm and encode it as an SSH string */
for( i = 0; algoStringMapTbl[ i ].algo != CRYPT_ALGO_LAST && \
algoStringMapTbl[ i ].algo != algo; i++ );
assert( algoStringMapTbl[ i ].algo != CRYPT_ALGO_LAST );
return( writeString32( stream, algoStringMapTbl[ i ].name, 0 ) );
}
/****************************************************************************
* *
* Miscellaneous Functions *
* *
****************************************************************************/
/* Process a client/server hello packet */
int processHelloSSH( SESSION_INFO *sessionInfoPtr,
SSH_HANDSHAKE_INFO *handshakeInfo, int *keyexLength,
const BOOLEAN isServer )
{
STREAM stream;
ALGOID_INFO algoIDInfo;
BOOLEAN preferredAlgoMismatch = FALSE, guessedKeyex = FALSE;
int length, status;
/* Process the client/server hello:
byte type = SSH2_MSG_KEXINIT
byte[16] cookie
string keyex algorithms
string pubkey algorithms
string client_crypto algorithms
string server_crypto algorithms
string client_mac algorithms
string server_mac algorithms
string client_compression algorithms
string server_compression algorithms
string client_language
string server_language
boolean first_keyex_packet_follows
uint32 reserved
The cookie isn't explicitly processed as with SSHv1 since SSHv2
hashes the entire hello message */
length = readPacketSSH2( sessionInfoPtr, SSH2_MSG_KEXINIT, 128 );
if( cryptStatusError( length ) )
return( length );
*keyexLength = length;
sMemConnect( &stream, sessionInfoPtr->receiveBuffer, length );
sSkip( &stream, ID_SIZE + SSH2_COOKIE_SIZE );
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -