📄 ssh2.c
字号:
/****************************************************************************
* *
* cryptlib SSHv2 Session Management *
* Copyright Peter Gutmann 1998-2008 *
* *
****************************************************************************/
#if defined( INC_ALL )
#include "crypt.h"
#include "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_SSH
/* 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 ALGO_STRING_INFO FAR_BSS algoStringKeyexTbl[] = {
{ "diffie-hellman-group-exchange-sha1", 34, CRYPT_PSEUDOALGO_DHE },
{ "diffie-hellman-group1-sha1", 26, CRYPT_ALGO_DH },
{ NULL, 0, CRYPT_ALGO_NONE }, { NULL, 0, CRYPT_ALGO_NONE }
};
static const ALGO_STRING_INFO FAR_BSS algoStringCoprTbl[] = {
{ "none", 4, CRYPT_PSEUDOALGO_COPR },
{ NULL, 0, CRYPT_ALGO_NONE }, { NULL, 0, CRYPT_ALGO_NONE }
};
static const ALGO_STRING_INFO FAR_BSS algoStringPubkeyTbl[] = {
{ "ssh-rsa", 7, CRYPT_ALGO_RSA },
{ "ssh-dss", 7, CRYPT_ALGO_DSA },
{ NULL, 0, CRYPT_ALGO_NONE }, { NULL, 0, CRYPT_ALGO_NONE }
};
static const ALGO_STRING_INFO FAR_BSS algoStringEncrTblClient[] = {
{ "3des-cbc", 8, CRYPT_ALGO_3DES },
{ "aes128-cbc", 10, CRYPT_ALGO_AES },
{ "blowfish-cbc", 12, CRYPT_ALGO_BLOWFISH },
{ "cast128-cbc", 11, CRYPT_ALGO_CAST },
{ "idea-cbc", 8, CRYPT_ALGO_IDEA },
{ "arcfour", 7, CRYPT_ALGO_RC4 },
{ NULL, 0, CRYPT_ALGO_NONE }, { NULL, 0, CRYPT_ALGO_NONE }
};
static const ALGO_STRING_INFO FAR_BSS algoStringEncrTblServer[] = {
{ "3des-cbc", 8, CRYPT_ALGO_3DES },
{ "blowfish-cbc", 12, CRYPT_ALGO_BLOWFISH },
{ "cast128-cbc", 11, CRYPT_ALGO_CAST },
{ "idea-cbc", 8, CRYPT_ALGO_IDEA },
{ "arcfour", 7, CRYPT_ALGO_RC4 },
{ NULL, 0, CRYPT_ALGO_NONE }, { NULL, 0, CRYPT_ALGO_NONE }
};
static const ALGO_STRING_INFO FAR_BSS algoStringMACTbl[] = {
{ "hmac-sha1", 9, CRYPT_ALGO_HMAC_SHA },
{ "hmac-md5", 8, CRYPT_ALGO_HMAC_MD5 },
{ NULL, 0, CRYPT_ALGO_NONE }, { NULL, 0, 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 */
int noAlgoInfoEntries;
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, algoStrInfoEntries, prefAlgo, getType ) \
{ \
memset( ( algoIDInfo ), 0, sizeof( ALGOID_INFO ) ); \
( algoIDInfo )->algoInfo = ( algoStrInfo ); \
( algoIDInfo )->noAlgoInfoEntries = ( algoStrInfoEntries ); \
( algoIDInfo )->preferredAlgo = ( prefAlgo ); \
( algoIDInfo )->getAlgoType = ( getType ); \
}
static int readAlgoStringEx( STREAM *stream, ALGOID_INFO *algoIDInfo,
INOUT ERROR_INFO *errorInfo )
{
BOOLEAN foundMatch = FALSE;
void *string = DUMMY_INIT_PTR;
int stringPos, stringLen, substringLen, algoIndex = 999;
int iterationCount = 0, status;
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( isWritePtr( algoIDInfo, sizeof( ALGOID_INFO ) ) );
assert( isReadPtr( algoIDInfo->algoInfo, \
sizeof( ALGO_STRING_INFO ) * \
algoIDInfo->noAlgoInfoEntries ) );
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 data and make sure that it's valid */
status = stringLen = readUint32( stream );
if( !cryptStatusError( status ) && stringLen < SSH2_MIN_ALGOID_SIZE )
status = CRYPT_ERROR_BADDATA; /* Quick-rej. for too-short strings */
if( !cryptStatusError( status ) )
status = sMemGetDataBlock( stream, &string, stringLen );
if( cryptStatusOK( status ) )
status = sSkip( stream, stringLen );
if( cryptStatusError( status ) )
{
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, errorInfo,
"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 && \
iterationCount++ < FAILSAFE_ITERATIONS_LARGE;
stringPos += substringLen + 1 )
{
const BYTE *stringPtr = string;
int currentAlgoIndex;
/* Find the length of the next algorithm name */
for( substringLen = stringPos; \
substringLen < stringLen && stringPtr[ substringLen ] != ','; \
substringLen++ );
substringLen -= stringPos;
if( substringLen < SSH2_MIN_ALGOID_SIZE || \
substringLen > CRYPT_MAX_TEXTSIZE )
{
/* Empty or too-short algorithm name (or excessively long one),
continue */
continue;
}
/* Check whether it's something that we can handle */
for( currentAlgoIndex = 0;
algoIDInfo->algoInfo[ currentAlgoIndex ].name != NULL && \
currentAlgoIndex < algoIDInfo->noAlgoInfoEntries && \
currentAlgoIndex < FAILSAFE_ITERATIONS_MED;
currentAlgoIndex++ )
{
if( substringLen == algoIDInfo->algoInfo[ currentAlgoIndex ].nameLen && \
!memcmp( algoIDInfo->algoInfo[ currentAlgoIndex ].name,
stringPtr + stringPos, substringLen ) )
break;
}
if( currentAlgoIndex >= algoIDInfo->noAlgoInfoEntries || \
currentAlgoIndex >= FAILSAFE_ITERATIONS_MED )
retIntError();
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:
retIntError();
}
}
if( iterationCount >= FAILSAFE_ITERATIONS_LARGE )
retIntError();
if( algoIndex > 50 )
{
char algoString[ 256 + 8 ];
const int algoStringLen = min( stringLen, \
min( MAX_ERRMSG_SIZE - 80, 255 ) );
/* We couldn't find anything to use, tell the caller what was
available */
if( algoStringLen > 0 )
memcpy( algoString, string, algoStringLen );
retExt( CRYPT_ERROR_NOTAVAIL,
( CRYPT_ERROR_NOTAVAIL, errorInfo,
"No algorithm compatible with the remote system's "
"selection was found: '%s'",
sanitiseString( algoString, 256, stringLen ) ) );
}
/* 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,
const int noAlgoStringEntries, CRYPT_ALGO_TYPE *algo,
const BOOLEAN useFirstMatch,
INOUT ERROR_INFO *errorInfo )
{
ALGOID_INFO algoIDInfo;
int status;
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( isReadPtr( algoInfo, sizeof( ALGO_STRING_INFO ) * \
noAlgoStringEntries ) );
assert( isWritePtr( algo, sizeof( CRYPT_ALGO_TYPE ) ) );
/* Clear return value */
*algo = CRYPT_ALGO_NONE;
setAlgoIDInfo( &algoIDInfo, algoInfo, noAlgoStringEntries,
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 */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -