/****************************************************************************
*																			*
*						  Certificate Signing Routines						*
*						Copyright Peter Gutmann 1997-2007					*
*																			*
****************************************************************************/

#if defined( INC_ALL )
  #include "cert.h"
  #include "asn1.h"
#else
  #include "cert/cert.h"
  #include "misc/asn1.h"
#endif /* Compiler-specific includes */

/****************************************************************************
*																			*
*								Utility Routines							*
*																			*
****************************************************************************/

/* Recover information normally set up on certificate import.  After 
   signing the certificate data is present without the certificate having 
   been explicitly imported so we have to explicitly perform the actions 
   normally performed on certificate import here */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3 ) ) \
static int recoverCertData( INOUT CERT_INFO *certInfoPtr,
							IN_ENUM( CRYPT_CERTTYPE ) \
								const CRYPT_CERTTYPE_TYPE certType, 
							IN_BUFFER( encodedCertDataLength ) \
								const void *encodedCertData,
							IN_LENGTH_SHORT_MIN( 16 ) \
								const int encodedCertDataLength )
	{
	STREAM stream;
	int length, status;

	assert( isWritePtr( certInfoPtr, sizeof( CERT_INFO ) ) );
	assert( isReadPtr( encodedCertData, encodedCertDataLength ) );

	REQUIRES( certType > CRYPT_CERTTYPE_NONE && \
			  certType < CRYPT_CERTTYPE_LAST );
	REQUIRES( encodedCertDataLength >= 16 && \
			  encodedCertDataLength < MAX_INTLENGTH_SHORT );

	/* If there's public-key data stored with the certificate free it since 
	   we now have a copy as part of the encoded certificate */
	if( certInfoPtr->publicKeyData != NULL )
		{
		zeroise( certInfoPtr->publicKeyData, certInfoPtr->publicKeyInfoSize );
		clFree( "recoverCertData", certInfoPtr->publicKeyData );
		certInfoPtr->publicKeyData = NULL;
		}

	/* If it's a CRMF request parse the signed form to locate the start of
	   the encoded DN if there is one (the issuer DN is already set up when
	   the issuer certificate is added) and the public key.  The public key 
	   is actually something of a special case in that in the CRMF/CMP 
	   tradition it has a weird nonstandard tag which means that a straight 
	   memcpy() won't move the data across correctly */
	if( certType == CRYPT_CERTTYPE_REQUEST_CERT )
		{
		sMemConnect( &stream, encodedCertData, encodedCertDataLength );
		readSequence( &stream, NULL );			/* Outer wrapper */
		readSequence( &stream, NULL );
		readUniversal( &stream );				/* Request ID */
		status = readSequence( &stream, NULL );	/* Inner wrapper */
		if( cryptStatusOK( status ) && \
			peekTag( &stream ) == MAKE_CTAG( 4 ) )
			status = readUniversal( &stream );	/* Validity */
		if( cryptStatusOK( status ) && \
			peekTag( &stream ) == MAKE_CTAG( 5 ) )
			{
			status = readConstructed( &stream, &length, 5 );
			if( cryptStatusOK( status ) )		/* Subj.name wrapper */
				status = sMemGetDataBlock( &stream, &certInfoPtr->subjectDNptr, 
										   certInfoPtr->subjectDNsize );
			ENSURES( cryptStatusOK( status ) );
			status = readUniversal( &stream );	/* Subject name */
			}
		if( cryptStatusOK( status ) && \
			peekTag( &stream ) != MAKE_CTAG( 6 ) )	/* Public key */
			status = CRYPT_ERROR_BADDATA;
		if( cryptStatusOK( status ) )
			status = sMemGetDataBlock( &stream, &certInfoPtr->publicKeyInfo, 
									   certInfoPtr->publicKeyInfoSize );
		ENSURES( cryptStatusOK( status ) && \
				 cryptStatusOK( getStreamObjectLength( &stream, &length ) ) && \
				 length == certInfoPtr->publicKeyInfoSize );
		sMemDisconnect( &stream );

		return( CRYPT_OK );
		}

	/* If it's PKI user data parse the encoded form to locate the start of
	   the user DN */
	if( certInfoPtr->type == CRYPT_CERTTYPE_PKIUSER )
		{
		sMemConnect( &stream, encodedCertData, encodedCertDataLength );
		readSequence( &stream, NULL );		/* Outer wrapper */
		status = readSequence( &stream, &length );
		if( cryptStatusOK( status ) )
			status = sMemGetDataBlock( &stream, &certInfoPtr->subjectDNptr, 
									   certInfoPtr->subjectDNsize );
		sMemDisconnect( &stream );
		ENSURES( cryptStatusOK( status ) );

		return( CRYPT_OK );
		}

	ENSURES( certType == CRYPT_CERTTYPE_CERTIFICATE || \
			 certType == CRYPT_CERTTYPE_CERTCHAIN );

	/* It's a certificate, parse the signed form to locate the start of the
	   encoded issuer and subject DN and public key (the length is recorded
	   when the certificate data is written but the position of the other 
	   elements in the certificate can't be determined until the certificate 
	   has been signed) */
	sMemConnect( &stream, encodedCertData, encodedCertDataLength );
	readSequence( &stream, NULL );			/* Outer wrapper */
	status = readSequence( &stream, NULL );	/* Inner wrapper */
	if( cryptStatusOK( status ) && \
		peekTag( &stream ) == MAKE_CTAG( 0 ) )
		readUniversal( &stream );			/* Version */
	readUniversal( &stream );				/* Serial number */
	status = readUniversal( &stream );		/* Signature algo */
	if( cryptStatusOK( status ) )
		status = sMemGetDataBlock( &stream, &certInfoPtr->issuerDNptr, 
								   certInfoPtr->issuerDNsize );
	ENSURES( cryptStatusOK( status ) );
	readUniversal( &stream );				/* Issuer DN */
	status = readUniversal( &stream );		/* Validity */
	if( cryptStatusOK( status ) )
		status = sMemGetDataBlock( &stream, &certInfoPtr->subjectDNptr, 
								   certInfoPtr->subjectDNsize );
	ENSURES( cryptStatusOK( status ) );
	status = readUniversal( &stream );		/* Subject DN */
	if( cryptStatusOK( status ) )
		status = sMemGetDataBlock( &stream, &certInfoPtr->publicKeyInfo, 
								   certInfoPtr->publicKeyInfoSize );
	ENSURES( cryptStatusOK( status ) && \
			 cryptStatusOK( getStreamObjectLength( &stream, &length ) ) && \
			 length == certInfoPtr->publicKeyInfoSize );
	sMemDisconnect( &stream );

	/* Since the certificate may be used for public-key operations as soon 
	   as it's signed we have to reconstruct the public-key context and 
	   apply to it the constraints that would be applied on import */
	sMemConnect( &stream, certInfoPtr->publicKeyInfo,
				 certInfoPtr->publicKeyInfoSize );
	status = iCryptReadSubjectPublicKey( &stream,
										 &certInfoPtr->iPubkeyContext,
										 FALSE );
	sMemDisconnect( &stream );
	if( cryptStatusError( status ) )
		return( status );
	status = krnlSendMessage( certInfoPtr->objectHandle,
							  IMESSAGE_SETDEPENDENT,
							  &certInfoPtr->iPubkeyContext,
							  SETDEP_OPTION_NOINCREF );
	if( cryptStatusOK( status ) )
		certInfoPtr->flags &= ~CERT_FLAG_DATAONLY;
	return( status );
	}


/* Check the key being used to sign a certificate object */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
static int checkSigningKey( INOUT CERT_INFO *certInfoPtr,
							const CERT_INFO *issuerCertInfoPtr,
							const BOOLEAN isCertificate,
							IN_RANGE( CRYPT_COMPLIANCELEVEL_OBLIVIOUS, \
									  CRYPT_COMPLIANCELEVEL_LAST - 1 ) \
								const int complianceLevel )
	{
	int status;

	assert( isWritePtr( certInfoPtr, sizeof( CERT_INFO ) ) );
	assert( isReadPtr( issuerCertInfoPtr, sizeof( CERT_INFO ) ) );

	REQUIRES( complianceLevel >= CRYPT_COMPLIANCELEVEL_OBLIVIOUS && \
			  complianceLevel < CRYPT_COMPLIANCELEVEL_LAST );

	/* Make sure that the signing key is associated with a complete issuer 
	   certificate which is valid for certificate/CRL signing (if it's a 
	   self-signed certificate then we don't have to have a completed 
	   certificate present because the self-sign operation hasn't created it 
	   yet) */
	if( ( !( certInfoPtr->flags & CERT_FLAG_SELFSIGNED ) && \
		  issuerCertInfoPtr->certificate == NULL ) || \
		( issuerCertInfoPtr->type != CRYPT_CERTTYPE_CERTIFICATE && \
		  issuerCertInfoPtr->type != CRYPT_CERTTYPE_CERTCHAIN ) )
		return( CRYPT_ARGERROR_VALUE );

	/* If it's an OCSP request or response then the signing certificate has 
	   to be valid for signing */
	if( certInfoPtr->type == CRYPT_CERTTYPE_OCSP_REQUEST || \
		certInfoPtr->type == CRYPT_CERTTYPE_OCSP_RESPONSE )
		{
		return( checkKeyUsage( issuerCertInfoPtr, CHECKKEY_FLAG_NONE,
							   CRYPT_KEYUSAGE_DIGITALSIGNATURE | \
							   CRYPT_KEYUSAGE_NONREPUDIATION,
							   complianceLevel, &certInfoPtr->errorLocus,
							   &certInfoPtr->errorType ) );
		}

	/* If it's a non-self-signed object then it must be signed by a CA 
	   certificate */
	if( !( certInfoPtr->flags & CERT_FLAG_SELFSIGNED ) )
		{
		status = checkKeyUsage( issuerCertInfoPtr, CHECKKEY_FLAG_CA,
								isCertificate ? CRYPT_KEYUSAGE_KEYCERTSIGN : \
												CRYPT_KEYUSAGE_CRLSIGN,
								complianceLevel, &certInfoPtr->errorLocus,
								&certInfoPtr->errorType );
		if( cryptStatusError( status ) && \
			certInfoPtr->errorType == CRYPT_ERRTYPE_CONSTRAINT )
			{
			/* If there was a constraint problem it's something in the 
			   issuer's certificate rather than the certificate being signed 
			   so we have to change the error type accordingly.  What's 
			   reported isn't strictly accurate since the locus is in the 
			   issuer rather than subject certificate but it's the best that 
			   we can do */
			certInfoPtr->errorType = CRYPT_ERRTYPE_ISSUERCONSTRAINT;
			}

		return( status );
		}

	return( CRYPT_OK );
	}

/****************************************************************************
*																			*
*							Signing Setup Functions							*
*																			*
****************************************************************************/

/* Copy a signing certificate chain into the certificate being signed */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
static int copySigningCertChain( INOUT CERT_INFO *certInfoPtr,
								 IN_HANDLE const CRYPT_CONTEXT iSignContext )
	{
	CERT_CERT_INFO *certInfo = certInfoPtr->cCertCert;

	assert( isWritePtr( certInfoPtr, sizeof( CERT_INFO ) ) );

	REQUIRES( isHandleRangeValid( iSignContext ) );

	/* If there's a chain of certificates present (for example from a 
	   previous signing attempt that wasn't completed due to an error), free 
	   them */
	if( certInfo->chainEnd > 0 )
		{
		int i;
			
		for( i = 0; i < certInfo->chainEnd && i < MAX_CHAINLENGTH; i++ )
			krnlSendNotifier( certInfo->chain[ i ], IMESSAGE_DECREFCOUNT );
		certInfo->chainEnd = 0;
		}

	/* If it's a self-signed certificate it must be the only one in the 
	   chain (creating a chain like this doesn't make much sense but we 
	   handle it anyway) */
	if( certInfoPtr->flags & CERT_FLAG_SELFSIGNED )
		{
		if( certInfo->chainEnd > 0 )
			{
			setErrorInfo( certInfoPtr, CRYPT_CERTINFO_CERTIFICATE,
						  CRYPT_ERRTYPE_ATTR_PRESENT );
			return( CRYPT_ERROR_INVALID );
			}
		
		return( CRYPT_OK );
		}

	/* Copy the certificate chain into the certificate to be signed */
	return( copyCertChain( certInfoPtr, iSignContext, FALSE ) );
	}

/* Set up any required timestamp data for a certificate object */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
static int setCertTimeinfo( INOUT CERT_INFO *certInfoPtr,
							IN_HANDLE_OPT const CRYPT_CONTEXT iSignContext,
							const BOOLEAN isCertificate )
	{
	const time_t currentTime = ( iSignContext == CRYPT_UNUSED ) ? \
							   getTime() : getReliableTime( iSignContext );
	int status;

	assert( isWritePtr( certInfoPtr, sizeof( CERT_INFO ) ) );

	REQUIRES( iSignContext == CRYPT_UNUSED || \
			  isHandleRangeValid( iSignContext ) );

	/* If it's some certificate variant or CRL/OCSP response and the various 
	   timestamps haven't been set yet start them at the current time and 
	   give them the default validity period or next update time if these
	   haven't been set.  The time used is the local time, this is converted 
	   to GMT when we write it to the certificate.  Issues like validity
	   period nesting and checking for valid time periods are handled
	   elsewhere */
	if( ( isCertificate || certInfoPtr->type == CRYPT_CERTTYPE_CRL || \
		  certInfoPtr->type == CRYPT_CERTTYPE_OCSP_RESPONSE ) && \
		certInfoPtr->startTime <= MIN_TIME_VALUE )
		{
		/* If the time is screwed up we can't provide a signed indication
		   of the time */
		if( currentTime <= MIN_TIME_VALUE )
			{
			setErrorInfo( certInfoPtr, CRYPT_CERTINFO_VALIDFROM,
						  CRYPT_ERRTYPE_ATTR_VALUE );
			return( CRYPT_ERROR_NOTINITED );
			}
		certInfoPtr->startTime = currentTime;
		}
	if( isCertificate && certInfoPtr->endTime <= MIN_TIME_VALUE )
		{
		int validity;

		status = krnlSendMessage( certInfoPtr->ownerHandle, 
								  IMESSAGE_GETATTRIBUTE, &validity, 
								  CRYPT_OPTION_CERT_VALIDITY );
		if( cryptStatusError( status ) )
			return( status );
		certInfoPtr->endTime = certInfoPtr->startTime + \
							   ( ( time_t ) validity * 86400L );
		}
	if( certInfoPtr->type == CRYPT_CERTTYPE_CRL || \
		certInfoPtr->type == CRYPT_CERTTYPE_OCSP_RESPONSE )
		{
		if( certInfoPtr->endTime <= MIN_TIME_VALUE )
			{
			if( certInfoPtr->type == CRYPT_CERTTYPE_OCSP_RESPONSE )
				{
				/* OCSP responses come directly from the certificate store
				   and represent an atomic (and ephemeral) snapshot of the
				   store state.  Because of this the next-update time is
				   effectively immediately since the next snapshot could
				   provide a different response */
				certInfoPtr->endTime = currentTime;
				}
			else
				{
				int updateInterval;

				status = krnlSendMessage( certInfoPtr->ownerHandle,
										  IMESSAGE_GETATTRIBUTE, &updateInterval,
										  CRYPT_OPTION_CERT_UPDATEINTERVAL );
				if( cryptStatusError( status ) )
					return( status );
				certInfoPtr->endTime = certInfoPtr->startTime + \
									   ( ( time_t ) updateInterval * 86400L );
				}
			}
		if( certInfoPtr->cCertRev->revocationTime <= MIN_TIME_VALUE )
			certInfoPtr->cCertRev->revocationTime = currentTime;
		}

	return( CRYPT_OK );
	}

/****************************************************************************
*																			*
*								Signing Functions							*
*																			*
****************************************************************************/

/* Pseudo-sign certificate information by writing the outer wrapper and
   moving the object into the initialised state */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2, 4 ) ) \
static int pseudoSignCertificate( INOUT CERT_INFO *certInfoPtr,
								  INOUT_BUFFER_FIXED( signedCertObjectMaxLength ) \
										void *signedCertObject,
								  IN_LENGTH_SHORT_MIN( 16 ) \
										const int signedCertObjectMaxLength,
								  IN_BUFFER( certObjectLength ) \
										const void *certObject,
								  IN_LENGTH_SHORT_MIN( 16 ) \
										const int certObjectLength )
	{
	STREAM stream;
	int signedCertObjectLength, status;

	assert( isWritePtr( certInfoPtr, sizeof( CERT_INFO ) ) );
	assert( isWritePtr( signedCertObject, signedCertObjectMaxLength ) );
	assert( isReadPtr( certObject, certObjectLength ) );

	REQUIRES( signedCertObjectMaxLength >= 16 && \
			  signedCertObjectMaxLength < MAX_INTLENGTH_SHORT );
	REQUIRES( certObjectLength >= 16 && \
			  certObjectLength <= signedCertObjectMaxLength && \
			  certObjectLength < MAX_INTLENGTH_SHORT );

	switch( certInfoPtr->type )
		{
		case CRYPT_CERTTYPE_OCSP_REQUEST:
		case CRYPT_CERTTYPE_PKIUSER:
			/* It's an unsigned OCSP request or PKI user info, write the
			   outer wrapper */
			signedCertObjectLength = sizeofObject( certObjectLength );
			ENSURES( signedCertObjectLength <= signedCertObjectMaxLength );
			sMemOpen( &stream, signedCertObject, signedCertObjectLength );
			writeSequence( &stream, certObjectLength );
			status = swrite( &stream, certObject, certObjectLength );
			ENSURES( cryptStatusOK( status ) );
			sMemDisconnect( &stream );
			if( certInfoPtr->type == CRYPT_CERTTYPE_PKIUSER )
				{
				status = recoverCertData( certInfoPtr, 
										  CRYPT_CERTTYPE_PKIUSER, 
										  signedCertObject,
										  signedCertObjectLength );
				if( cryptStatusError( status ) )
					return( status );
				}
			break;

		case CRYPT_CERTTYPE_RTCS_REQUEST:
		case CRYPT_CERTTYPE_RTCS_RESPONSE:
		case CRYPT_CERTTYPE_OCSP_RESPONSE:
			/* It's an RTCS request/response or OCSP response, it's already
			   in the form required */
			signedCertObjectLength = certObjectLength;
			ENSURES( signedCertObjectLength <= signedCertObjectMaxLength );
			memcpy( signedCertObject, certObject, certObjectLength );
			break;

		case CRYPT_CERTTYPE_REQUEST_CERT:
			{
			const int dataSize = certObjectLength + \
								 sizeofObject( sizeofShortInteger( 0 ) );

			ENSURES( certInfoPtr->type == CRYPT_CERTTYPE_REQUEST_CERT );

			/* It's an encryption-only key, wrap up the certificate data 
			   with an indication that private key POP will be performed via 
			   out-of-band means and remember where the encoded data 
			   starts */
			signedCertObjectLength = sizeofObject( dataSize );
			ENSURES( signedCertObjectLength <= signedCertObjectMaxLength );
			sMemOpen( &stream, signedCertObject, signedCertObjectLength );
			writeSequence( &stream, dataSize );
			swrite( &stream, certObject, certObjectLength );
			writeConstructed( &stream, sizeofShortInteger( 0 ), 2 );
			status = writeShortInteger( &stream, 0, 1 );
			ENSURES( cryptStatusOK( status ) );
			sMemDisconnect( &stream );
			status = recoverCertData( certInfoPtr, 
									  CRYPT_CERTTYPE_REQUEST_CERT,
									  signedCertObject,
									  signedCertObjectLength );
			if( cryptStatusError( status ) )
				return( status );

			/* The pseudo-signature has been checked (since we just created
			   it), this also avoids nasty semantic problems with not-really-
			   signed CRMF requests with encryption-only keys */
			certInfoPtr->flags |= CERT_FLAG_SELFSIGNED;
			break;
			}

		case CRYPT_CERTTYPE_REQUEST_REVOCATION:
			/* Revocation requests can't be signed so the (pseudo-)signed
			   data is just the object data */
			signedCertObjectLength = certObjectLength;
			ENSURES( signedCertObjectLength <= signedCertObjectMaxLength );
			memcpy( signedCertObject, certObject, certObjectLength );

			/* Since revocation requests can't be signed we mark them as
			   pseudo-signed to avoid any problems that might arise from
			   this */
			certInfoPtr->flags |= CERT_FLAG_SELFSIGNED;
			break;

		default:
			retIntError();
		}
	certInfoPtr->certificate = signedCertObject;
	certInfoPtr->certificateSize = signedCertObjectLength;

	/* The object is now (pseudo-)signed and initialised */
	certInfoPtr->flags |= CERT_FLAG_SIGCHECKED;
	if( certInfoPtr->type == CRYPT_CERTTYPE_REQUEST_CERT )
		{
		/* If it's a CRMF request with POP done via out-of-band means we
		   got here via a standard signing action (except that the key was
		   an encryption-only key), don't change the object state since the
		   kernel will do this as the post-signing step */
		return( CRYPT_OK );
		}
	return( krnlSendMessage( certInfoPtr->objectHandle,
							 IMESSAGE_SETATTRIBUTE, MESSAGE_VALUE_UNUSED,
							 CRYPT_IATTRIBUTE_INITIALISED ) );
	}

/* Sign a certificate object */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int signCert( INOUT CERT_INFO *certInfoPtr, 
			  IN_HANDLE_OPT const CRYPT_CONTEXT iSignContext )
	{
	CRYPT_ALGO_TYPE hashAlgo;
	CERT_INFO *issuerCertInfoPtr = NULL;
	STREAM stream;
	WRITECERT_FUNCTION writeCertFunction;
	const CRYPT_SIGNATURELEVEL_TYPE signatureLevel = \
				( certInfoPtr->type == CRYPT_CERTTYPE_OCSP_REQUEST ) ? \
					certInfoPtr->cCertRev->signatureLevel : \
					CRYPT_SIGNATURELEVEL_NONE;
	const BOOLEAN isCertificate = \
			( certInfoPtr->type == CRYPT_CERTTYPE_CERTIFICATE || \
			  certInfoPtr->type == CRYPT_CERTTYPE_ATTRIBUTE_CERT || \
			  certInfoPtr->type == CRYPT_CERTTYPE_CERTCHAIN ) ? TRUE : FALSE;
	BOOLEAN issuerCertAcquired = FALSE, nonSigningKey = FALSE;
	BYTE certObjectBuffer[ 1024 + 8 ], *certObjectPtr = certObjectBuffer;
	void *signedCertObject;
	int certObjectLength, signedCertObjectLength, signedCertAllocSize;
	int extraDataLength = 0, complianceLevel, status;

	assert( isWritePtr( certInfoPtr, sizeof( CERT_INFO ) ) );

	REQUIRES( iSignContext == CRYPT_UNUSED || \
			  isHandleRangeValid( iSignContext ) );
	REQUIRES( certInfoPtr->certificate == NULL );

	/* Determine how much checking we need to perform */
	status = krnlSendMessage( certInfoPtr->ownerHandle,
							  IMESSAGE_GETATTRIBUTE, &complianceLevel,
							  CRYPT_OPTION_CERT_COMPLIANCELEVEL );
	if( cryptStatusError( status ) )
		return( status );

	/* If it's a non-signing key we have to create a special format of 
	   certificate request that isn't signed but contains an indication that 
	   the private key POP will be performed by out-of-band means.  We also 
	   have to check for the iSignContext being absent to handle OCSP 
	   requests for which the signature is optional so there may be no 
	   signing key present */
	if( iSignContext == CRYPT_UNUSED || \
		cryptStatusError( krnlSendMessage( iSignContext, IMESSAGE_CHECK,
						  NULL, MESSAGE_CHECK_PKC_SIGN ) ) )
		nonSigningKey = TRUE;

	/* Obtain the issuer certificate from the private key if necessary */
	if( isCertificate || certInfoPtr->type == CRYPT_CERTTYPE_CRL || \
		( ( certInfoPtr->type == CRYPT_CERTTYPE_OCSP_REQUEST || \
			certInfoPtr->type == CRYPT_CERTTYPE_OCSP_RESPONSE ) && \
		  !nonSigningKey ) )
		{
		/* If it's a self-signed certificate then the issuer is also the 
		   subject */
		if( certInfoPtr->flags & CERT_FLAG_SELFSIGNED )
			issuerCertInfoPtr = certInfoPtr;
		else
			{
			CRYPT_CERTIFICATE dataOnlyCert;

			/* Get the data-only certificate from the context */
			status = krnlSendMessage( iSignContext, IMESSAGE_GETDEPENDENT,
									  &dataOnlyCert, OBJECT_TYPE_CERTIFICATE );
			if( cryptStatusError( status ) )
				return( ( status == CRYPT_ARGERROR_OBJECT ) ? \
						CRYPT_ARGERROR_VALUE : status );
			status = krnlAcquireObject( dataOnlyCert, OBJECT_TYPE_CERTIFICATE,
										( void ** ) &issuerCertInfoPtr,
										CRYPT_ARGERROR_VALUE );
			if( cryptStatusError( status ) )
				return( status );
			issuerCertAcquired = TRUE;
			}

		/* Check the signing key */
		status = checkSigningKey( certInfoPtr, issuerCertInfoPtr, 
								  isCertificate, complianceLevel );
		if( cryptStatusError( status ) )
			{
			if( issuerCertAcquired )
				krnlReleaseObject( issuerCertInfoPtr->objectHandle );
			return( status );
			}
		}

	/* If we need to include extra data in the signature make sure that it's
	   available and determine how big it'll be.  If there's no issuer 
	   certificate available and we've been asked for extra signature data 
	   we fall back to providing just a raw signature rather than bailing 
	   out completely */
	if( signatureLevel > CRYPT_SIGNATURELEVEL_NONE && \
		issuerCertInfoPtr != NULL )
		{
		ENSURES( certInfoPtr->type == CRYPT_CERTTYPE_REQUEST_CERT || \
				 certInfoPtr->type == CRYPT_CERTTYPE_OCSP_REQUEST );

		if( signatureLevel == CRYPT_SIGNATURELEVEL_SIGNERCERT )
			{
			status = exportCert( NULL, 0, &extraDataLength,
								 CRYPT_CERTFORMAT_CERTIFICATE,
								 issuerCertInfoPtr );
			}
		else
			{
			MESSAGE_DATA msgData;

			ENSURES( signatureLevel == CRYPT_SIGNATURELEVEL_ALL );

			setMessageData( &msgData, NULL, 0 );
			status = krnlSendMessage( issuerCertInfoPtr->objectHandle,
									  IMESSAGE_CRT_EXPORT, &msgData,
									  CRYPT_ICERTFORMAT_CERTSEQUENCE );
			extraDataLength = msgData.length;
			}
		if( cryptStatusError( status ) )
			{
			if( issuerCertAcquired )
				krnlReleaseObject( issuerCertInfoPtr->objectHandle );
			return( status );
			}
		}

	/* If it's a certificate chain copy over the signing certificate(s) */
	if( certInfoPtr->type == CRYPT_CERTTYPE_CERTCHAIN )
		{
		status = copySigningCertChain( certInfoPtr, iSignContext );
		if( cryptStatusError( status ) )
			{
			if( issuerCertAcquired )
				krnlReleaseObject( issuerCertInfoPtr->objectHandle );
			return( status );
			}
		}

	/* Set up any required timestamps */
	status = setCertTimeinfo( certInfoPtr, iSignContext, isCertificate );
	if( cryptStatusError( status ) )
		{
		if( issuerCertAcquired )
			krnlReleaseObject( issuerCertInfoPtr->objectHandle );
		return( status );
		}

	/* If it's a certificate, set up the certificate serial number */
	if( isCertificate )
		{
		status = setSerialNumber( certInfoPtr, NULL, 0 );
		if( cryptStatusError( status ) )
			{
			if( issuerCertAcquired )
				krnlReleaseObject( issuerCertInfoPtr->objectHandle );
			return( status );
			}
		}

	/* Determine the hash algorithm to use and if it's a certificate or 
	   CRL remember it for when we write the certificate (the value is 
	   embedded in the certificate to prevent an obscure attack on unpadded 
	   RSA signature algorithms) */
	krnlSendMessage( certInfoPtr->ownerHandle, IMESSAGE_GETATTRIBUTE,
					 &hashAlgo, CRYPT_OPTION_ENCR_HASH );
	if( certInfoPtr->type == CRYPT_CERTTYPE_CERTIFICATE || \
		certInfoPtr->type == CRYPT_CERTTYPE_CERTCHAIN || \
		certInfoPtr->type == CRYPT_CERTTYPE_ATTRIBUTE_CERT )
		certInfoPtr->cCertCert->hashAlgo = hashAlgo;
	else
		{
		if( certInfoPtr->type == CRYPT_CERTTYPE_CRL )
			certInfoPtr->cCertRev->hashAlgo = hashAlgo;
		}

	/* Select the function to use to write the certificate object to be
	   signed */
	writeCertFunction = getCertWriteFunction( certInfoPtr->type );
	if( writeCertFunction == NULL )
		{
		if( issuerCertAcquired )
			krnlReleaseObject( issuerCertInfoPtr->objectHandle );
		retIntError();
		}

	/* Determine how big the encoded certificate information will be,
	   allocate memory for it and the full signed certificate, and write the
	   encoded certificate information */
	sMemNullOpen( &stream );
	status = writeCertFunction( &stream, certInfoPtr, issuerCertInfoPtr, 
								iSignContext );
	certObjectLength = stell( &stream );
	sMemClose( &stream );
	if( cryptStatusError( status ) )
		{
		if( issuerCertAcquired )
			krnlReleaseObject( issuerCertInfoPtr->objectHandle );
		return( status );
		}
	signedCertAllocSize = certObjectLength + 1024 + extraDataLength;
	if( ( certObjectLength > 1024 && \
		  ( certObjectPtr = clDynAlloc( "signCert", \
										certObjectLength ) ) == NULL ) || \
		( signedCertObject = clAlloc( "signCert", \
									  signedCertAllocSize ) ) == NULL )
		{
		if( certObjectPtr != NULL && certObjectPtr != certObjectBuffer )
			clFree( "signCert", certObjectPtr );
		if( issuerCertAcquired )
			krnlReleaseObject( issuerCertInfoPtr->objectHandle );
		return( CRYPT_ERROR_MEMORY );
		}
	sMemOpen( &stream, certObjectPtr, certObjectLength );
	status = writeCertFunction( &stream, certInfoPtr, issuerCertInfoPtr, 
								iSignContext );
	ENSURES( cryptStatusError( status ) || \
			 certObjectLength == stell( &stream ) );
	sMemDisconnect( &stream );
	if( issuerCertAcquired )
		krnlReleaseObject( issuerCertInfoPtr->objectHandle );
	if( cryptStatusError( status ) )
		{
		zeroise( certObjectPtr, certObjectLength );
		if( certObjectPtr != certObjectBuffer )
			clFree( "signCert", certObjectPtr );
		clFree( "signCert", signedCertObject );
		return( status );
		}
	ENSURES( !cryptStatusError( checkObjectEncoding( certObjectPtr, \
													 certObjectLength ) ) );

	/* If there's no signing key present pseudo-sign the certificate
	   information by writing the outer wrapper and moving the object into
	   the initialised state */
	if( nonSigningKey )
		{
		status = pseudoSignCertificate( certInfoPtr, signedCertObject,
										signedCertAllocSize, certObjectPtr, 
										certObjectLength );
		zeroise( certObjectPtr, certObjectLength );
		if( certObjectPtr != certObjectBuffer )
			clFree( "signCert", certObjectPtr );
		ENSURES( !cryptStatusError( \
					checkObjectEncoding( certInfoPtr->certificate, \
										 certInfoPtr->certificateSize ) ) );
		return( status );
		}

	/* Sign the certificate information.  CRMF and OCSP use a b0rken
	   signature format (the authors couldn't quite manage a cut & paste of
	   two lines of text) so if it's one of these we have to use nonstandard
	   formatting */
	if( certInfoPtr->type == CRYPT_CERTTYPE_REQUEST_CERT || \
		certInfoPtr->type == CRYPT_CERTTYPE_OCSP_REQUEST )
		{
		X509SIG_FORMATINFO formatInfo;

		if( certInfoPtr->type == CRYPT_CERTTYPE_REQUEST_CERT )
			{
			/* [1] SEQUENCE */
			setX509FormatInfo( &formatInfo, 1, FALSE );
			}
		else
			{
			/* [0] EXPLICIT SEQUENCE */
			setX509FormatInfo( &formatInfo, 0, TRUE );
			}
		if( signatureLevel == CRYPT_SIGNATURELEVEL_SIGNERCERT )
			{
			formatInfo.extraLength = ( int ) \
						sizeofObject( sizeofObject( extraDataLength ) );
			}
		else
			{
			if( signatureLevel == CRYPT_SIGNATURELEVEL_ALL )
				{
				formatInfo.extraLength = ( int ) \
						sizeofObject( extraDataLength );
				}
			}
		status = createX509signature( signedCertObject,
							signedCertAllocSize, &signedCertObjectLength, 
							certObjectPtr, certObjectLength, iSignContext,
							hashAlgo, &formatInfo );
		}
	else
		{
		/* It's a standard signature */
		status = createX509signature( signedCertObject,
							signedCertAllocSize, &signedCertObjectLength, 
							certObjectPtr, certObjectLength, iSignContext,
							hashAlgo, NULL );
		}
	zeroise( certObjectPtr, certObjectLength );
	if( certObjectPtr != certObjectBuffer )
		clFree( "signCert", certObjectPtr );
	if( cryptStatusError( status ) )
		{
		return( ( status == CRYPT_ARGERROR_NUM1 ) ? \
				CRYPT_ARGERROR_VALUE : status );
		}
	certInfoPtr->certificate = signedCertObject;
	certInfoPtr->certificateSize = signedCertObjectLength;

	/* If we need to include extra data with the signature attach it to the 
	   end of the signature */
	if( extraDataLength > 0 )
		{
		const int extraDataType = \
			( signatureLevel == CRYPT_SIGNATURELEVEL_SIGNERCERT ) ? \
			CRYPT_CERTFORMAT_CERTIFICATE : CRYPT_ICERTFORMAT_CERTSEQUENCE;

		sMemOpen( &stream, ( BYTE * ) signedCertObject + signedCertObjectLength,
				  signedCertAllocSize - signedCertObjectLength );
		if( signatureLevel == CRYPT_SIGNATURELEVEL_SIGNERCERT )
			{
			writeConstructed( &stream, sizeofObject( extraDataLength ), 0 );
			writeSequence( &stream, extraDataLength );
			}
		else
			{
			ENSURES( signatureLevel == CRYPT_SIGNATURELEVEL_ALL );

			writeConstructed( &stream, extraDataLength, 0 );
			}
		status = exportCertToStream( &stream, issuerCertInfoPtr->objectHandle,
									 extraDataType );
		certInfoPtr->certificateSize = signedCertObjectLength + \
									   stell( &stream );
		sMemDisconnect( &stream );
		if( cryptStatusError( status ) )
			{
			zeroise( certInfoPtr->certificate, signedCertAllocSize );
			clFree( "signCert", certInfoPtr->certificate );
			certInfoPtr->certificate = NULL;
			certInfoPtr->certificateSize = 0;
			return( status );
			}
		}
	ENSURES( !cryptStatusError( \
				checkObjectEncoding( certInfoPtr->certificate, \
									 certInfoPtr->certificateSize ) ) );

	/* If it's a certification request it's now self-signed.  In addition 
	   the signature has been checked since we've just created it */
	if( certInfoPtr->type == CRYPT_CERTTYPE_CERTREQUEST || \
		certInfoPtr->type == CRYPT_CERTTYPE_REQUEST_CERT )
		certInfoPtr->flags |= CERT_FLAG_SELFSIGNED;
	certInfoPtr->flags |= CERT_FLAG_SIGCHECKED;

#if 0	/* 15/6/04 Only the root should be marked as self-signed, having
				   supposedly self-signed certificates inside the chain 
				   causes problems when trying to detect pathkludge 
				   certificates */
	/* If it's a certificate chain and the root is self-signed then the 
	   entire chain counts as self-signed */
	if( certInfoPtr->type == CRYPT_CERTTYPE_CERTCHAIN )
		{
		int selfSigned;

		status = krnlSendMessage( \
					certInfoPtr->cCertCert->chain[ certInfoPtr->cCertCert->chainEnd - 1 ],
					IMESSAGE_GETATTRIBUTE, &selfSigned,
					CRYPT_CERTINFO_SELFSIGNED );
		if( cryptStatusOK( status ) && selfSigned )
			certInfoPtr->flags |= CERT_FLAG_SELFSIGNED;
		}
#endif /* 0 */

	/* If it's not an object type with special-case post-signing
	   requirements we're done */
	if( certInfoPtr->type != CRYPT_CERTTYPE_CERTIFICATE && \
		certInfoPtr->type != CRYPT_CERTTYPE_CERTCHAIN && \
		certInfoPtr->type != CRYPT_CERTTYPE_REQUEST_CERT )
		return( CRYPT_OK );

	/* Recover information such as pointers to encoded certificate data */
	return( recoverCertData( certInfoPtr, certInfoPtr->type, 
							 signedCertObject, signedCertObjectLength ) );
	}
