📄 ssh2_cli.c
字号:
{
memset( keyAgreeParams, 0, sizeof( KEYAGREE_PARAMS ) );
status = krnlSendMessage( handshakeInfo->iServerCryptContext,
IMESSAGE_CTX_ENCRYPT, keyAgreeParams,
sizeof( KEYAGREE_PARAMS ) );
}
if( cryptStatusError( status ) )
return( status );
/* We've already sent the client hello as part of the keyex negotiation
so there's no need to bundle it with the client keyex, reset the
start position in the send buffer */
sMemOpen( stream, sessionInfoPtr->sendBuffer,
sessionInfoPtr->sendBufSize - EXTRA_PACKET_SIZE );
return( CRYPT_OK );
}
/* Handle PAM authentication */
static int processPamAuthentication( SESSION_INFO *sessionInfoPtr )
{
const ATTRIBUTE_LIST *userNamePtr = \
findSessionInfo( sessionInfoPtr->attributeList,
CRYPT_SESSINFO_USERNAME );
const ATTRIBUTE_LIST *passwordPtr = \
findSessionInfo( sessionInfoPtr->attributeList,
CRYPT_SESSINFO_PASSWORD );
STREAM stream;
int length, pamIteration, status;
/* Send a user-auth request asking for PAM authentication:
byte type = SSH2_MSG_USERAUTH_REQUEST
string user_name
string service_name = "ssh-connection"
string method_name = "keyboard-interactive"
string language = ""
string sub_methods = "password"
The sub-methods are implementation-dependent and the spec suggests an
implementation strategy in which the server ignores them so
specifying anything here is mostly wishful thinking, but we ask for
password auth. anyway in case it helps */
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 );
writeString32( &stream, "keyboard-interactive", 20 );
writeUint32( &stream, 0 ); /* No language tag */
if( sessionInfoPtr->protocolFlags & SSH_PFLAG_PAMPW )
{
/* Some servers choke if we supply a sub-method hint for the
authentication */
status = writeUint32( &stream, 0 );
}
else
status = writeString32( &stream, "password", 8 );
if( cryptStatusOK( status ) )
status = sendPacketSSH2( sessionInfoPtr, &stream, FALSE );
sMemDisconnect( &stream );
if( cryptStatusError( status ) )
return( status );
/* Handle the PAM negotiation. This can (in theory) go on indefinitely,
to avoid potential DoS problems we limit it to five iterations. In
general we'll go for two iterations (or three for OpenSSH's empty-
message bug), so we shouldn't ever get to five */
for( pamIteration = 0; pamIteration < 5; pamIteration++ )
{
BYTE nameBuffer[ CRYPT_MAX_TEXTSIZE + 8 ];
BYTE promptBuffer[ CRYPT_MAX_TEXTSIZE + 8 ];
int nameLength, promptLength = -1, noPrompts = -1, type;
/* Read back the response to our last message. Although the spec
requires that the server not respond with a SSH2_MSG_USERAUTH_-
FAILURE message if the request fails because of an invalid user
name (to prevent an attacker from being able to determine valid
user names by checking for error responses), some servers can
return a failure indication at this point so we have to allow for
a failure response as well as the expected SSH2_MSG_USERAUTH_-
INFO_REQUEST */
status = length = \
readHSPacketSSH2( sessionInfoPtr, SSH2_MSG_SPECIAL_USERAUTH_PAM,
ID_SIZE );
if( cryptStatusError( status ) )
return( status );
/* See what we got. If it's not a PAM info request, we're done */
sMemConnect( &stream, sessionInfoPtr->receiveBuffer, length );
type = sgetc( &stream );
if( type != SSH2_MSG_USERAUTH_INFO_REQUEST )
sMemDisconnect( &stream );
/* If it's a success status, we're done */
if( type == SSH2_MSG_USERAUTH_SUCCESS )
return( CRYPT_OK );
/* If the authentication failed, provide more specific details to
the caller */
if( type == SSH2_MSG_USERAUTH_FAILURE )
{
/* If we failed on the first attempt (before we even tried to
send a password), it's probably because the user name is
invalid (or the server has the SSH_PFLAG_PAMPW bug). Having
the server return a failure due to an invalid user name
shouldn't happen (see the comment above), but we handle it
just in case */
if( pamIteration == 0 )
{
char userNameBuffer[ CRYPT_MAX_TEXTSIZE + 8 ];
memcpy( userNameBuffer, userNamePtr->value,
userNamePtr->valueLength );
retExt( CRYPT_ERROR_WRONGKEY,
( CRYPT_ERROR_WRONGKEY, SESSION_ERRINFO,
"Server reported: Invalid user name '%s'",
sanitiseString( userNameBuffer,
CRYPT_MAX_TEXTSIZE,
userNamePtr->valueLength ) ) );
}
/* It's a failure after we've tried to authenticate ourselves,
report the details to the caller */
return( reportAuthFailure( sessionInfoPtr, length, TRUE ) );
}
/* Process the PAM user-auth request:
byte type = SSH2_MSG_USERAUTH_INFO_REQUEST
string name
string instruction
string language = {}
int num_prompts
string prompt[ n ]
boolean echo[ n ]
Exactly whose name is supplied or what the instruction field is
for is left unspecified by the RFC (and they may indeed be left
empty), so we just skip it. Many implementations feel similarly
about this and leave the fields empty.
If the PAM authentication (from a previous iteration) fails or
succeeds, the server is supposed to send back a standard user-
auth success or failure status, but could also send another
SSH2_MSG_USERAUTH_INFO_REQUEST even if it contains no payload (an
OpenSSH bug), so we have to handle this as a special case */
status = readString32( &stream, nameBuffer, CRYPT_MAX_TEXTSIZE,
&nameLength ); /* Name */
if( cryptStatusOK( status ) )
{
nameBuffer[ nameLength ] = '\0';
status = readUniversal32( &stream ); /* Instruction */
}
if( cryptStatusOK( status ) )
status = readUniversal32( &stream ); /* Language */
if( cryptStatusOK( status ) )
{
status = noPrompts = readUint32( &stream ); /* No.prompts */
if( !cryptStatusError( status ) && noPrompts > 8 )
{
/* Requesting more than a small number of prompts is
suspicious */
status = CRYPT_ERROR_BADDATA;
}
}
if( !cryptStatusError( status ) && noPrompts > 0 )
{
status = readString32( &stream, promptBuffer,
CRYPT_MAX_TEXTSIZE, &promptLength );
if( cryptStatusOK( status ) )
promptBuffer[ promptLength ] = '\0';
}
sMemDisconnect( &stream );
if( cryptStatusError( status ) )
{
retExt( status,
( status, SESSION_ERRINFO,
"Invalid PAM authentication request packet" ) );
}
/* If we got a prompt, make sure that we're being asked for some
form of password authentication. This assumes that the prompt
string begins with the word "password" (which always seems to be
the case), if this isn't the case then it may be necessary to do
a substring search */
if( noPrompts > 0 && \
( promptLength < 8 || \
strCompare( promptBuffer, "Password", 8 ) ) )
{
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, SESSION_ERRINFO,
"Server requested unknown PAM authentication type "
"'%s'", ( nameLength > 0 ) ? \
sanitiseString( nameBuffer, CRYPT_MAX_TEXTSIZE, \
nameLength ) : \
sanitiseString( promptBuffer, CRYPT_MAX_TEXTSIZE, \
promptLength ) ) );
}
/* Send back the PAM user-auth response:
byte type = SSH2_MSG_USERAUTH_INFO_RESPONSE
int num_responses = num_prompts
string response
What to do if there's more than one prompt is a bit tricky,
usually PAM is used as a form of (awkward) password
authentication and there's only a single prompt, if we ever
encounter a situation where there's more than one prompt, it's
probably a request to confirm the password, so we just send it
again for successive prompts */
status = openPacketStreamSSH( &stream, sessionInfoPtr,
CRYPT_USE_DEFAULT,
SSH2_MSG_USERAUTH_INFO_RESPONSE );
if( cryptStatusError( status ) )
return( status );
status = writeUint32( &stream, noPrompts );
while( cryptStatusOK( status ) && noPrompts-- > 0 )
{
status = writeString32( &stream, passwordPtr->value,
passwordPtr->valueLength );
}
if( cryptStatusOK( status ) )
status = sendPacketSSH2( sessionInfoPtr, &stream, FALSE );
sMemDisconnect( &stream );
if( cryptStatusError( status ) )
return( status );
}
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, SESSION_ERRINFO,
"Too many iterations of negotiation during PAM "
"authentication" ) );
}
/****************************************************************************
* *
* 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;
STREAM stream;
void *clientHelloPtr = DUMMY_INIT_PTR, *keyexPtr = DUMMY_INIT_PTR;
int serverHelloLength, clientHelloLength, keyexLength = DUMMY_INIT;
int packetOffset, status;
/* The higher-level code has already read the server version info, send
back our own version info (SSHv2 sends a CR and LF as terminator,
but this isn't hashed) */
status = swrite( &sessionInfoPtr->stream, SSH2_ID_STRING "\r\n",
SSH_ID_STRING_SIZE + 2 );
if( cryptStatusError( status ) )
{
sNetGetErrorInfo( &sessionInfoPtr->stream,
&sessionInfoPtr->errorInfo );
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 */
status = hashAsString( handshakeInfo->iExchangeHashcontext,
SSH2_ID_STRING, SSH_ID_STRING_SIZE );
if( cryptStatusOK( status ) )
status = hashAsString( handshakeInfo->iExchangeHashcontext,
sessionInfoPtr->receiveBuffer,
strlen( sessionInfoPtr->receiveBuffer ) );
if( cryptStatusError( status ) )
return( status );
/* 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 = initDHcontextSSH( &handshakeInfo->iServerCryptContext,
&handshakeInfo->serverKeySize, NULL, 0,
CRYPT_USE_DEFAULT );
if( cryptStatusError( status ) )
return( 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 = processHelloSSH( sessionInfoPtr, handshakeInfo,
&serverHelloLength, 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 */
status = openPacketStreamSSH( &stream, sessionInfoPtr, CRYPT_USE_DEFAULT,
SSH2_MSG_KEXINIT );
if( cryptStatusError( status ) )
return( status );
streamBookmarkSetFullPacket( &stream, clientHelloLength );
status = exportVarsizeAttributeToStream( &stream, SYSTEM_OBJECT_HANDLE,
CRYPT_IATTRIBUTE_RANDOM_NONCE,
SSH2_COOKIE_SIZE );
if( cryptStatusError( status ) )
{
sMemDisconnect( &stream );
return( status );
}
writeAlgoString( &stream, ( handshakeInfo->requestedServerKeySize > 0 ) ? \
CRYPT_PSEUDOALGO_DHE : CRYPT_ALGO_DH );
writeAlgoString( &stream, handshakeInfo->pubkeyAlgo );
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -