📄 ssl_cli.c
字号:
/****************************************************************************
* *
* cryptlib SSL v3/TLS Client Management *
* Copyright Peter Gutmann 1998-2008 *
* *
****************************************************************************/
#if defined( INC_ALL )
#include "crypt.h"
#include "misc_rw.h"
#include "session.h"
#include "ssl.h"
#else
#include "crypt.h"
#include "misc/misc_rw.h"
#include "session/session.h"
#include "session/ssl.h"
#endif /* Compiler-specific includes */
#ifdef USE_SSL
/****************************************************************************
* *
* Utility Functions *
* *
****************************************************************************/
/* Encode a list of available algorithms. Some buggy older versions of IIS
that only support crippled crypto drop the connection when they see a
client hello advertising strong crypto, rather than sending an alert as
they should. To work around this, we advertise a dummy cipher suite
SSL_RSA_EXPORT_WITH_RC4_40_MD5 as a canary to force IIS to send back a
response that we can then turn into an error message. The need to do
this is somewhat unfortunate since it will appear to an observer that
cryptlib will use crippled crypto (in fact it won't even load such a
key), but there's no other way to detect the buggy IIS apart from
completely restarting the session activation at the session level with
crippled-crypto advertised in the restarted session */
static int writeCipherSuiteList( STREAM *stream, const BOOLEAN usePSK )
{
typedef struct {
const CRYPT_ALGO_TYPE cryptAlgo;
const int cipherSuite;
const BOOLEAN isPSK;
} CIPHERSUITE_INFO;
const static CIPHERSUITE_INFO cipherSuiteList[] = {
{ CRYPT_ALGO_3DES, TLS_PSK_WITH_3DES_EDE_CBC_SHA, TRUE },
{ CRYPT_ALGO_AES, TLS_PSK_WITH_AES_256_CBC_SHA, TRUE },
{ CRYPT_ALGO_AES, TLS_PSK_WITH_AES_128_CBC_SHA, TRUE },
{ CRYPT_ALGO_RC4, TLS_PSK_WITH_RC4_128_SHA, TRUE },
#ifdef PREFER_DH_SUITES
{ CRYPT_ALGO_3DES, TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, FALSE },
{ CRYPT_ALGO_AES, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, FALSE },
{ CRYPT_ALGO_AES, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, FALSE },
{ CRYPT_ALGO_3DES, SSL_RSA_WITH_3DES_EDE_CBC_SHA, FALSE },
{ CRYPT_ALGO_AES, TLS_RSA_WITH_AES_256_CBC_SHA, FALSE },
{ CRYPT_ALGO_AES, TLS_RSA_WITH_AES_128_CBC_SHA, FALSE },
#else
{ CRYPT_ALGO_3DES, SSL_RSA_WITH_3DES_EDE_CBC_SHA, FALSE },
{ CRYPT_ALGO_AES, TLS_RSA_WITH_AES_256_CBC_SHA, FALSE },
{ CRYPT_ALGO_AES, TLS_RSA_WITH_AES_128_CBC_SHA, FALSE },
{ CRYPT_ALGO_3DES, TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, FALSE },
{ CRYPT_ALGO_AES, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, FALSE },
{ CRYPT_ALGO_AES, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, FALSE },
#endif /* PREFER_DH_SUITES */
{ CRYPT_ALGO_IDEA, SSL_RSA_WITH_IDEA_CBC_SHA, FALSE },
{ CRYPT_ALGO_RC4, SSL_RSA_WITH_RC4_128_SHA, FALSE },
{ CRYPT_ALGO_RC4, SSL_RSA_WITH_RC4_128_MD5, FALSE },
{ CRYPT_ALGO_DES, SSL_RSA_WITH_DES_CBC_SHA, FALSE },
{ CRYPT_ALGO_DES, TLS_DHE_RSA_WITH_DES_CBC_SHA, FALSE },
{ CRYPT_ALGO_SHA1, SSL_RSA_EXPORT_WITH_RC4_40_MD5, FALSE }, /* Canary */
{ CRYPT_ALGO_NONE, SSL_NULL_WITH_NULL, FALSE },
{ CRYPT_ALGO_NONE, SSL_NULL_WITH_NULL, FALSE }
};
int availableSuites[ 32 + 8 ], cipherSuiteCount = 0, suiteIndex = 0;
int status;
/* Walk down the list of algorithms (and the corresponding cipher
suites) remembering each one that's available for use */
while( cipherSuiteList[ suiteIndex ].cryptAlgo != CRYPT_ALGO_NONE && \
cipherSuiteCount < 32 && \
suiteIndex < FAILSAFE_ARRAYSIZE( cipherSuiteList, CIPHERSUITE_INFO ) )
{
const CRYPT_ALGO_TYPE cryptAlgo = \
cipherSuiteList[ suiteIndex ].cryptAlgo;
if( !usePSK && cipherSuiteList[ suiteIndex ].isPSK )
{
/* It's a PSK suite but we're not using a PSK handshake, skip
it */
suiteIndex++;
continue;
}
if( !algoAvailable( cipherSuiteList[ suiteIndex ].cryptAlgo ) )
{
while( cipherSuiteList[ suiteIndex ].cryptAlgo == cryptAlgo && \
suiteIndex < FAILSAFE_ARRAYSIZE( cipherSuiteList, \
CIPHERSUITE_INFO ) )
suiteIndex++;
ENSURES( suiteIndex < FAILSAFE_ARRAYSIZE( cipherSuiteList, \
CIPHERSUITE_INFO ) );
continue;
}
while( cipherSuiteList[ suiteIndex ].cryptAlgo == cryptAlgo && \
cipherSuiteCount < 32 && \
suiteIndex < FAILSAFE_ARRAYSIZE( cipherSuiteList, CIPHERSUITE_INFO ) )
{
availableSuites[ cipherSuiteCount++ ] = \
cipherSuiteList[ suiteIndex++ ].cipherSuite;
}
if( suiteIndex >= FAILSAFE_ARRAYSIZE( cipherSuiteList, CIPHERSUITE_INFO ) )
retIntError();
}
if( suiteIndex >= FAILSAFE_ARRAYSIZE( cipherSuiteList, CIPHERSUITE_INFO ) )
retIntError();
assert( cipherSuiteCount < 32 );
/* Encode the list of available cipher suites */
status = writeUint16( stream, cipherSuiteCount * UINT16_SIZE );
for( suiteIndex = 0;
cryptStatusOK( status ) && suiteIndex < cipherSuiteCount;
suiteIndex++ )
status = writeUint16( stream, availableSuites[ suiteIndex ] );
return( status );
}
/* Make sure that the server URL matches the value in the returned
certificate. This code isn't currently called because it's not certain
what the best way is to report this to the user is, and more importantly
because there are quite a few servers out there where the server name
doesn't match what's in the cert (according to SecuritySpace, the
majority of all web site certs are invalid for one reason or another) but
for which the user will just click "OK" anyway even if we can tunnel a
warning indication back to them, so we leave it to the caller to perform
whatever checking and take whatever action they consider necessary */
#if 0
static int checkURL( SESSION_INFO *sessionInfoPtr )
{
MESSAGE_DATA msgData;
char hostName[ MAX_URL_SIZE + 8 ];
const int serverNameLength = strlen( sessionInfoPtr->serverName );
int hostNameLength, splatPos = CRYPT_ERROR, postSplatLen, i, status;
/* Read the server name specification from the server's cert */
setMessageData( &msgData, hostName, MAX_URL_SIZE );
status = krnlSendMessage( sessionInfoPtr->iKeyexCryptContext,
IMESSAGE_GETATTRIBUTE_S, &msgData,
CRYPT_CERTINFO_DNSNAME );
if( cryptStatusError( status ) )
{
status = krnlSendMessage( sessionInfoPtr->iKeyexCryptContext,
IMESSAGE_GETATTRIBUTE_S, &msgData,
CRYPT_CERTINFO_COMMONNAME );
}
if( cryptStatusError( status ) )
{
retExt( status,
( status, SESSION_ERRINFO,
"Couldn't read server name from server certificate" ) );
}
hostNameLength = msgData.length;
/* Look for a splat in the host name spec */
for( i = 0; i < hostNameLength; i++ )
if( hostName[ i ] == '*' )
{
if( splatPos != CRYPT_ERROR )
{
/* Can't have more than one splat in a host name */
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, SESSION_ERRINFO,
"Server name in certificate contains more than "
"one wildcard" ) );
}
splatPos = i;
}
/* If there's no wildcarding, perform a direct match */
if( splatPos == CRYPT_ERROR )
{
if( hostNameLength != serverNameLength || \
strCompare( hostName, sessionInfoPtr->serverName,
serverNameLength ) )
{
/* Host doesn't match the name in the cert */
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, SESSION_ERRINFO,
"Server name doesn't match name in server "
"certificate" ) );
}
return( CRYPT_OK );
}
/* Determine how much to match before and after the splat */
postSplatLen = hostNameLength - splatPos - 1;
if( postSplatLen + splatPos > serverNameLength )
{
/* The fixed name spec text is longer than the server name, a match
can't be possible */
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, SESSION_ERRINFO,
"Server name doesn't match name in server certificate" ) );
}
/* Check that the pre- and post-splat URL components match */
if( splatPos > 0 && \
strCompare( hostName, sessionInfoPtr->serverName, splatPos ) )
{
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, SESSION_ERRINFO,
"Server name doesn't match name in server certificate" ) );
}
if( strCompare( hostName + splatPos + 1,
sessionInfoPtr->serverName + serverNameLength - postSplatLen,
postSplatLen ) )
{
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, SESSION_ERRINFO,
"Server name doesn't match name in server certificate" ) );
}
return( CRYPT_OK );
}
#endif /* 0 */
/****************************************************************************
* *
* Client-side Connect Functions *
* *
****************************************************************************/
/* Perform the initial part of the handshake with the server */
int beginClientHandshake( SESSION_INFO *sessionInfoPtr,
SSL_HANDSHAKE_INFO *handshakeInfo )
{
STREAM *stream = &handshakeInfo->stream;
#if 0 /* Old PSK mechanism */
const ATTRIBUTE_LIST *attributeListPtr = \
findSessionInfo( sessionInfoPtr->attributeList,
CRYPT_SESSINFO_USERNAME );
#endif /* 0 */
MESSAGE_DATA msgData;
int packetOffset, length, status;
/* Get the nonce that's used to randomise all crypto ops */
setMessageData( &msgData, handshakeInfo->clientNonce, SSL_NONCE_SIZE );
status = krnlSendMessage( SYSTEM_OBJECT_HANDLE, IMESSAGE_GETATTRIBUTE_S,
&msgData, CRYPT_IATTRIBUTE_RANDOM_NONCE );
if( cryptStatusError( status ) )
return( status );
/* Build the client hello packet:
byte ID = SSL_HAND_CLIENT_HELLO
uint24 len
byte[2] version = { 0x03, 0x0n }
uint32 time | Client nonce
byte[28] nonce |
byte sessIDlen
byte[] sessID
uint16 suiteLen
uint16[] suite
byte coprLen = 1
byte[] copr = { 0x00 }
[ uint16 extListLen | RFC 3546
byte extType
uint16 extLen
byte[] extData ] */
status = openPacketStreamSSL( stream, sessionInfoPtr, CRYPT_USE_DEFAULT,
SSL_MSG_HANDSHAKE );
if( cryptStatusError( status ) )
return( status );
packetOffset = continueHSPacketStream( stream, SSL_HAND_CLIENT_HELLO );
sputc( stream, SSL_MAJOR_VERSION );
sputc( stream, sessionInfoPtr->version );
handshakeInfo->clientOfferedVersion = sessionInfoPtr->version;
swrite( stream, handshakeInfo->clientNonce, SSL_NONCE_SIZE );
#if 0 /* Old PSK mechanism */
if( attributeListPtr != NULL )
{
BYTE buffer[ SESSIONID_SIZE + 8 ];
/* If there's a user name present, we're "resuming" a session based
on a shared secret, send the user name as the session ID */
sputc( stream, SESSIONID_SIZE );
memset( buffer, 0, SESSIONID_SIZE );
memcpy( buffer, attributeListPtr->value,
min( attributeListPtr->valueLength, SESSIONID_SIZE ) );
swrite( stream, buffer, SESSIONID_SIZE );
}
else
sputc( stream, 0 ); /* No session ID */
#else
sputc( stream, 0 ); /* No session ID */
#endif /* 0 */
writeCipherSuiteList( stream,
findSessionInfo( sessionInfoPtr->attributeList,
CRYPT_SESSINFO_USERNAME ) ? \
TRUE : FALSE );
sputc( stream, 1 ); /* No compression */
sputc( stream, 0 );
#if 0 /* TLS extension test code. Since few clients/servers do this, we
have to fake it ourselves for testing purpose. In addition the
RFC rather optimistically expects implementations to handle the
presence of unexpected data at the end of the hello packet, since
this is often not the case (quite a few servers fail the
handshake if extension data is present) we leave the following
disabled by default */
writeUint16( stream, UINT16_SIZE + UINT16_SIZE + 1 );
writeUint16( stream, TLS_EXT_MAX_FRAGMENT_LENTH );
writeUint16( stream, 1 );
sputc( stream, 3 );
#endif /* 0 */
status = completeHSPacketStream( stream, packetOffset );
if( cryptStatusOK( status ) )
status = sendPacketSSL( sessionInfoPtr, stream, FALSE );
if( cryptStatusError( status ) )
{
sMemDisconnect( stream );
return( status );
}
/* Perform the dual MAC'ing of the client hello in between the network
ops where it's effectively free */
status = dualMacDataWrite( handshakeInfo, stream );
sMemDisconnect( stream );
if( cryptStatusError( status ) )
return( status );
/* Process the server hello. The server usually sends us a session ID,
indicated by a return status of OK_SPECIAL, but we don't do anything
further with it since we won't be resuming this session */
status = readHSPacketSSL( sessionInfoPtr, handshakeInfo, &length,
SSL_MSG_FIRST_HANDSHAKE );
if( cryptStatusError( status ) )
return( status );
sMemConnect( stream, sessionInfoPtr->receiveBuffer, length );
status = processHelloSSL( sessionInfoPtr, handshakeInfo, stream, FALSE );
if( cryptStatusError( status ) && status != OK_SPECIAL )
{
sMemDisconnect( stream );
return( status );
}
return( CRYPT_OK );
}
/* Exchange keys with the server */
int exchangeClientKeys( SESSION_INFO *sessionInfoPtr,
SSL_HANDSHAKE_INFO *handshakeInfo )
{
STREAM *stream = &handshakeInfo->stream;
BOOLEAN needClientCert = FALSE;
int packetOffset, length, status;
/* Process the optional server supplemental data:
byte ID = SSL_HAND_SUPPLEMENTAL_DATA
uint24 len
uint16 type
uint16 len
byte[] value */
status = refreshHSStream( sessionInfoPtr, handshakeInfo );
if( cryptStatusError( status ) )
return( status );
if( sPeek( stream ) == SSL_HAND_SUPPLEMENTAL_DATA )
{
int type;
status = checkHSPacketHeader( sessionInfoPtr, stream, &length,
SSL_HAND_SUPPLEMENTAL_DATA,
UINT16_SIZE + UINT16_SIZE + 1 );
if( cryptStatusError( status ) )
{
sMemDisconnect( stream );
return( status );
}
type = readUint16( stream );
if( cryptStatusError( type ) )
{
sMemDisconnect( stream );
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, SESSION_ERRINFO,
"Invalid supplemental data type %04X", type ) );
}
length = readUint16( stream );
if( cryptStatusError( length ) || \
( length > 0 && cryptStatusError( sSkip( stream, length ) ) ) )
{
sMemDisconnect( stream );
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, SESSION_ERRINFO,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -