📄 ssh2_cli.c
字号:
static int completeClientHandshake( SESSION_INFO *sessionInfoPtr,
SSH_HANDSHAKE_INFO *handshakeInfo )
{
const ATTRIBUTE_LIST *userNamePtr = \
findSessionInfo( sessionInfoPtr->attributeList,
CRYPT_SESSINFO_USERNAME );
const ATTRIBUTE_LIST *passwordPtr = \
findSessionInfo( sessionInfoPtr->attributeList,
CRYPT_SESSINFO_PASSWORD );
STREAM stream;
BYTE stringBuffer[ CRYPT_MAX_TEXTSIZE + 8 ];
int stringLength, signedDataLength, length, packetOffset, status;
/* Set up the security information required for the session */
status = initSecurityInfo( sessionInfoPtr, handshakeInfo );
if( cryptStatusError( status ) )
return( status );
/* Wait for the server's change cipherspec message. From this point
on the read channel is in the secure state */
status = readHSPacketSSH2( sessionInfoPtr, SSH2_MSG_NEWKEYS, ID_SIZE );
if( cryptStatusError( status ) )
return( status );
sessionInfoPtr->flags |= SESSION_ISSECURE_READ;
/* Build our change cipherspec message and request authentication with
the server:
byte type = SSH2_MSG_NEWKEYS
...
After this point the write channel is also in the secure state */
status = openPacketStreamSSH( &stream, sessionInfoPtr, CRYPT_USE_DEFAULT,
SSH2_MSG_NEWKEYS );
if( cryptStatusOK( status ) )
status = wrapPacketSSH2( sessionInfoPtr, &stream, 0, FALSE, TRUE );
if( cryptStatusError( status ) )
{
sMemDisconnect( &stream );
return( status );
}
sessionInfoPtr->flags |= SESSION_ISSECURE_WRITE;
/* ...
byte type = SSH2_MSG_SERVICE_REQUEST
string service_name = "ssh-userauth" */
status = continuePacketStreamSSH( &stream, SSH2_MSG_SERVICE_REQUEST,
&packetOffset );
if( cryptStatusOK( status ) )
status = writeString32( &stream, "ssh-userauth", 12 );
if( cryptStatusOK( status ) )
status = wrapPacketSSH2( sessionInfoPtr, &stream, packetOffset,
FALSE, TRUE );
if( cryptStatusError( status ) )
{
sMemDisconnect( &stream );
return( status );
}
/* Send the whole mess to the server. For some reason SSHv2 requires
the use of two authentication messages, an "I'm about to
authenticate" packet and an "I'm authenticating" packet, so we have
to perform the authentication in two parts. SSL at this point uses a
Finished message in which the client and server do a mutual proof-of-
possession of encryption and MAC keys via a pipeline-stalling message
that prevents any further (sensitive) data from being exchanged until
the PoP has concluded (the SSL Finished also authenticates the
handshake messages). The signed exchange hash from the server proves
to the client that the server knows the master secret, but not
necessarily that the client and server share encryption and MAC keys.
Without this mutual PoP, the client could potentially end up sending
passwords to the server using an incorrect (and potentially weak) key
if it's messed up and derived the key incorrectly. Although mutual
PoP isn't a design goal of the SSH handshake, we do it anyway (as far
as we can without a proper Finished message), although this introduces
a pipeline stall at this point
The spec in fact says that after a key exchange with implicit server
authentication the client has to wait for the server to send a
service-accept packet before continuing, however it never explains
what implicit (and, by extension, explicit) server authentication
actually are. This text is a leftover from an extremely early SSH
draft in which the only keyex mechanism was "double-encrypting-sha",
a mechanism that required a pipeline stall at this point because the
client wasn't able to authenticate the server until it received the
first encrypted/MAC'ed message from it. To extricate ourselves from
the confusion due to the missing definition we could define "implicit
authentication" to be "Something completely different from what we're
doing here", which means that we could send the two packets together
without having to wait for the server, but it's probably better to
use SSL-tyle Finished semantics at this point even if it adds an
extra RTT delay */
status = sendPacketSSH2( sessionInfoPtr, &stream, TRUE );
sMemDisconnect( &stream );
if( cryptStatusError( status ) )
return( status );
status = length = \
readHSPacketSSH2( sessionInfoPtr, SSH2_MSG_SERVICE_ACCEPT,
ID_SIZE + sizeofString32( "", 8 ) );
if( cryptStatusError( status ) )
return( status );
sMemConnect( &stream, sessionInfoPtr->receiveBuffer, length );
sgetc( &stream ); /* Skip packet type */
status = readString32( &stream, stringBuffer, CRYPT_MAX_TEXTSIZE,
&stringLength );
sMemDisconnect( &stream );
if( cryptStatusError( status ) || \
stringLength != 12 || memcmp( stringBuffer, "ssh-userauth", 12 ) )
{
/* More of a sanity check than anything else, the MAC should have
caught any keying problems */
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, SESSION_ERRINFO,
"Invalid service accept packet" ) );
}
/* The buggy Tectia (ssh.com) server requires a dummy request for
authentication methods, otherwise it rejects any method other than
'password' as invalid, with the error "Client requested non-existing
method 'publickey'". To work around this we submit a dummy auth.
request using the method 'none' */
if( sessionInfoPtr->protocolFlags & SSH_PFLAG_DUMMYUSERAUTH )
{
/* Send the dummy auth request */
status = openPacketStreamSSH( &stream, sessionInfoPtr,
CRYPT_USE_DEFAULT,
SSH2_MSG_USERAUTH_REQUEST );
if( cryptStatusError( status ) )
return( status );
writeString32( &stream, userNamePtr->value, userNamePtr->valueLength );
writeString32( &stream, "ssh-connection", 14 );
status = writeString32( &stream, "none", 4 );
if( cryptStatusOK( status ) )
status = wrapPacketSSH2( sessionInfoPtr, &stream, 0, FALSE, TRUE );
if( cryptStatusOK( status ) )
status = sendPacketSSH2( sessionInfoPtr, &stream, TRUE );
sMemDisconnect( &stream );
if( cryptStatusError( status ) )
return( status );
/* Wait for the server's ack of the authentication. Since this is
just something used to de-confuse the buggy Tectia server, we
ignore the content (as long as the packet's valid), any auth.
problems will be resolved by the real auth below */
status = length = \
readHSPacketSSH2( sessionInfoPtr, SSH2_MSG_SPECIAL_USERAUTH,
ID_SIZE );
if( cryptStatusError( status ) )
return( status );
}
/* byte type = SSH2_MSG_USERAUTH_REQUEST
string user_name
string service_name = "ssh-connection"
...
The way in which we handle authentication here isn't totally
appropriate since we assume that the user knows the appropriate form
of authentication to use. If they're ambiguous and supply both a
password and a private key and the server only accepts PKC-based
authentication, we'll always preferentially choose password-based
authentication. The way around this is to send an auth-request with
a method-type of "none" to see what the server wants, but the only
thing cryptlib can do (since it's non-interactive during the
handshake phase) is disconnect, tell the user what went wrong, and try
again. The current mechanism does this anyway, so we don't gain much
except extra RTT delays by adding this question-and-answer facility */
status = openPacketStreamSSH( &stream, sessionInfoPtr, CRYPT_USE_DEFAULT,
SSH2_MSG_USERAUTH_REQUEST );
if( cryptStatusError( status ) )
return( status );
streamBookmarkSetFullPacket( &stream, signedDataLength );
writeString32( &stream, userNamePtr->value, userNamePtr->valueLength );
writeString32( &stream, "ssh-connection", 14 );
if( passwordPtr != NULL )
{
/* ...
string method-name = "password"
boolean FALSE
string password */
writeString32( &stream, "password", 8 );
sputc( &stream, 0 );
status = writeString32( &stream, passwordPtr->value,
passwordPtr->valueLength );
}
else
{
CRYPT_ALGO_TYPE pkcAlgo;
MESSAGE_CREATEOBJECT_INFO createInfo;
void *dataPtr, *signedDataPtr = DUMMY_INIT_PTR;
int dataLength, sigLength = DUMMY_INIT;
krnlSendMessage( sessionInfoPtr->privateKey, IMESSAGE_GETATTRIBUTE,
&pkcAlgo, CRYPT_CTXINFO_ALGO );
/* ...
string method-name = "publickey"
boolean TRUE
string "ssh-rsa" "ssh-dss"
string [ client key/certificate ]
string "ssh-rsa" "ssh-dss"
mpint e p
mpint n q
mpint g
mpint y
string [ client signature ]
string "ssh-rsa" "ssh-dss"
string signature signature.
Note the doubled-up algorithm name, the spec first requires that
the public-key auth packet send the algorithm name and then
includes it a second time as part of the client key info */
writeString32( &stream, "publickey", 9 );
sputc( &stream, 1 );
writeAlgoString( &stream, pkcAlgo );
status = exportAttributeToStream( &stream,
sessionInfoPtr->privateKey,
CRYPT_IATTRIBUTE_KEY_SSH );
if( cryptStatusOK( status ) )
{
status = streamBookmarkComplete( &stream, &signedDataPtr,
&signedDataLength,
signedDataLength );
}
if( cryptStatusError( status ) )
{
sMemDisconnect( &stream );
return( status );
}
/* Hash the authentication request data:
string exchange hash
[ user_auth_request packet payload up to signature start ] */
setMessageCreateObjectInfo( &createInfo, CRYPT_ALGO_SHA1 );
status = krnlSendMessage( SYSTEM_OBJECT_HANDLE,
IMESSAGE_DEV_CREATEOBJECT, &createInfo,
OBJECT_TYPE_CONTEXT );
if( cryptStatusError( status ) )
{
sMemDisconnect( &stream );
return( status );
}
if( sessionInfoPtr->protocolFlags & SSH_PFLAG_NOHASHLENGTH )
{
/* Some implementations erroneously omit the length when hashing
the exchange hash */
status = krnlSendMessage( createInfo.cryptHandle,
IMESSAGE_CTX_HASH,
handshakeInfo->sessionID,
handshakeInfo->sessionIDlength );
}
else
{
status = hashAsString( createInfo.cryptHandle,
handshakeInfo->sessionID,
handshakeInfo->sessionIDlength );
}
if( cryptStatusOK( status ) )
status = krnlSendMessage( createInfo.cryptHandle,
IMESSAGE_CTX_HASH,
signedDataPtr, signedDataLength );
if( cryptStatusOK( status ) )
status = krnlSendMessage( createInfo.cryptHandle,
IMESSAGE_CTX_HASH, "", 0 );
if( cryptStatusError( status ) )
{
sMemDisconnect( &stream );
krnlSendNotifier( createInfo.cryptHandle, IMESSAGE_DECREFCOUNT );
return( status );
}
/* Sign the hash. The reason for the min() part of the expression
is that iCryptCreateSignature() gets suspicious of very large
buffer sizes, for example when the user has specified the use of
a huge send buffer */
status = sMemGetDataBlockRemaining( &stream, &dataPtr, &dataLength );
if( cryptStatusOK( status ) )
{
status = iCryptCreateSignature( dataPtr,
min( dataLength, MAX_INTLENGTH_SHORT - 1 ),
&sigLength, CRYPT_IFORMAT_SSH,
sessionInfoPtr->privateKey, createInfo.cryptHandle,
CRYPT_UNUSED, CRYPT_UNUSED );
}
if( cryptStatusOK( status ) )
status = sSkip( &stream, sigLength );
krnlSendNotifier( createInfo.cryptHandle, IMESSAGE_DECREFCOUNT );
}
if( cryptStatusError( status ) )
{
sMemDisconnect( &stream );
return( status );
}
/* Send the authentication info to the server */
status = wrapPacketSSH2( sessionInfoPtr, &stream, 0, TRUE, TRUE );
if( cryptStatusOK( status ) )
status = sendPacketSSH2( sessionInfoPtr, &stream, TRUE );
sMemDisconnect( &stream );
if( cryptStatusError( status ) )
return( status );
/* Wait for the server's ack of the authentication */
status = length = \
readHSPacketSSH2( sessionInfoPtr, SSH2_MSG_SPECIAL_USERAUTH,
ID_SIZE );
if( !cryptStatusError( status ) )
{
int type;
sMemConnect( &stream, sessionInfoPtr->receiveBuffer, length );
type = sgetc( &stream );
sMemDisconnect( &stream );
if( type == SSH2_MSG_USERAUTH_FAILURE )
{
/* The authentication failed, provide more specific details for
the caller, with an optional fallback to PAM authentication
if the server requested it */
status = reportAuthFailure( sessionInfoPtr, length, FALSE );
}
}
if( cryptStatusError( status ) )
return( status );
/* We've finally made it through all of the formalities (post proelia
praemia), create (if necessary) and open a channel */
if( getCurrentChannelNo( sessionInfoPtr, \
CHANNEL_READ ) == UNUSED_CHANNEL_NO )
{
/* The user hasn't specified any channel details, create a
channel of the default type */
status = createChannel( sessionInfoPtr );
if( cryptStatusError( status ) )
return( status );
}
return( sendChannelOpen( sessionInfoPtr ) );
}
/****************************************************************************
* *
* Session Access Routines *
* *
****************************************************************************/
void initSSH2clientProcessing( SESSION_INFO *sessionInfoPtr,
SSH_HANDSHAKE_INFO *handshakeInfo )
{
UNUSED_ARG( sessionInfoPtr );
handshakeInfo->beginHandshake = beginClientHandshake;
handshakeInfo->exchangeKeys = exchangeClientKeys;
handshakeInfo->completeHandshake = completeClientHandshake;
}
#endif /* USE_SSH */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -