📄 ssh2_cli.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. There are two of these, one that favours
password-based authentication and one that favours PKC-based
authentication, depending on whether the user has specified a password
or PKC as their authentication choice */
static const FAR_BSS ALGO_STRING_INFO algoStringUserauthentPWTbl[] = {
{ "password", CRYPT_PSEUDOALGO_PASSWORD },
{ "keyboard-interactive", CRYPT_PSEUDOALGO_PAM },
{ "publickey", CRYPT_ALGO_RSA },
{ NULL, CRYPT_ALGO_NONE }
};
static const FAR_BSS ALGO_STRING_INFO algoStringUserauthentPKCTbl[] = {
{ "publickey", CRYPT_ALGO_RSA },
{ "password", CRYPT_PSEUDOALGO_PASSWORD },
{ "keyboard-interactive", CRYPT_PSEUDOALGO_PAM },
{ NULL, CRYPT_ALGO_NONE }
};
/****************************************************************************
* *
* 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;
const ATTRIBUTE_LIST *attributeListPtr = \
findSessionAttribute( sessionInfoPtr->attributeList,
CRYPT_SESSINFO_SERVER_FINGERPRINT );
BYTE fingerPrint[ CRYPT_MAX_HASHSIZE ];
int hashSize;
getHashParameters( CRYPT_ALGO_MD5, &hashFunction, &hashSize );
hashFunction( NULL, fingerPrint, keyData, keyDataLength, HASH_ALL );
if( attributeListPtr == NULL )
/* Remember the value for the caller */
return( addSessionAttribute( &sessionInfoPtr->attributeList,
CRYPT_SESSINFO_SERVER_FINGERPRINT,
fingerPrint, hashSize ) );
/* In the unlikely event that the user has passed us a 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( attributeListPtr->valueLength == 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( attributeListPtr->valueLength != hashSize || \
memcmp( attributeListPtr->value, fingerPrint, hashSize ) )
retExt( sessionInfoPtr, CRYPT_ERROR_WRONGKEY,
"Server key fingerprint doesn't match requested "
"fingerprint" );
return( CRYPT_OK );
}
/* Report specific details on an authentication failure to the caller */
static int processPamAuthentication( SESSION_INFO *sessionInfoPtr ); /* Fwd.dec for fn.*/
static int reportAuthFailure( SESSION_INFO *sessionInfoPtr,
const int length, const BOOLEAN isPamAuth )
{
STREAM stream;
CRYPT_ALGO_TYPE authentAlgo;
const BOOLEAN hasPassword = \
( findSessionAttribute( sessionInfoPtr->attributeList,
CRYPT_SESSINFO_PASSWORD ) != NULL ) ? \
TRUE : FALSE;
int status;
/* The authentication failed, pick apart the response to see if we can
return more meaningful error info:
byte type = SSH2_MSG_USERAUTH_FAILURE
string available_auth_types
boolean partial_success
We decode the response to favour password- or PKC-based
authentication depending on whether the user specified a password
or PKC as their authentication choice.
God knows how the partial_success flag is really meant to be applied
(there are a whole pile of odd conditions surrounding changed
passwords and similar issues), according to the spec it means that the
authentication was successful, however the packet type indicates that
the authentication failed and something else is needed. This whole
section of the protocol winds up in an extremely complex state machine
with all sorts of special-case conditions, several of which require
manual intervention by the user. It's easiest to not even try and
handle this stuff */
sMemConnect( &stream, sessionInfoPtr->receiveBuffer, length );
sgetc( &stream ); /* Skip packet type */
status = readAlgoString( &stream, hasPassword ? \
algoStringUserauthentPWTbl : \
algoStringUserauthentPKCTbl,
&authentAlgo, FALSE, sessionInfoPtr );
sMemDisconnect( &stream );
if( cryptStatusError( status ) )
{
/* If the problem is due to lack of a compatible algorithm, make the
error message a bit more specific to tell the user that we got
through most of the handshake but failed at the authentication
stage */
if( status == CRYPT_ERROR_NOTAVAIL )
retExt( sessionInfoPtr, CRYPT_ERROR_NOTAVAIL,
"Remote system supports neither password nor "
"public-key authentication" );
/* There was some other problem with the returned information, we
still report it as a failed-authentication error but leave the
extended error info in place to let the caller see what the
underlying cause was */
return( CRYPT_ERROR_WRONGKEY );
}
/* SSH reports authentication failures in a somewhat bizarre way,
instead of saying "authentication failed" it returns a list of
allowed authentication methods, one of which may be the one that we
just used. To figure out whether we used the wrong auth method or
the wrong auth value, we have to perform a complex decode and match
of the info in the returned packet with what we sent */
if( !hasPassword )
{
/* If we used a PKC and the server wants a password, report the
error as a missing password */
if( authentAlgo == CRYPT_PSEUDOALGO_PASSWORD || \
authentAlgo == CRYPT_PSEUDOALGO_PAM )
{
setErrorInfo( sessionInfoPtr, CRYPT_SESSINFO_PASSWORD,
CRYPT_ERRTYPE_ATTR_ABSENT );
retExt( sessionInfoPtr, CRYPT_ERROR_NOTINITED,
"Server requested password authentication but only a "
"public/private key was available" );
}
retExt( sessionInfoPtr, CRYPT_ERROR_WRONGKEY,
"Server reported: Invalid public-key authentication" );
}
/* If the server requested keyboard-interactive (== misnamed PAM)
authentication, try again using PAM authentication unless we've
already been called as a result of failed PAM authentication */
if( authentAlgo == CRYPT_PSEUDOALGO_PAM && !isPamAuth )
return( processPamAuthentication( sessionInfoPtr ) );
/* If we used a password and the server wants a PKC, report the error
as a missing private key. RSA in this case is a placeholder that
means "any public-key algorithm", it could just as well have been
DSA */
if( authentAlgo == CRYPT_ALGO_RSA )
{
setErrorInfo( sessionInfoPtr, CRYPT_SESSINFO_PRIVATEKEY,
CRYPT_ERRTYPE_ATTR_ABSENT );
retExt( sessionInfoPtr, CRYPT_ERROR_NOTINITED,
"Server requested public-key authentication but only a "
"password was available" );
}
retExt( sessionInfoPtr, CRYPT_ERROR_WRONGKEY,
"Server reported: Invalid password" );
}
/* Handle an ephemeral DH key exchange */
static int processDHE( SESSION_INFO *sessionInfoPtr,
SSH_HANDSHAKE_INFO *handshakeInfo,
STREAM *stream, KEYAGREE_PARAMS *keyAgreeParams )
{
const int offset = LENGTH_SIZE + sizeofString32( "ssh-dh", 6 );
BYTE *keyexInfoPtr;
int keyexInfoLength, length, packetOffset, status;
/* ...
byte type = SSH2_MSG_KEXDH_GEX_REQUEST_OLD
uint32 n = 1024 bits
There's an alternative format that allows the client to specify a
range of key sizes:
byte type = SSH2_MSG_KEXDH_GEX_REQUEST_NEW
uint32 min = 1024 bits
uint32 n = SSH2_DEFAULT_KEYSIZE (as bits)
uint32 max = CRYPT_MAX_PKCSIZE (as bits)
but a number of implementations don't support this yet, with some
servers just dropping the connection without any error response if
they encounter the newer packet type */
#if 1
packetOffset = continuePacketStreamSSH( stream,
SSH2_MSG_KEXDH_GEX_REQUEST_OLD );
streamBookmarkSet( stream, keyexInfoPtr, keyexInfoLength );
writeUint32( stream, bytesToBits( SSH2_DEFAULT_KEYSIZE ) );
#else
packetOffset = continuePacketStreamSSH( stream,
SSH2_MSG_KEXDH_GEX_REQUEST_NEW );
streamBookmarkSet( stream, keyexInfoPtr, keyexInfoLength );
writeUint32( stream, 1024 );
writeUint32( stream, bytesToBits( SSH2_DEFAULT_KEYSIZE ) );
writeUint32( stream, bytesToBits( CRYPT_MAX_PKCSIZE ) );
#endif /* 1 */
streamBookmarkComplete( stream, keyexInfoLength );
status = wrapPacketSSH2( sessionInfoPtr, stream, packetOffset );
if( cryptStatusOK( status ) )
status = sendPacketSSH2( sessionInfoPtr, stream, TRUE );
sMemDisconnect( stream );
if( cryptStatusError( status ) )
return( status );
/* Remember the encoded key size info for later when we generate the
exchange hash */
memcpy( handshakeInfo->encodedReqKeySizes, keyexInfoPtr,
keyexInfoLength );
handshakeInfo->encodedReqKeySizesLength = keyexInfoLength;
/* Process the ephemeral DH key:
byte type = SSH2_MSG_KEXDH_GEX_GROUP
mpint p
mpint g */
length = readPacketSSH2( sessionInfoPtr, SSH2_MSG_KEXDH_GEX_GROUP,
ID_SIZE + \
sizeofString32( "", bitsToBytes( MIN_PKCSIZE_BITS ) ) + \
sizeofString32( "", 1 ) );
if( cryptStatusError( length ) )
return( length );
sMemConnect( stream, sessionInfoPtr->receiveBuffer, length );
sgetc( stream ); /* Skip packet type */
streamBookmarkSet( stream, keyexInfoPtr, keyexInfoLength );
readInteger32( stream, NULL, NULL, bitsToBytes( MIN_PKCSIZE_BITS ),
CRYPT_MAX_PKCSIZE );
status = readInteger32( stream, NULL, NULL, 1, CRYPT_MAX_PKCSIZE );
streamBookmarkComplete( stream, keyexInfoLength );
sMemDisconnect( stream );
if( cryptStatusError( status ) )
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Invalid DH ephemeral key data packet" );
/* Since this phase of the key negotiation exchanges raw key components
rather than the standard SSH public-key format, we have to rewrite
the raw key components into a standard SSH key so that we can import
it:
From: To:
string [ key/certificate ]
string "ssh-dh"
mpint p mpint p
mpint g mpint g */
memmove( keyexInfoPtr + offset, keyexInfoPtr, keyexInfoLength );
sMemOpen( stream, keyexInfoPtr, offset );
writeUint32( stream, ( offset - LENGTH_SIZE ) + keyexInfoLength );
writeString32( stream, "ssh-dh", 0 );
sMemDisconnect( stream );
/* Destroy the existing static DH key, load the new one, and re-perform
phase 1 of the DH key agreement process */
krnlSendNotifier( handshakeInfo->iServerCryptContext,
IMESSAGE_DECREFCOUNT );
status = initDHcontextSSH( &handshakeInfo->iServerCryptContext,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -