📄 ssh2_svr.c
字号:
};
static const FAR_BSS ALGO_STRING_INFO algoStringPubkeyDSATbl[] = {
{ "ssh-dss", CRYPT_ALGO_DSA },
{ NULL, CRYPT_ALGO_NONE }
};
RESOURCE_DATA msgData;
BYTE *bufPtr;
int length, serverKeyexLength, clientKeyexLength, status;
/* Get the public-key algorithm that we'll be advertising to the client
and set the algorithm table used for processing the client hello to
only match the one that we're offering */
status = krnlSendMessage( sessionInfoPtr->privateKey,
IMESSAGE_GETATTRIBUTE,
&handshakeInfo->pubkeyAlgo,
CRYPT_CTXINFO_ALGO );
if( cryptStatusError( status ) )
return( status );
switch( handshakeInfo->pubkeyAlgo )
{
case CRYPT_ALGO_RSA:
handshakeInfo->algoStringPubkeyTbl = algoStringPubkeyRSATbl;
break;
case CRYPT_ALGO_DSA:
handshakeInfo->algoStringPubkeyTbl = algoStringPubkeyDSATbl;
break;
default:
assert( NOTREACHED );
return( CRYPT_ERROR_FAILED );
}
/* SSHv2 hashes parts of the handshake messages for integrity-protection
purposes, so we hash the ID strings (first the client string that we
read previously, then our server string) encoded as SSH string
values */
hashAsString( handshakeInfo->iExchangeHashcontext,
sessionInfoPtr->receiveBuffer,
strlen( sessionInfoPtr->receiveBuffer ) );
hashAsString( handshakeInfo->iExchangeHashcontext, SSH2_ID_STRING,
strlen( SSH2_ID_STRING ) );
/* Send the server hello packet:
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 = "none"
string server_compression algorithms = "none"
string client_language = {}
string server_language = {}
boolean first_keyex_packet_follows = FALSE
uint32 reserved = 0
The SSH spec leaves the order in which things happen ambiguous, in
order to save a while round trip it has provisions for both sides
shouting at each other and then a complex interlock process where
bits of the initial exchange can be discarded and retried if necessary.
This is ugly and error-prone. The client code solves this by waiting
for the server hello, choosing known-good algorithms, and then sending
the client hello immediately followed by the client key exchange data.
Since it waits for the server to speak first, it can choose parameters
that are accepted the first time.
Unfortunately, this doesn't work if we're the server, since we'd end
up waiting for the client to speak first while it waits for us to
speak first, so we have to send the server hello in order to prevent
deadlock. This works fine with almost all clients tested, which take
the same approach and wait for the server to speak first. The message
flow is then:
server hello;
client hello;
client keyex;
server keyex;
One exception to this is the F-Secure client, which has the client
speak first, choosing as its preference the incompletely specified
"x509v3-sign-dss" format (see the comment in exchangeServerKeys()
below) that we can't use since no-one's quite sure what the format is.
In this case the message flow is:
server hello;
client hello;
client keyex1;
client keyex2;
server keyex;
This is handled by having the code that reads the client hello return
OK_SPECIAL to indicate that the next packet should be skipped. An
alternative (and simpler) strategy would be to always throw away the
F-Secure client's first keyex, since it's using an algorithm choice
that's impossible to use */
bufPtr = sessionInfoPtr->sendBuffer + SSH2_HEADER_SIZE;
*bufPtr++ = SSH2_MSG_KEXINIT;
setMessageData( &msgData, bufPtr, SSH2_COOKIE_SIZE );
krnlSendMessage( SYSTEM_OBJECT_HANDLE, IMESSAGE_GETATTRIBUTE_S,
&msgData, CRYPT_IATTRIBUTE_RANDOM_NONCE );
bufPtr += SSH2_COOKIE_SIZE;
putAlgoList( &bufPtr, algoKeyexList );
putAlgoID( &bufPtr, handshakeInfo->pubkeyAlgo );
putAlgoList( &bufPtr, algoEncrList );
putAlgoList( &bufPtr, algoEncrList );
putAlgoList( &bufPtr, algoMACList );
putAlgoList( &bufPtr, algoMACList );
bufPtr += encodeString( bufPtr, algoStringCoprList, 0 );
bufPtr += encodeString( bufPtr, algoStringCoprList, 0 );
mputLong( bufPtr, 0 ); /* No language tag */
mputLong( bufPtr, 0 );
*bufPtr++ = 0; /* Don't try and guess the keyex */
mputLong( bufPtr, 0 ); /* Reserved */
serverKeyexLength = ( int ) \
( bufPtr - ( sessionInfoPtr->sendBuffer + SSH2_HEADER_SIZE ) );
status = sendPacketSSH2( sessionInfoPtr, serverKeyexLength, FALSE );
if( cryptStatusError( status ) )
return( status );
/* While we wait for the client to digest our hello and send back its
response, create the context with the DH key */
status = initDHcontext( &handshakeInfo->iServerCryptContext,
&handshakeInfo->serverKeySize, NULL, 0,
CRYPT_USE_DEFAULT );
if( cryptStatusError( status ) )
return( status );
/* Process the client hello packet */
status = processHello( sessionInfoPtr, handshakeInfo,
&clientKeyexLength, TRUE );
if( status == OK_SPECIAL )
/* There's an incorrectly-guessed keyex following the client
hello, skip it */
status = readPacketSSH2( sessionInfoPtr,
( handshakeInfo->requestedServerKeySize > 0 ) ? \
SSH2_MSG_KEXDH_GEX_INIT : SSH2_MSG_KEXDH_INIT );
if( cryptStatusError( status ) )
return( status );
/* Hash the client and server hello messages */
hashAsString( handshakeInfo->iExchangeHashcontext,
sessionInfoPtr->receiveBuffer, clientKeyexLength );
status = hashAsString( handshakeInfo->iExchangeHashcontext,
sessionInfoPtr->sendBuffer + SSH2_HEADER_SIZE,
serverKeyexLength );
if( cryptStatusError( status ) )
return( status );
/* If we're using a nonstandard DH key value, negotiate a new key with
the client */
if( handshakeInfo->requestedServerKeySize > 0 )
{
BOOLEAN isExtReq = FALSE;
const int extraLength = LENGTH_SIZE + ( LENGTH_SIZE + 6 );
int keySize;
/* Get the keyex key request from the client. Portions of the the
request info are hashed later on as part of the exchange hash, so
we have to save a copy for then. Note that we save the original
encoded form, because some clients send non-integral lengths that
don't survive the conversion from bits to bytes */
length = readPacketSSH2( sessionInfoPtr, SSH2_MSG_KEXDH_GEX_REQUEST );
if( cryptStatusError( length ) )
return( length );
bufPtr = sessionInfoPtr->receiveBuffer;
if( *bufPtr++ == SSH2_MSG_KEXDH_GEX_REQUEST_NEW )
{
/* It's a { min_length, length, max_length } sequence, save a
copy and get the length value */
memcpy( handshakeInfo->encodedReqKeySizes, bufPtr, UINT_SIZE * 3 );
handshakeInfo->encodedReqKeySizesLength = UINT_SIZE * 3;
bufPtr += UINT_SIZE;
keySize = ( int ) mgetLong( bufPtr );
bufPtr += UINT_SIZE;
}
else
{
/* It's a straight length, save a copy and get the length
value */
memcpy( handshakeInfo->encodedReqKeySizes, bufPtr, UINT_SIZE );
handshakeInfo->encodedReqKeySizesLength = UINT_SIZE;
keySize = ( int ) mgetLong( bufPtr );
}
if( keySize < MIN_PKCSIZE_BITS || \
keySize > bytesToBits( CRYPT_MAX_PKCSIZE ) )
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Client requested invalid ephemeral DH key size %d bits",
keySize );
handshakeInfo->requestedServerKeySize = bitsToBytes( keySize );
/* If the requested key size differs too much from the built-in
default one, destroy the existing default DH key and load a new
one of the appropriate size. Things get quite confusing here
because the spec is a schizophrenic mix of two different
documents, one that specifies the behaviour for the original
message format which uses a single length value and a second one
that specifies the behaviour for the { min, n, max } combination.
The range option was added as an attempted fix for
implementations that couldn't handle the single size option, but
the real problem is that the server knows what key sizes are
appropriate but the client has to make the choice, without any
indication of what the server can actually handle. Because of
this the spec (in its n-only mindset, which also applies to the
min/n/max version since it's the same document) contains assorted
weasel-words that allow the server to choose any key size it
feels like if the client sends a range indication that's
inappropriate. Although the spec ends up saying that the server
can do anything it feels like ("The server should return the
smallest group it knows that is larger than the size the client
requested. If the server does not know a group that is larger
than the client request, then it SHOULD return the largest group
it knows"), we use a least-upper-bound interpretation of the
above, mostly because we store a range of fixed keys of different
sizes and can always find something reasonably close to any
(sensible) requested length */
if( handshakeInfo->requestedServerKeySize < \
SSH2_DEFAULT_KEYSIZE - 16 || \
handshakeInfo->requestedServerKeySize > \
SSH2_DEFAULT_KEYSIZE + 16 )
{
krnlSendNotifier( handshakeInfo->iServerCryptContext,
IMESSAGE_DECREFCOUNT );
status = initDHcontext( &handshakeInfo->iServerCryptContext,
&handshakeInfo->serverKeySize, NULL, 0,
handshakeInfo->requestedServerKeySize );
if( cryptStatusError( status ) )
return( status );
}
/* Send the DH key values to the client:
byte type = SSH2_MSG_KEXDH_GEX_GROUP
mpint p
mpint g
Since this phase of the key negotiation exchanges raw key
components rather than the standard SSH public-key format, we
have to rewrite the public key before we can send it to the
client */
bufPtr = sessionInfoPtr->sendBuffer + SSH2_HEADER_SIZE;
*bufPtr++ = SSH2_MSG_KEXDH_GEX_GROUP;
setMessageData( &msgData, bufPtr, 128 + ( CRYPT_MAX_PKCSIZE * 2 ) );
status = krnlSendMessage( handshakeInfo->iServerCryptContext,
IMESSAGE_GETATTRIBUTE_S, &msgData,
CRYPT_IATTRIBUTE_KEY_SSH2 );
if( cryptStatusError( status ) )
return( status );
length = msgData.length - extraLength;
memmove( bufPtr, bufPtr + extraLength, length );
status = sendPacketSSH2( sessionInfoPtr, ID_SIZE + length, FALSE );
if( cryptStatusError( status ) )
return( status );
}
/* Process the client keyex:
byte type = SSH2_MSG_KEXDH_INIT / SSH2_MSG_KEXDH_GEX_INIT
mpint y */
length = readPacketSSH2( sessionInfoPtr,
( handshakeInfo->requestedServerKeySize > 0 ) ? \
SSH2_MSG_KEXDH_GEX_INIT : SSH2_MSG_KEXDH_INIT );
if( cryptStatusError( length ) )
return( length );
bufPtr = sessionInfoPtr->receiveBuffer + ID_SIZE;
clientKeyexLength = ( int ) mgetLong( bufPtr );
if( length != ID_SIZE + LENGTH_SIZE + clientKeyexLength || \
clientKeyexLength < handshakeInfo->serverKeySize - 8 || \
clientKeyexLength > handshakeInfo->serverKeySize + 1 )
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Invalid client keyex packet length %d, keyex length %d",
length, clientKeyexLength );
memcpy( handshakeInfo->clientKeyexValue, bufPtr - LENGTH_SIZE,
LENGTH_SIZE + clientKeyexLength );
handshakeInfo->clientKeyexValueLength = LENGTH_SIZE + clientKeyexLength;
return( CRYPT_OK );
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -