Exemple #1
0
int getPgpPacketInfo( INOUT STREAM *stream, 
					  OUT QUERY_INFO *queryInfo )
	{
	const long startPos = stell( stream );
	long offset, length;
	int ctb, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isWritePtr( queryInfo, sizeof( QUERY_INFO ) ) );

	/* Clear return value */
	memset( queryInfo, 0, sizeof( QUERY_INFO ) );

	/* Read the packet header and extract information from the CTB.  Note
	   that the assignment of version numbers is speculative only because
	   it's possible to use PGP 2.x packet headers to wrap up OpenPGP
	   packets */
	status = pgpReadPacketHeader( stream, &ctb, &length, 8 );
	if( cryptStatusError( status ) )
		return( status );
	queryInfo->formatType = CRYPT_FORMAT_PGP;
	queryInfo->version = pgpGetPacketVersion( ctb );
	offset = stell( stream );
	if( cryptStatusError( offset ) )
		return( offset );
	queryInfo->size = ( offset - startPos ) + length;
	switch( pgpGetPacketType( ctb ) )
		{
		case PGP_PACKET_SKE:
			queryInfo->type = CRYPT_OBJECT_ENCRYPTED_KEY;
			break;

		case PGP_PACKET_PKE:
			queryInfo->type = CRYPT_OBJECT_PKCENCRYPTED_KEY;
			break;

		case PGP_PACKET_SIGNATURE:
			queryInfo->type = CRYPT_OBJECT_SIGNATURE;
			break;

		case PGP_PACKET_SIGNATURE_ONEPASS:
			/* First half of a one-pass signature, this is given a special
			   type of 'none' since it's not a normal packet */
			queryInfo->type = CRYPT_OBJECT_NONE;
			break;

		default:
			DEBUG_DIAG(( "Found unrecognised ctb %X", ctb ));
			assert( DEBUG_WARN );
			return( CRYPT_ERROR_BADDATA );
		}

	/* Make sure that all of the data is present without resetting the 
	   stream */
	return( ( sMemDataLeft( stream ) < length ) ? \
			CRYPT_ERROR_UNDERFLOW : CRYPT_OK );
	}
Exemple #2
0
static int getObjectInfo( INOUT STREAM *stream, 
						  OUT QUERY_INFO *queryInfo )
	{
	const long startPos = stell( stream );
	long value;
	int tag, length, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isWritePtr( queryInfo, sizeof( QUERY_INFO ) ) );

	/* Clear return value */
	memset( queryInfo, 0, sizeof( QUERY_INFO ) );

	/* We always need at least MIN_CRYPT_OBJECTSIZE more bytes to do
	   anything */
	if( sMemDataLeft( stream ) < MIN_CRYPT_OBJECTSIZE )
		return( CRYPT_ERROR_UNDERFLOW );

	/* Get the type, length, and version information */
	status = getStreamObjectLength( stream, &length );
	if( cryptStatusError( status ) )
		return( status );
	queryInfo->formatType = CRYPT_FORMAT_CRYPTLIB;
	queryInfo->size = length;
	tag = peekTag( stream );
	if( cryptStatusError( tag ) )
		return( tag );
	readGenericHole( stream, NULL, 16, tag );
	status = readShortInteger( stream, &value );
	if( cryptStatusError( status ) )
		return( status );
	queryInfo->version = value;
	switch( tag )
		{
		case BER_SEQUENCE:
			/* This could be a signature or a PKC-encrypted key, see what
			   follows */
			switch( value )
				{
				case KEYTRANS_VERSION:
				case KEYTRANS_EX_VERSION:
					queryInfo->type = CRYPT_OBJECT_PKCENCRYPTED_KEY;
					break;

				case SIGNATURE_VERSION:
				case SIGNATURE_EX_VERSION:
					queryInfo->type = CRYPT_OBJECT_SIGNATURE;
					break;

				default:
					return( CRYPT_ERROR_BADDATA );
				}
			if( value == KEYTRANS_VERSION || value == SIGNATURE_VERSION )
				queryInfo->formatType = CRYPT_FORMAT_CMS;
			break;

		case MAKE_CTAG( CTAG_RI_KEYAGREE ):
			/* It's CMS' wierd X9.42-inspired key agreement mechanism, we
			   can't do much with this (mind you neither can anyone else)
			   so we should probably really treat it as a 
			   CRYPT_ERROR_BADDATA if we encounter it rather than just 
			   ignoring it */
			queryInfo->type = CRYPT_OBJECT_NONE;
			DEBUG_DIAG(( "Found keyAgreeRecipientInfo" ));
			assert( DEBUG_WARN );
			break;

		case MAKE_CTAG( CTAG_RI_PWRI ):
			queryInfo->type = CRYPT_OBJECT_ENCRYPTED_KEY;
			break;

		default:
			queryInfo->type = CRYPT_OBJECT_NONE;
			if( tag > MAKE_CTAG( CTAG_RI_PWRI ) && \
				tag <= MAKE_CTAG( CTAG_RI_MAX ) )
				{
				/* This is probably a new RecipientInfo type, skip it */
				DEBUG_DIAG(( "Found unknown RecipientInfo %X", tag ));
				assert( DEBUG_WARN );
				break;
				}
			return( CRYPT_ERROR_BADDATA );
		}

	/* Reset the stream and make sure that all of the data is present */
	sseek( stream, startPos );
	return( sMemDataLeft( stream ) < queryInfo->size ? \
			CRYPT_ERROR_UNDERFLOW : CRYPT_OK );
	}
Exemple #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 );
	}
Exemple #4
0
static int processPacketGroup( INOUT STREAM *stream, 
							   INOUT PGP_INFO *pgpInfo,
							   IN_OPT const KEY_MATCH_INFO *keyMatchInfo,
							   INOUT_OPT PGP_KEYINFO **matchedKeyInfoPtrPtr,
							   IN_INT_SHORT_Z const int keyGroupNo,
							   INOUT ERROR_INFO *errorInfo )
	{
	int iterationCount, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isWritePtr( pgpInfo, sizeof( PGP_INFO ) ) );
	assert( ( keyMatchInfo == NULL && matchedKeyInfoPtrPtr == NULL ) || \
			( isReadPtr( keyMatchInfo, sizeof( KEY_MATCH_INFO ) ) && \
			  isWritePtr( matchedKeyInfoPtrPtr, sizeof( PGP_KEYINFO * ) ) ) );

	REQUIRES( ( keyMatchInfo == NULL && matchedKeyInfoPtrPtr == NULL ) || \
			  ( keyMatchInfo != NULL && matchedKeyInfoPtrPtr != NULL && \
				pgpInfo->keyData != NULL && \
				pgpInfo->keyDataLen == KEYRING_BUFSIZE ) );
	REQUIRES( keyGroupNo >= 0 && keyGroupNo < MAX_INTLENGTH_SHORT );
	REQUIRES( errorInfo != NULL );

	/* Clear the index information before we read the current key(s), since 
	   it may already have been initialised during a previous (incomplete) 
	   key read */
	memset( &pgpInfo->key, 0, sizeof( PGP_KEYINFO ) );
	memset( &pgpInfo->subKey, 0, sizeof( PGP_KEYINFO ) );
	memset( pgpInfo->userID, 0, sizeof( char * ) * MAX_PGP_USERIDS );
	memset( pgpInfo->userIDlen, 0, sizeof( int ) * MAX_PGP_USERIDS );
	pgpInfo->lastUserID = 0;

	/* Read all the packets in this packet group */
	for( status = CRYPT_OK, iterationCount = 0;
		 cryptStatusOK( status ) && sMemDataLeft( stream ) > 0 && \
			iterationCount++ < FAILSAFE_ITERATIONS_MED;
		 iterationCount++ )
		{
		status = readKey( stream, pgpInfo, keyGroupNo, errorInfo );
		}
	ENSURES( iterationCount < FAILSAFE_ITERATIONS_MED );
	if( cryptStatusError( status ) )
		{
		if( status != OK_SPECIAL && status != CRYPT_ERROR_NOSECURE )
			return( status );

		/* There's either something in the key information that we can't 
		   handle or it's stored with no security, skip the key */
		if( keyMatchInfo == NULL )
			pgpFreeEntry( pgpInfo );
		return( status );
		}

	/* If we're reading all keys, we're done */
	if( keyMatchInfo == NULL )
		return( CRYPT_OK );

	/* We're searching for a particular key, see if this is the one */
	if( pgpCheckKeyMatch( pgpInfo, &pgpInfo->key, keyMatchInfo ) )
		{
		*matchedKeyInfoPtrPtr = &pgpInfo->key;
		return( CRYPT_OK );
		}
	if( pgpCheckKeyMatch( pgpInfo, &pgpInfo->subKey, keyMatchInfo ) )
		{
		*matchedKeyInfoPtrPtr = &pgpInfo->subKey;
		return( CRYPT_OK );
		}

	/* No match, tell the caller to keep looking */
	return( CRYPT_ERROR_NOTFOUND );
	}
Exemple #5
0
static int writePkiHeader( INOUT STREAM *stream,
                           INOUT SESSION_INFO *sessionInfoPtr,
                           INOUT CMP_PROTOCOL_INFO *protocolInfo )
{
    CRYPT_HANDLE senderNameObject = DUMMY_INIT, recipNameObject = DUMMY_INIT;
    STREAM nullStream;
    MESSAGE_DATA msgData;
#ifdef USE_FULL_HEADERS
    const BOOLEAN sendFullHeader = TRUE;
#else
    BOOLEAN sendFullHeader = FALSE;
#endif /* USE_FULL_HEADERS */
    BOOLEAN sendClibID = FALSE, sendCertID = FALSE, sendMacInfo = FALSE;
    BOOLEAN sendUserID = FALSE;
    int senderNameLength, recipNameLength, attributeLength = 0;
    int protInfoLength = DUMMY_INIT, totalLength, hashAlgo, status;

    assert( isWritePtr( stream, sizeof( STREAM ) ) );
    assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
    assert( isWritePtr( protocolInfo, sizeof( CMP_PROTOCOL_INFO ) ) );

    /* Determine which of the many unnecessary and inexplicable bits of the
       CMP header we actually have to send:

    	sendCertID: Sent on the first message where it's required (which
    		isn't necessarily the first message in an exchange, for example
    		it's not used in an ir/ip), either to identify the CA's cert in
    		a CTL sent in a PKIBoot response or to identify the signing
    		certificate when we're using signature-based message
    		authentication.

    	sendClibID: Sent on the first message to tell the other side that
    		this is a cryptlib client/server.

    	sendFullHeader: Sent if the other side isn't running cryptlib, unless
    		we're doing PKIBoot, for which we couldn't send full headers even
    		if we wanted to

    	sendMacInfo: Sent if we're using MAC integrity protection and the
    		the other side isn't running cryptlib, or if this is the first
    		message.

    	sendUserID: Sent on the first message or if we're sending full
    		headers, provided that it's actually available to send */
    if( !( sessionInfoPtr->protocolFlags & CMP_PFLAG_CERTIDSENT ) && \
            ( ( isServer( sessionInfoPtr ) && \
                protocolInfo->operation == CTAG_PB_GENM ) || \
              !protocolInfo->useMACsend ) )
        sendCertID = TRUE;
    if( !( sessionInfoPtr->protocolFlags & CMP_PFLAG_CLIBIDSENT ) )
        sendClibID = TRUE;
#ifndef USE_FULL_HEADERS
    if( !protocolInfo->isCryptlib && \
            protocolInfo->operation != CTAG_PB_GENM )
        sendFullHeader = TRUE;
#endif /* !USE_FULL_HEADERS */
    if( protocolInfo->useMACsend && \
            !( protocolInfo->isCryptlib && \
               ( sessionInfoPtr->protocolFlags & CMP_PFLAG_MACINFOSENT ) ) )
        sendMacInfo = TRUE;
    if( ( sendFullHeader || \
            !( sessionInfoPtr->protocolFlags & CMP_PFLAG_USERIDSENT ) ) && \
            ( protocolInfo->userIDsize > 0 ) )
        sendUserID = TRUE;

    REQUIRES( !sendFullHeader || !protocolInfo->headerRead || \
              ( protocolInfo->userIDsize > 0 && \
                protocolInfo->userIDsize < MAX_INTLENGTH_SHORT ) );
    REQUIRES( protocolInfo->transIDsize > 0 && \
              protocolInfo->transIDsize < MAX_INTLENGTH_SHORT );

    /* Get any other state information that we may need */
    status = krnlSendMessage( sessionInfoPtr->ownerHandle,
                              IMESSAGE_GETATTRIBUTE, &hashAlgo,
                              CRYPT_OPTION_ENCR_HASH );
    ENSURES( cryptStatusOK( status ) );
    protocolInfo->hashAlgo = hashAlgo;	/* int vs.enum */

    /* Determine how big the sender and recipient information will be.  We
       shouldn't need to send a recipient name for an ir because it won't
       usually be known yet, but various implementations can't handle a
       zero-length GeneralName so we supply it if it's available even though
       it's redundant */
    if( sendFullHeader )
    {
        status = initDNInfo( sessionInfoPtr, &senderNameObject,
                             &recipNameObject, &senderNameLength,
                             &recipNameLength,
                             ( protocolInfo->operation == CTAG_PB_IR ) ? \
                             TRUE : FALSE,
                             protocolInfo->cryptOnlyKey );
        if( cryptStatusError( status ) )
            return( status );
    }
    else
    {
        /* We're not using sender or recipient information since it doesn't
           serve any useful purpose, just set the fields to an empty
           SEQUENCE */
        senderNameLength = recipNameLength = sizeofObject( 0 );
    }

    /* Determine how big the remaining header data will be */
    sMemNullOpen( &nullStream );
    if( protocolInfo->useMACsend )
    {
        status = writeMacInfo( &nullStream, protocolInfo, sendMacInfo );
    }
    else
    {
        status = writeContextAlgoID( &nullStream, protocolInfo->authContext,
                                     protocolInfo->hashAlgo );
    }
    if( cryptStatusOK( status ) )
        protInfoLength = stell( &nullStream );
    sMemClose( &nullStream );
    if( cryptStatusError( status ) )
        return( status );
    if( sendClibID )
    {
        attributeLength += sizeofObject( \
                                         sizeofOID( OID_CRYPTLIB_PRESENCECHECK ) + \
                                         sizeofObject( 0 ) );
    }
    if( sendCertID )
    {
        const int certIDsize = sizeofCertID( protocolInfo->authContext );

        ENSURES( certIDsize > 0 && certIDsize < MAX_INTLENGTH_SHORT );

        attributeLength += certIDsize;
    }
    totalLength = sizeofShortInteger( CMP_VERSION ) + \
                  objSize( senderNameLength ) + objSize( recipNameLength ) + \
                  objSize( protInfoLength ) + \
                  objSize( sizeofObject( protocolInfo->transIDsize ) );
    if( sendUserID )
        totalLength += objSize( sizeofObject( protocolInfo->userIDsize ) );
    if( sendFullHeader )
    {
        if( protocolInfo->senderNonceSize > 0 )
            totalLength += objSize( \
                                    sizeofObject( protocolInfo->senderNonceSize ) );
        if( protocolInfo->recipNonceSize > 0 )
            totalLength += objSize( \
                                    sizeofObject( protocolInfo->recipNonceSize ) );
    }
    if( attributeLength > 0 )
        totalLength += objSize( objSize( attributeLength ) );

    /* Perform an early check for data-size problems before we go through
       all of the following code */
    if( sizeofObject( totalLength ) <= 0 || \
            sizeofObject( totalLength ) > sMemDataLeft( stream ) )
        return( CRYPT_ERROR_OVERFLOW );

    /* Write the PKI header wrapper, version information, and sender and
       recipient names if there's name information present */
    writeSequence( stream, totalLength );
    writeShortInteger( stream, CMP_VERSION, DEFAULT_TAG );
    if( sendFullHeader )
    {
        writeConstructed( stream, senderNameLength, 4 );
        if( senderNameObject != CRYPT_ERROR )
        {
            status = exportAttributeToStream( stream, senderNameObject,
                                              CRYPT_IATTRIBUTE_SUBJECT );
            if( cryptStatusError( status ) )
                return( status );
        }
        else
            writeSequence( stream, 0 );
        writeConstructed( stream, recipNameLength, 4 );
        if( recipNameObject != CRYPT_ERROR )
        {
            status = exportAttributeToStream( stream, recipNameObject,
                                              CRYPT_IATTRIBUTE_SUBJECT );
        }
        else
            status = writeSequence( stream, 0 );
    }
    else
    {
        /* This is one of the portions of CMP where an optional field is
           marked as mandatory, to balance out the mandatory fields that are
           marked as optional.  To work around this we write the names as
           zero-length DNs */
        writeConstructed( stream, senderNameLength, 4 );
        writeSequence( stream, 0 );
        writeConstructed( stream, recipNameLength, 4 );
        status = writeSequence( stream, 0 );
    }
    if( cryptStatusError( status ) )
        return( status );

    /* Write the protection information, assorted nonces and IDs, and extra
       information that the other side may be able to make use of */
    writeConstructed( stream, protInfoLength, CTAG_PH_PROTECTIONALGO );
    if( protocolInfo->useMACsend )
    {
        status = writeMacInfo( stream, protocolInfo, sendMacInfo );
        sessionInfoPtr->protocolFlags |= CMP_PFLAG_MACINFOSENT;
    }
    else
    {
        status = writeContextAlgoID( stream, protocolInfo->authContext,
                                     protocolInfo->hashAlgo );
    }
    if( cryptStatusError( status ) )
        return( status );
    if( sendUserID )
    {
        /* We're using full headers or we're the client sending our first
           message, identify the sender key */
        writeConstructed( stream, objSize( protocolInfo->userIDsize ),
                          CTAG_PH_SENDERKID );
        writeOctetString( stream, protocolInfo->userID,
                          protocolInfo->userIDsize, DEFAULT_TAG );
        DEBUG_PRINT(( "%s: Writing userID.\n",
                      protocolInfo->isServer ? "SVR" : "CLI" ));
        DEBUG_DUMP_HEX( protocolInfo->isServer ? "SVR" : "CLI",
                        protocolInfo->userID, protocolInfo->userIDsize );
        sessionInfoPtr->protocolFlags |= CMP_PFLAG_USERIDSENT;
    }
    writeConstructed( stream, objSize( protocolInfo->transIDsize ),
                      CTAG_PH_TRANSACTIONID );
    status = writeOctetString( stream, protocolInfo->transID,
                               protocolInfo->transIDsize, DEFAULT_TAG );
    if( cryptStatusError( status ) )
        return( status );
    if( sendFullHeader )
    {
        if( protocolInfo->senderNonceSize > 0 )
        {
            /* We're using nonces, generate a new sender nonce (the initial
               nonce will have been set when the protocol state was
               initialised) */
            setMessageData( &msgData, protocolInfo->senderNonce,
                            protocolInfo->senderNonceSize );
            krnlSendMessage( SYSTEM_OBJECT_HANDLE, IMESSAGE_GETATTRIBUTE_S,
                             &msgData, CRYPT_IATTRIBUTE_RANDOM_NONCE );
            writeConstructed( stream,
                              objSize( protocolInfo->senderNonceSize ),
                              CTAG_PH_SENDERNONCE );
            status = writeOctetString( stream, protocolInfo->senderNonce,
                                       protocolInfo->senderNonceSize,
                                       DEFAULT_TAG );
        }
        if( protocolInfo->recipNonceSize > 0 )
        {
            writeConstructed( stream,
                              objSize( protocolInfo->recipNonceSize ),
                              CTAG_PH_RECIPNONCE );
            status = writeOctetString( stream, protocolInfo->recipNonce,
                                       protocolInfo->recipNonceSize,
                                       DEFAULT_TAG );
        }
    }
    if( cryptStatusError( status ) )
        return( status );
    if( attributeLength > 0 )
    {
        ENSURES( sendClibID || sendCertID );

        /* We haven't sent any messages yet, let the other side know that
           we're running cryptlib and identify our signing certificate as
           required */
        writeConstructed( stream, objSize( attributeLength ),
                          CTAG_PH_GENERALINFO );
        writeSequence( stream, attributeLength );
        if( sendClibID )
        {
            sessionInfoPtr->protocolFlags |= CMP_PFLAG_CLIBIDSENT;
            writeSequence( stream, sizeofOID( OID_CRYPTLIB_PRESENCECHECK ) + \
                           sizeofObject( 0 ) );
            writeOID( stream, OID_CRYPTLIB_PRESENCECHECK );
            status = writeSet( stream, 0 );
        }
        if( sendCertID )
        {
            sessionInfoPtr->protocolFlags |= CMP_PFLAG_CERTIDSENT;
            status = writeCertID( stream, protocolInfo->authContext );
        }
    }
    return( status );
}