📄 ssh2_cli.c
字号:
/****************************************************************************
* *
* cryptlib SSHv2 Session Management *
* Copyright Peter Gutmann 1998-2003 *
* *
****************************************************************************/
#include <stdlib.h>
#include <string.h>
#if defined( INC_ALL )
#include "crypt.h"
#include "session.h"
#include "ssh.h"
#elif defined( INC_CHILD )
#include "../crypt.h"
#include "../session/session.h"
#include "../session/ssh.h"
#else
#include "crypt.h"
#include "session/session.h"
#include "session/ssh.h"
#endif /* Compiler-specific includes */
#ifdef USE_SSH2
/****************************************************************************
* *
* Utility Functions *
* *
****************************************************************************/
/* Generate/check an SSHv2 key fingerprint. This is simply an MD5 hash of
the server's key/certificate data */
static int processKeyFingerprint( SESSION_INFO *sessionInfoPtr,
const void *keyData,
const int keyDataLength )
{
HASHFUNCTION hashFunction;
BYTE fingerPrint[ CRYPT_MAX_HASHSIZE ];
int hashSize;
getHashParameters( CRYPT_ALGO_MD5, &hashFunction, &hashSize );
hashFunction( NULL, fingerPrint, keyData, keyDataLength, HASH_ALL );
if( sessionInfoPtr->keyFingerprintSize > 0 )
{
/* In the unlikely event that the user has passed us an SHA-1
fingerprint (which isn't allowed by the spec, but no doubt
someone out there's using it based on the fact that the SSH
architecture draft suggests an SHA-1 fingerprint while the SSH
fingerprint draft requires an MD5 fingerprint), calculate that
instead */
if( sessionInfoPtr->keyFingerprintSize == 20 )
{
getHashParameters( CRYPT_ALGO_SHA, &hashFunction, &hashSize );
hashFunction( NULL, fingerPrint, keyData, keyDataLength,
HASH_ALL );
}
/* There's an existing fingerprint value, make sure that it matches
what we just calculated */
if( memcmp( sessionInfoPtr->keyFingerprint, fingerPrint, hashSize ) )
retExt( sessionInfoPtr, CRYPT_ERROR_WRONGKEY,
"Server key fingerprint doesn't match requested "
"fingerprint" );
}
else
{
/* Remember the value for the caller */
memcpy( sessionInfoPtr->keyFingerprint, fingerPrint, hashSize );
sessionInfoPtr->keyFingerprintSize = hashSize;
}
return( CRYPT_OK );
}
/* Create a request for the appropriate type of service, either encrypted-
telnet, SFTP, or port forwarding */
static int createOpenRequest( SESSION_INFO *sessionInfoPtr,
BYTE *buffer )
{
BYTE *bufPtr = buffer + SSH2_HEADER_SIZE;
int length, status;
/* If the user has requested the use of a custom subsystem (and at the
moment the only one that's likely to be used is SFTP), request this
from the server */
if( sessionInfoPtr->sshSubsystemLength > 0 )
{
/* ...
byte type = SSH2_MSG_CHANNEL_REQUEST
uint32 recipient_channel = 0
string request_name = "subsystem"
boolean want_reply = FALSE
string subsystem_name */
*bufPtr++ = SSH2_MSG_CHANNEL_REQUEST;
mputLong( bufPtr, 0 );
bufPtr += encodeString( bufPtr, "subsystem", 0 );
*bufPtr++ = 0;
bufPtr += encodeString( bufPtr, sessionInfoPtr->sshSubsystem,
sessionInfoPtr->sshSubsystemLength );
return( wrapPacket( sessionInfoPtr, buffer,
bufPtr - ( buffer + SSH2_HEADER_SIZE ) ) );
}
/* If the user has requested port-forwarding, request this from the
server */
if( sessionInfoPtr->sshPortForwardLength > 0 )
{
URL_INFO urlInfo;
/* ...
byte type = SSH_MSG_GLOBAL_REQUEST
string request_name = "tcpip-forward"
boolean want_reply
string address_to_bind (e.g. "0.0.0.0")
uint32 port_to_bind
The exact details of what we should send at this stage of the
handshake are a bit unclear. Most implementations go through the
standard channel open process to provide a general control channel
and then specify the port-forwarding in addition to this (see
processChannelOpen() for the hoops we have to jump through to
handle this). This double-open can cause problems with some
applications hanging off the tunnel because they may see the
output from opening the channel and starting a shell as tunnelled
data and get confused by it. The safest option seems to be to
only open the forwarded channel, without opening a (mostly
redundant) control channel */
sNetParseURL( &urlInfo, sessionInfoPtr->sshPortForward,
sessionInfoPtr->sshPortForwardLength );
bufPtr = buffer + SSH2_HEADER_SIZE;
*bufPtr++ = SSH2_MSG_GLOBAL_REQUEST;
bufPtr += encodeString( bufPtr, "tcpip-forward", 0 );
*bufPtr++ = 0;
bufPtr += encodeString( bufPtr, urlInfo.host, urlInfo.hostLen );
mputLong( bufPtr, urlInfo.port );
return( wrapPacket( sessionInfoPtr, buffer,
bufPtr - ( buffer + SSH2_HEADER_SIZE ) ) );
}
/* It's a standard channel open:
...
byte type = SSH2_MSG_CHANNEL_REQUEST
uint32 recipient_channel = 0
string request_name = "pty-req"
boolean want_reply = FALSE
string TERM_environment_variable = "vt100"
uint32 cols = 80
uint32 rows = 24
uint32 pixel_width = 0
uint32 pixel_height = 0
string tty_mode_info = ""
... */
*bufPtr++ = SSH2_MSG_CHANNEL_REQUEST;
mputLong( bufPtr, 0 );
bufPtr += encodeString( bufPtr, "pty-req", 0 );
*bufPtr++ = 0;
bufPtr += encodeString( bufPtr, "vt100", 0 );/* Generic */
mputLong( bufPtr, 80 );
mputLong( bufPtr, 24 ); /* 24 x 80 */
mputLong( bufPtr, 0 );
mputLong( bufPtr, 0 ); /* No graphics capabilities */
bufPtr += encodeString( bufPtr, "", 0 );/* No special TTY modes */
status = length = wrapPacket( sessionInfoPtr, buffer,
bufPtr - ( buffer + SSH2_HEADER_SIZE ) );
if( cryptStatusError( status ) )
return( status );
/* ...
byte type = SSH2_MSG_CHANNEL_REQUEST
uint32 recipient_channel = 0
string request_name = "shell"
boolean want_reply = FALSE
This final request, once sent, moves the server into interactive
session mode, if we're talking to a standard Unix server implementing
a remote shell we could read the stdout data response from starting
the shell but this may not be the case so we leave the response for
the user to process explicitly */
bufPtr = buffer + length + SSH2_HEADER_SIZE;
*bufPtr++ = SSH2_MSG_CHANNEL_REQUEST;
mputLong( bufPtr, 0 );
bufPtr += encodeString( bufPtr, "shell", 0 );
*bufPtr++ = 0;
status = wrapPacket( sessionInfoPtr, buffer + length,
bufPtr - ( buffer + length + SSH2_HEADER_SIZE ) );
return( cryptStatusError( status ) ? status : length + status );
}
/****************************************************************************
* *
* Client-side Connect Functions *
* *
****************************************************************************/
/* Perform the initial part of the handshake with the server */
static int beginClientHandshake( SESSION_INFO *sessionInfoPtr,
SSH_HANDSHAKE_INFO *handshakeInfo )
{
MESSAGE_CREATEOBJECT_INFO createInfo;
KEYAGREE_PARAMS keyAgreeParams;
RESOURCE_DATA msgData;
BYTE *bufPtr;
int length, length2, serverKeyexLength, clientKeyexLength;
int mpiLength, status;
/* The higher-level code has already read the server session info, send
back our own version info (SSHv2 uses a CR and LF as terminator,
which differs from SSHv1) */
length = strlen( SSH2_ID_STRING );
memcpy( sessionInfoPtr->sendBuffer, SSH2_ID_STRING "\r\n", length + 2 );
status = swrite( &sessionInfoPtr->stream, sessionInfoPtr->sendBuffer,
length + 2 );
if( cryptStatusError( status ) )
{
sNetGetErrorInfo( &sessionInfoPtr->stream,
sessionInfoPtr->errorMessage,
&sessionInfoPtr->errorCode );
return( status );
}
/* SSHv2 hashes parts of the handshake messages for integrity-protection
purposes, so we hash the ID strings (first our client string, then the
server string that we read previously) encoded as SSH string values */
hashAsString( handshakeInfo->iExchangeHashcontext, SSH2_ID_STRING,
length );
hashAsString( handshakeInfo->iExchangeHashcontext,
sessionInfoPtr->receiveBuffer,
strlen( sessionInfoPtr->receiveBuffer ) );
/* While we wait for the server to digest our version info and send
back its response, we can create the context with the DH key and
perform phase 1 of the DH key agreement process */
status = initDHcontext( &handshakeInfo->iServerCryptContext,
&handshakeInfo->serverKeySize, NULL, 0,
CRYPT_USE_DEFAULT );
if( cryptStatusOK( status ) )
{
memset( &keyAgreeParams, 0, sizeof( KEYAGREE_PARAMS ) );
status = krnlSendMessage( handshakeInfo->iServerCryptContext,
IMESSAGE_CTX_ENCRYPT, &keyAgreeParams,
sizeof( KEYAGREE_PARAMS ) );
}
if( cryptStatusError( status ) )
return( status );
/* Process the server hello */
status = processHello( sessionInfoPtr, handshakeInfo,
&serverKeyexLength, FALSE );
if( cryptStatusError( status ) )
return( status );
/* Build the client hello and DH phase 1 keyex packet:
byte type = SSH2_MSG_KEXINIT
byte[16] cookie
string keyex algorithms = DH
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 whole 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, so what we do is wait for the server
hello (already done earlier), choose known-good algorithms, and then
send the client hello immediately followed by the client keyex.
Since we wait for the server to speak first, we can choose parameters
that are accepted the first time. In theory this means that we can
set keyex_follows to true (since a correct keyex packet always
follows the hello), however because of the nondeterministic initial
exchange the spec requires that a (guessed) keyex be discarded by the
server if the hello doesn't match (even if the keyex does):
svr: hello
client: matched hello, keyex
svr: (discard keyex)
To avoid this problem, we set keyex_follows to false to make it clear
to the server that the keyex is the real thing and shouldn't be
discarded */
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;
if( handshakeInfo->requestedServerKeySize > 0 )
bufPtr += encodeString( bufPtr, "diffie-hellman-group-exchange-sha1",
0 );
else
putAlgoID( &bufPtr, CRYPT_ALGO_DH );
putAlgoID( &bufPtr, handshakeInfo->pubkeyAlgo );
putAlgoID( &bufPtr, sessionInfoPtr->cryptAlgo );
putAlgoID( &bufPtr, sessionInfoPtr->cryptAlgo );
putAlgoID( &bufPtr, sessionInfoPtr->integrityAlgo );
putAlgoID( &bufPtr, sessionInfoPtr->integrityAlgo );
putAlgoID( &bufPtr, CRYPT_ALGO_NONE );
putAlgoID( &bufPtr, CRYPT_ALGO_NONE );
mputLong( bufPtr, 0 ); /* No language tag */
mputLong( bufPtr, 0 );
*bufPtr++ = 0; /* Tell the server not to discard the packet */
mputLong( bufPtr, 0 ); /* Reserved */
clientKeyexLength = ( int ) ( bufPtr - \
( sessionInfoPtr->sendBuffer + SSH2_HEADER_SIZE ) );
status = length = wrapPacket( sessionInfoPtr,
sessionInfoPtr->sendBuffer, clientKeyexLength );
if( cryptStatusError( status ) )
return( status );
/* Hash the client and server hello messages. We have to do this now
(rather than deferring it until we're waiting on network traffic from
the server) because they may get overwritten by the keyex negotiation
if we're using a non-builtin DH key value */
hashAsString( handshakeInfo->iExchangeHashcontext,
sessionInfoPtr->sendBuffer + SSH2_HEADER_SIZE,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -