Пример #1
0
int streamBookmarkComplete( INOUT STREAM *stream, 
							OUT_OPT_PTR void **dataPtrPtr, 
							OUT_LENGTH_Z int *length, 
							IN_LENGTH const int position )
	{
	const int dataLength = stell( stream ) - position;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isWritePtr( dataPtrPtr, sizeof( void * ) ) );
	assert( isWritePtr( length, sizeof( int ) ) );

	REQUIRES( position >= 0 && position < MAX_INTLENGTH );
	REQUIRES( dataLength > 0 || dataLength < stell( stream ) );

	/* Clear return values */
	*dataPtrPtr = NULL;
	*length = 0;

	*length = dataLength;
	return( sMemGetDataBlockAbs( stream, position, dataPtrPtr, dataLength ) );
	}
Пример #2
0
static int pgpReadDecryptMPI( INOUT STREAM *stream,
							  IN_HANDLE const CRYPT_CONTEXT iCryptContext,
							  IN_LENGTH_PKC const int minLength, 
							  IN_LENGTH_PKC const int maxLength )
	{
	void *mpiDataPtr = DUMMY_INIT_PTR;
	const long mpiDataStartPos = stell( stream ) + UINT16_SIZE;
	int mpiLength, dummy, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	
	REQUIRES( isHandleRangeValid( iCryptContext ) );
	REQUIRES( minLength >= bitsToBytes( 155 ) && \
			  minLength <= maxLength && \
			  maxLength <= CRYPT_MAX_PKCSIZE );

	/* Get the MPI length and decrypt the payload data.  We have to be 
	   careful how we handle this because readInteger16Ubits() returns the 
	   canonicalised form of the values (with leading zeroes truncated) so 
	   the returned length value doesn't necessarily represent the amount
	   of data that we need to decrypt:

		startPos	dataStart		 stell()
			|			|				|
			v			v <-- length -->v
		+---+-----------+---------------+
		|	|			|///////////////| Stream
		+---+-----------+---------------+ */
	status = readInteger16Ubits( stream, NULL, &dummy, minLength, 
								 maxLength );
	if( cryptStatusError( status ) )
		return( status );
	mpiLength = stell( stream ) - mpiDataStartPos;
	status = sMemGetDataBlockAbs( stream, mpiDataStartPos, &mpiDataPtr, 
								  mpiLength );
	if( cryptStatusOK( status ) )
		status = krnlSendMessage( iCryptContext, IMESSAGE_CTX_DECRYPT,
								  mpiDataPtr, mpiLength );
	return( status );
	}
Пример #3
0
static int readKey( INOUT STREAM *stream, 
					INOUT PGP_INFO *pgpInfo, 
					IN_INT_SHORT_Z const int keyGroupNo,
					INOUT ERROR_INFO *errorInfo )
	{
	PGP_KEYINFO *keyInfo = &pgpInfo->key;
	HASHFUNCTION hashFunction;
	HASHINFO hashInfo;
	BYTE hash[ CRYPT_MAX_HASHSIZE + 8 ], packetHeader[ 64 + 8 ];
	BOOLEAN isPublicKey = TRUE, isPrimaryKey = FALSE;
	void *pubKeyPayload;
	long packetLength;
	int pubKeyPos, pubKeyPayloadPos, endPos, pubKeyPayloadLen;
	int ctb, length, value, hashSize, iterationCount, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isWritePtr( pgpInfo, sizeof( PGP_INFO ) ) );

	REQUIRES( keyGroupNo >= 0 && keyGroupNo < MAX_INTLENGTH_SHORT );
	REQUIRES( errorInfo != NULL );

	/* Process the CTB and packet length */
	ctb = sPeek( stream );
	if( cryptStatusError( ctb ) )
		{
		/* If there was an error reading the CTB, which is the first byte of 
		   the packet group, it means that we've run out of data so we 
		   return the status as a not-found error rather than the actual
		   stream status */
		return( CRYPT_ERROR_NOTFOUND );
		}
	switch( pgpGetPacketType( ctb ) )
		{
		case PGP_PACKET_SECKEY_SUB:
			keyInfo = &pgpInfo->subKey;
			isPublicKey = FALSE;
			break;

		case PGP_PACKET_SECKEY:
			isPublicKey = FALSE;
			break;

		case PGP_PACKET_PUBKEY_SUB:
			keyInfo = &pgpInfo->subKey;
			break;

		case PGP_PACKET_PUBKEY:
			isPrimaryKey = TRUE;
			break;

		default:
			retExt( CRYPT_ERROR_BADDATA, 
					( CRYPT_ERROR_BADDATA, errorInfo, 
					  "Invalid PGP CTB %02X for key packet group %d", 
					  ctb, keyGroupNo ) );
		}
	status = pgpReadPacketHeader( stream, NULL, &packetLength, 64 );
	if( cryptStatusError( status ) )
		{
		retExt( status,
				( status, errorInfo, 
				  "Invalid PGP key packet header for key packet group %d", 
				  keyGroupNo ) );
		}
	if( packetLength < 64 || packetLength > sMemDataLeft( stream ) )
		{
		retExt( CRYPT_ERROR_BADDATA, 
				( CRYPT_ERROR_BADDATA, errorInfo, 
				  "Invalid PGP key packet length %ld for key packet group %d", 
				  packetLength, keyGroupNo ) );
		}

	/* Since there can (in theory) be arbitrary numbers of subkeys and other 
	   odds and ends attached to a key and the details of what to do with 
	   these things gets a bit vague, we just skip any further subkeys that 
	   may be present */
	if( keyInfo->pkcAlgo != CRYPT_ALGO_NONE )
		{
		status = sSkip( stream, packetLength );
		for( iterationCount = 0; 
			 cryptStatusOK( status ) && \
				iterationCount < FAILSAFE_ITERATIONS_MED; 
			 iterationCount++ )
			{
			status = readUserID( stream, pgpInfo, 
								 isPrimaryKey ? &hashInfo : NULL );
			}
		ENSURES( iterationCount < FAILSAFE_ITERATIONS_MED );
		if( cryptStatusError( status ) && status != OK_SPECIAL )
			{
			retExt( status, 
					( status, errorInfo, 
					  "Invalid additional PGP subkey information for key "
					  "packet group %d", keyGroupNo ) );
			}

		/* We've skipped the current subkey, we're done */
		return( CRYPT_OK );
		}

	/* Determine which bits make up the public and the private key data.  The
	   public-key data starts at the version number and includes the date,
	   validity, and public-key components.  Since there's no length 
	   information included for this data block we have to record bookmarks
	   and then later retroactively calculate the length based on how much
	   data we've read in the meantime:

		  pubKey pubKeyPayload		 privKey			 endPos
			|		|					|					|
			v		v					v					v
		+---+---------------------------+-------------------+
		|hdr|		|	Public key		|	Private key		|
		+---+---------------------------+-------------------+
			|		|<pubKeyPayloadLen->|					|
			|<----- pubKeyDataLen ----->|<-- privKeyDLen -->| 
			|<--------------- packetLength ---------------->| */
	pubKeyPos = stell( stream );
	endPos = pubKeyPos + packetLength;
	ENSURES( endPos > pubKeyPos && endPos < MAX_BUFFER_SIZE );
	status = value = sgetc( stream );
	if( cryptStatusError( status ) )
		return( status );
	if( value != PGP_VERSION_2 && value != PGP_VERSION_3 && \
		value != PGP_VERSION_OPENPGP )
		{
		/* Unknown version number, skip this packet */
		return( OK_SPECIAL );
		}
	pgpInfo->isOpenPGP = ( value == PGP_VERSION_OPENPGP ) ? TRUE : FALSE;

	/* Build the packet header, which is hashed along with the key components
	   to get the OpenPGP keyID.  This is generated anyway when the context
	   is created but we need to generate it here as well in order to locate
	   the key in the first place:

		byte		ctb = 0x99
		byte[2]		length
		byte		version = 4
		byte[4]		key generation time
	  [	byte[2]		validity time - PGP 2.x only ]
		byte[]		key data

	   We can't add the length or key data yet since we have to parse the
	   key data to know how long it is, so we can only build the static part
	   of the header at this point */
	packetHeader[ 0 ] = 0x99;
	packetHeader[ 3 ] = PGP_VERSION_OPENPGP;

	/* Read the timestamp and validity period (for PGP 2.x keys) */
	status = sread( stream, packetHeader + 4, 4 );
	if( !cryptStatusError( status ) && !pgpInfo->isOpenPGP )
		status = sSkip( stream, 2 );
	if( cryptStatusError( status ) )
		return( status );

	/* Read the public key components */
	pubKeyPayloadPos = stell( stream );
	status = readPublicKeyComponents( stream, keyInfo, &length );
	if( cryptStatusError( status ) )
		{
		/* If the error status is OK_SPECIAL then the problem was an
		   unrecognised algorithm or something similar so we just skip the 
		   packet */
		if( status == OK_SPECIAL )
			{
			DEBUG_DIAG(( "Encountered unrecognised algorithm while "
						 "reading key" ));
			assert( DEBUG_WARN );
			return( OK_SPECIAL );
			}
		retExt( status, 
				( status, errorInfo, 
				  "Invalid PGP public-key components for key packet group %d",
				  keyGroupNo ) );
		}

	/* Now that we know where the public key data starts and finishes, we 
	   can set up references to it */
	keyInfo->pubKeyDataLen = stell( stream ) - pubKeyPos;
	status = sMemGetDataBlockAbs( stream, pubKeyPos, &keyInfo->pubKeyData, 
								  keyInfo->pubKeyDataLen );
	if( cryptStatusError( status ) )
		{
		DEBUG_DIAG(( "Couldn't set up reference to key data" ));
		assert( DEBUG_WARN );
		return( status );
		}
	pubKeyPayloadLen = stell( stream ) - pubKeyPayloadPos;
	status = sMemGetDataBlockAbs( stream, pubKeyPayloadPos, &pubKeyPayload, 
								  pubKeyPayloadLen );
	if( cryptStatusError( status ) )
		{
		DEBUG_DIAG(( "Couldn't set up reference to key data" ));
		assert( DEBUG_WARN );
		return( status );
		}

	/* Complete the packet header that we read earlier on by adding the
	   length information */
	packetHeader[ 1 ] = intToByte( ( ( 1 + 4 + length ) >> 8 ) & 0xFF );
	packetHeader[ 2 ] = intToByte( ( 1 + 4 + length ) & 0xFF );

	/* Hash the data needed to generate the OpenPGP keyID */
	getHashParameters( CRYPT_ALGO_SHA1, 0, &hashFunction, &hashSize );
	hashFunction( hashInfo, NULL, 0, packetHeader, 1 + 2 + 1 + 4, 
				  HASH_STATE_START );
	hashFunction( hashInfo, hash, CRYPT_MAX_HASHSIZE, 
				  pubKeyPayload, pubKeyPayloadLen, HASH_STATE_END );
	memcpy( keyInfo->openPGPkeyID, hash + hashSize - PGP_KEYID_SIZE,
			PGP_KEYID_SIZE );

	/* If it's a private keyring, process the private key components */
	if( !isPublicKey )
		{
		/* Handle decryption information for private-key components if 
		   necessary */
		status = readPrivateKeyDecryptionInfo( stream, keyInfo );
		if( cryptStatusError( status ) )
			{
			/* If the error status is OK_SPECIAL then the problem was an
			   unrecognised algorithm or something similar so we just skip
			   the packet */
			if( status == OK_SPECIAL )
				{
				DEBUG_DIAG(( "Encountered unrecognised algorithm while "
							 "reading key" ));
				assert( DEBUG_WARN );
				return( OK_SPECIAL );
				}
			retExt( status, 
					( status, errorInfo, 
					  "Invalid PGP private-key decryption information for "
					  "key packet group %d", keyGroupNo ) );
			}

		/* What's left is the private-key data */
		keyInfo->privKeyDataLen = endPos - stell( stream );
		status = sMemGetDataBlock( stream, &keyInfo->privKeyData, 
								   keyInfo->privKeyDataLen );
		if( cryptStatusOK( status ) )
			status = sSkip( stream, keyInfo->privKeyDataLen );
		if( cryptStatusError( status ) )
			return( status );
		}

	/* If it's the primary key, start hashing it in preparation for 
	   performing signature checks on subpackets */
	if( isPrimaryKey )
		{
		packetHeader[ 0 ] = 0x99;
		packetHeader[ 1 ] = intToByte( ( keyInfo->pubKeyDataLen >> 8 ) & 0xFF );
		packetHeader[ 2 ] = intToByte( keyInfo->pubKeyDataLen & 0xFF );	
		hashFunction( hashInfo, NULL, 0, packetHeader, 1 + 2, 
					  HASH_STATE_START );
		hashFunction( hashInfo, NULL, 0, keyInfo->pubKeyData, 
					  keyInfo->pubKeyDataLen, HASH_STATE_CONTINUE );
		}

	/* Read any associated subpacket(s), of which the only ones of real 
	   interest are the userID packet(s) */
	for( iterationCount = 0; 
		 cryptStatusOK( status ) && \
			iterationCount < FAILSAFE_ITERATIONS_MED; 
		 iterationCount++ )
		{
		status = readUserID( stream, pgpInfo, 
							 isPrimaryKey ? &hashInfo : NULL );
		}
	ENSURES( iterationCount < FAILSAFE_ITERATIONS_MED );
	if( cryptStatusError( status ) && status != OK_SPECIAL )
		{
		retExt( status, 
				( status, errorInfo, 
				  "Invalid PGP userID information for key packet group %d",
				  keyGroupNo ) );
		}

	/* If there's no user ID present, set a generic label */
	if( pgpInfo->lastUserID <= 0 )
		{
		pgpInfo->userID[ 0 ] = "PGP key (no user ID found)";
		pgpInfo->userIDlen[ 0 ] = 26;
		pgpInfo->lastUserID = 1;
		}

	return( CRYPT_OK );
	}
Пример #4
0
int beginServerHandshake( INOUT SESSION_INFO *sessionInfoPtr, 
						  INOUT SSL_HANDSHAKE_INFO *handshakeInfo )
	{
	STREAM *stream = &handshakeInfo->stream;
	SCOREBOARD_LOOKUP_RESULT lookupResult = DUMMY_INIT_STRUCT;
	MESSAGE_DATA msgData;
	int length, resumedSessionID = CRYPT_ERROR;
	int packetOffset, status;

	assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
	assert( isWritePtr( handshakeInfo, sizeof( SSL_HANDSHAKE_INFO ) ) );

	/* Read and process the client hello */
	status = readHSPacketSSL( sessionInfoPtr, handshakeInfo, &length,
							  SSL_MSG_FIRST_HANDSHAKE );
	if( cryptStatusError( status ) )
		return( status );
	sMemConnect( stream, sessionInfoPtr->receiveBuffer, length );
	status = processHelloSSL( sessionInfoPtr, handshakeInfo, stream, TRUE );
	sMemDisconnect( stream );
	if( cryptStatusError( status ) )
		{
		if( status != OK_SPECIAL )
			return( status );

		/* The client has sent us a sessionID in an attempt to resume a 
		   previous session, see if it's in the session cache */
		resumedSessionID = \
			lookupScoreboardEntry( sessionInfoPtr->sessionSSL->scoreboardInfoPtr,
					SCOREBOARD_KEY_SESSIONID_SVR, handshakeInfo->sessionID, 
					handshakeInfo->sessionIDlength,
					&lookupResult );
#ifdef CONFIG_SUITEB_TESTS 
		resumedSessionID = CRYPT_ERROR;	/* Disable for Suite B tests */
#endif /* CONFIG_SUITEB_TESTS */
		}

	/* Handle session resumption.  If it's a new session or the session data 
	   has expired from the cache, generate a new session ID */
	if( cryptStatusError( resumedSessionID ) )
		{
		setMessageData( &msgData, handshakeInfo->sessionID, SESSIONID_SIZE );
		status = krnlSendMessage( SYSTEM_OBJECT_HANDLE, 
								  IMESSAGE_GETATTRIBUTE_S, &msgData, 
								  CRYPT_IATTRIBUTE_RANDOM_NONCE );
		if( cryptStatusError( status ) )
			return( status );
		handshakeInfo->sessionIDlength = SESSIONID_SIZE;
		}
	else
		{
		/* We're resuming a previous session, remember the premaster secret */
		status = attributeCopyParams( handshakeInfo->premasterSecret, 
									  SSL_SECRET_SIZE,
									  &handshakeInfo->premasterSecretSize,
									  lookupResult.data, 
									  lookupResult.dataSize );
		ENSURES( cryptStatusOK( status ) );
		}

	/* Get the nonce that's used to randomise all crypto operations and set 
	   up the server DH/ECDH context if necessary */
	setMessageData( &msgData, handshakeInfo->serverNonce, SSL_NONCE_SIZE );
	status = krnlSendMessage( SYSTEM_OBJECT_HANDLE, IMESSAGE_GETATTRIBUTE_S, 
							  &msgData, CRYPT_IATTRIBUTE_RANDOM_NONCE );
	if( cryptStatusOK( status ) && isKeyxAlgo( handshakeInfo->keyexAlgo ) )
		{
		status = initDHcontextSSL( &handshakeInfo->dhContext, NULL, 0,
							( handshakeInfo->authAlgo != CRYPT_ALGO_NONE ) ? \
							sessionInfoPtr->privateKey : CRYPT_UNUSED,
							isEccAlgo( handshakeInfo->keyexAlgo ) ? \
								handshakeInfo->eccCurveID : CRYPT_ECCCURVE_NONE );
		}
	if( cryptStatusError( status ) )
		return( status );

	/* Build the server hello, certificate, optional certificate request, 
	   and done packets:

		byte		ID = SSL_HAND_SERVER_HELLO
		uint24		len
		byte[2]		version = { 0x03, 0x0n }
		byte[32]	nonce
		byte		sessIDlen
		byte[]		sessID
		uint16		suite
		byte		copr = 0
	  [	uint16	extListLen		-- RFC 3546/RFC 4366
			byte	extType
			uint16	extLen
			byte[]	extData ] 
		...

	   We have to be careful how we handle extensions because the RFC makes 
	   the rather optimistic assumption that implementations can handle the 
	   presence of unexpected data at the end of the hello packet, to avoid 
	   problems with this we avoid sending extensions unless they're in 
	   response to extensions already sent by the client */
	status = openPacketStreamSSL( stream, sessionInfoPtr, CRYPT_USE_DEFAULT, 
								  SSL_MSG_HANDSHAKE );
	if( cryptStatusError( status ) )
		return( status );
	status = continueHSPacketStream( stream, SSL_HAND_SERVER_HELLO, 
									 &packetOffset );
	if( cryptStatusError( status ) )
		{
		sMemDisconnect( stream );
		return( status );
		}
	sputc( stream, SSL_MAJOR_VERSION );
	sputc( stream, sessionInfoPtr->version );
	swrite( stream, handshakeInfo->serverNonce, SSL_NONCE_SIZE );
	sputc( stream, handshakeInfo->sessionIDlength );
	swrite( stream, handshakeInfo->sessionID, 
			handshakeInfo->sessionIDlength );
	writeUint16( stream, handshakeInfo->cipherSuite ); 
	status = sputc( stream, 0 );	/* No compression */
	if( handshakeInfo->hasExtensions )
		status = writeServerExtensions( stream, handshakeInfo );
	if( cryptStatusOK( status ) )
		status = completeHSPacketStream( stream, packetOffset );
	if( cryptStatusError( status ) )
		{
		sMemDisconnect( stream );
		return( status );
		}

	/* If it's a resumed session then the server hello is followed 
	   immediately by the change cipherspec, which is sent by the shared 
	   handshake completion code */
	if( !cryptStatusError( resumedSessionID ) )
		{
		status = completePacketStreamSSL( stream, 0 );
		if( cryptStatusOK( status ) )
			status = hashHSPacketWrite( handshakeInfo, stream, 0 );
		if( cryptStatusError( status ) )
			{
			sMemDisconnect( stream );
			return( status );
			}

		/* Tell the caller that it's a resumed session, leaving the stream
		   open in order to write the change cipherspec message that follows 
		   the server hello in a resumed session */
		DEBUG_PRINT(( "Resuming session with client based on "
					  "sessionID = \n" ));
		DEBUG_DUMP_DATA( handshakeInfo->sessionID, 
						 handshakeInfo->sessionIDlength );
		return( OK_SPECIAL );
		}

	/*	...	(optional server supplemental data)
		byte		ID = SSL_HAND_SUPPLEMENTAL_DATA
		uint24		len
		uint16		type
		uint16		len
		byte[]		value
		... */

	/*	...
		(optional server certificate chain)
		... */
	if( handshakeInfo->authAlgo != CRYPT_ALGO_NONE )
		{
		status = writeSSLCertChain( sessionInfoPtr, stream );
		if( cryptStatusError( status ) )
			{
			sMemDisconnect( stream );
			return( status );
			}
		}

	/*	...			(optional server keyex)
		byte		ID = SSL_HAND_SERVER_KEYEXCHANGE
		uint24		len
	   DH:
		uint16		dh_pLen
		byte[]		dh_p
		uint16		dh_gLen
		byte[]		dh_g
		uint16		dh_YsLen
		byte[]		dh_Ys
	  [	byte		hashAlgoID		-- TLS 1.2 ]
	  [	byte		sigAlgoID		-- TLS 1.2 ]
		uint16		signatureLen
		byte[]		signature
	   ECDH:
		byte		curveType
		uint16		namedCurve
		uint8		ecPointLen		-- NB uint8 not uint16
		byte[]		ecPoint
	  [	byte		hashAlgoID		-- TLS 1.2 ]
	  [	byte		sigAlgoID		-- TLS 1.2 ]
		uint16		signatureLen
		byte[]		signature */
	if( isKeyxAlgo( handshakeInfo->keyexAlgo ) )
		{
		KEYAGREE_PARAMS keyAgreeParams;
		void *keyData = DUMMY_INIT_PTR;
		int keyDataOffset, keyDataLength = DUMMY_INIT;

		/* Perform phase 1 of the DH/ECDH key agreement process */
		memset( &keyAgreeParams, 0, sizeof( KEYAGREE_PARAMS ) );
		status = krnlSendMessage( handshakeInfo->dhContext,
								  IMESSAGE_CTX_ENCRYPT, &keyAgreeParams,
								  sizeof( KEYAGREE_PARAMS ) );
		if( cryptStatusError( status ) )
			{
			zeroise( &keyAgreeParams, sizeof( KEYAGREE_PARAMS ) );
			sMemDisconnect( stream );
			return( status );
			}

		/* Write the DH/ECDH key parameters and public value and sign them */
		status = continueHSPacketStream( stream, SSL_HAND_SERVER_KEYEXCHANGE, 
										 &packetOffset );
		if( cryptStatusError( status ) )
			{
			zeroise( &keyAgreeParams, sizeof( KEYAGREE_PARAMS ) );
			sMemDisconnect( stream );
			return( status );
			}
		keyDataOffset = stell( stream );
		status = exportAttributeToStream( stream, handshakeInfo->dhContext,
										  CRYPT_IATTRIBUTE_KEY_SSL );
		if( cryptStatusOK( status ) )
			{
			if( isEccAlgo( handshakeInfo->keyexAlgo ) )
				{
				sputc( stream, keyAgreeParams.publicValueLen );
				swrite( stream, keyAgreeParams.publicValue,
						keyAgreeParams.publicValueLen );
				}
			else
				{
				status = writeInteger16U( stream, keyAgreeParams.publicValue, 
										  keyAgreeParams.publicValueLen );
				}
			}
		if( cryptStatusOK( status ) )
			{
			keyDataLength = stell( stream ) - keyDataOffset;
			status = sMemGetDataBlockAbs( stream, keyDataOffset, &keyData, 
										  keyDataLength );
			}
		if( cryptStatusOK( status ) )
			{
			status = createKeyexSignature( sessionInfoPtr, handshakeInfo,
										   stream, keyData, keyDataLength );
			}
		zeroise( &keyAgreeParams, sizeof( KEYAGREE_PARAMS ) );
		if( cryptStatusOK( status ) )
			status = completeHSPacketStream( stream, packetOffset );
		if( cryptStatusError( status ) )
			{
			sMemDisconnect( stream );
			return( status );
			}
		}

	/*	...			(optional request for client certificate authentication)
		byte		ID = SSL_HAND_SERVER_CERTREQUEST
		uint24		len
		byte		certTypeLen
		byte[]		certType = { RSA, DSA, ECDSA }
	  [	uint16	sigHashListLen		-- TLS 1.2 ]
	  [		byte	hashAlgoID		-- TLS 1.2 ]
	  [		byte	sigAlgoID		-- TLS 1.2 ]
		uint16		caNameListLen = 4
			uint16	caNameLen = 2
			byte[]	caName = { 0x30, 0x00 }
		... */
	if( clientCertAuthRequired( sessionInfoPtr ) )
		{
		status = continueHSPacketStream( stream, SSL_HAND_SERVER_CERTREQUEST, 
										 &packetOffset );
		if( cryptStatusError( status ) )
			{
			sMemDisconnect( stream );
			return( status );
			}
		status = writeCertRequest( sessionInfoPtr, handshakeInfo, stream );
		if( cryptStatusOK( status ) )
			status = completeHSPacketStream( stream, packetOffset );
		if( cryptStatusError( status ) )
			{
			sMemDisconnect( stream );
			return( status );
			}
		}

	/*	...
		byte		ID = SSL_HAND_SERVER_HELLODONE
		uint24		len = 0 */
	status = continueHSPacketStream( stream, SSL_HAND_SERVER_HELLODONE, 
									 &packetOffset );
	if( cryptStatusOK( status ) )
		status = completeHSPacketStream( stream, packetOffset );
	if( cryptStatusError( status ) )
		{
		sMemDisconnect( stream );
		return( status );
		}

	/* Send the combined server packets to the client.  We perform the 
	   assorted hashing of the packets in between the network ops where 
	   it's effectively free */
	status = sendPacketSSL( sessionInfoPtr, stream, FALSE );
	if( cryptStatusOK( status ) )
		status = hashHSPacketWrite( handshakeInfo, stream, 0 );
	sMemDisconnect( stream );
	return( status );
	}