/****************************************************************************
*																			*
*						 cryptlib HTTP Mapping Routines						*
*						Copyright Peter Gutmann 1998-2004					*
*																			*
****************************************************************************/

#include <stdlib.h>
#include <string.h>
#if defined( INC_ALL )
  #include "crypt.h"
  #include "keyset.h"
  #include "asn1.h"
#elif defined( INC_CHILD )
  #include "../crypt.h"
  #include "keyset.h"
  #include "../misc/asn1.h"
#else
  #include "crypt.h"
  #include "keyset/keyset.h"
  #include "misc/asn1.h"
#endif /* Compiler-specific includes */

#ifdef USE_HTTP

/* The default size of the HTTP read buffer.  This is adjusted dynamically if
   the data being read won't fit (e.g. large CRLs).  The default size is 
   fine for certs */

#define HTTP_BUFFER_SIZE	4096

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

/* Set up key information for a query */

static char *getKeyName( const CRYPT_KEYID_TYPE keyIDtype )
	{
	switch( keyIDtype )
		{
		case CRYPT_KEYID_NAME:
			return( "name" );

		case CRYPT_KEYID_URI:
			return( "uri" );

		case CRYPT_IKEYID_KEYID:
			return( "sKIDHash" );

		case CRYPT_IKEYID_ISSUERID:
			return( "iAndSHash" );

		case CRYPT_IKEYID_CERTID:
			return( "certHash" );
		}

	assert( NOTREACHED );
	return( NULL );			/* Get rid of compiler warning */
	}

/* Callback function to adjust the I/O buffer size if the initial buffer
   isn't large enough */

static int bufferAdjustCallback( void *callbackParams, void **bufPtr,
								 const int bufSize )
	{
	KEYSET_INFO *keysetInfo = ( KEYSET_INFO * ) callbackParams;
	void *newBuffer;

	assert( keysetInfo->type == KEYSET_HTTP );
	assert( keysetInfo->subType == KEYSET_SUBTYPE_NONE );
	assert( keysetInfo->keyData != NULL );
	assert( keysetInfo->keyDataSize < bufSize );

	/* Allocate a new buffer and replace the existing one */
	if( ( newBuffer = clAlloc( "bufferAdjustCallback", bufSize ) ) == NULL )
		return( CRYPT_ERROR_MEMORY );
	zeroise( keysetInfo->keyData, keysetInfo->keyDataSize );
	clFree( "bufferAdjustCallback", keysetInfo->keyData );
	*bufPtr = newBuffer;
	keysetInfo->keyData = newBuffer;
	keysetInfo->keyDataSize = bufSize;
	return( CRYPT_OK );
	}

/****************************************************************************
*																			*
*						 		Keyset Access Routines						*
*																			*
****************************************************************************/

/* Retrieve a cert/CRL from an HTTP server, either as a flat URL if the key
   name is "[none]" or as a cert store */

static int getItemFunction( KEYSET_INFO *keysetInfo,
							CRYPT_HANDLE *iCryptHandle,
							const KEYMGMT_ITEM_TYPE itemType,
							const CRYPT_KEYID_TYPE keyIDtype,
							const void *keyID,  const int keyIDlength,
							void *auxInfo, int *auxInfoLength,
							const int flags )
	{
	HTTP_INFO *httpInfo = keysetInfo->keysetHTTP;
	MESSAGE_CREATEOBJECT_INFO createInfo;
	int length, status;

	assert( itemType == KEYMGMT_ITEM_PUBLICKEY );
	assert( keyIDtype == CRYPT_KEYID_NAME || keyIDtype == CRYPT_KEYID_URI );
	assert( auxInfo == NULL ); assert( *auxInfoLength == 0 );

	/* Set the keyID as the query portion of the URL if necessary */
	if( keyIDlength != 6 || strCompare( keyID, "[none]", 6 ) )
		{
		const char *keyName = getKeyName( keyIDtype );
		char queryBuffer[ 1024 ], *queryBufPtr = queryBuffer;
		const int keyNameLen = strlen( keyName );

		if( keyIDlength > 1024 - 64 && \
		    ( queryBufPtr = clDynAlloc( "getItemFunction", \
										keyIDlength + 64 ) ) == NULL )
			return( CRYPT_ERROR_MEMORY );
		strcpy( queryBufPtr, keyName );
		strcat( queryBufPtr + keyNameLen, "=" );
		memcpy( queryBufPtr + keyNameLen + 1, keyID, keyIDlength );
		sioctl( &httpInfo->stream, STREAM_IOCTL_QUERY, queryBufPtr, 
				keyNameLen + 1 + keyIDlength );
		if( queryBufPtr != queryBuffer )
			clFree( "getItemFunction", queryBufPtr );
		}

	/* If we haven't allocated a buffer for the data yet, do so now */
	if( keysetInfo->keyData == NULL )
		{
		/* Allocate the initial I/O buffer */
		if( ( keysetInfo->keyData = clAlloc( "getItemFunction", \
											 HTTP_BUFFER_SIZE ) ) == NULL )
			return( CRYPT_ERROR_MEMORY );
		keysetInfo->keyDataSize = HTTP_BUFFER_SIZE;

		/* Since we don't know the size of the data being read in advance,
		   we have to set up a callback to adjust the buffer size if
		   necessary */
		sioctl( &httpInfo->stream, STREAM_IOCTL_CALLBACKFUNCTION,
				( void * ) bufferAdjustCallback, 0 );
		sioctl( &httpInfo->stream, STREAM_IOCTL_CALLBACKPARAMS,
				keysetInfo, 0 );
		}
	httpInfo->bufPos = 0;

	/* Send the request to the server */
	status = sread( &httpInfo->stream, keysetInfo->keyData,
					keysetInfo->keyDataSize );
	if( cryptStatusError( status ) )
		{
		sNetGetErrorInfo( &httpInfo->stream, httpInfo->errorMessage, 
						  &httpInfo->errorCode );

		/* If it's a not-found error, this is non-fatal condition (it just
		   means that the requested cert wasn't found, but doesn't prevent 
		   us from submitting further requests), so we clear the stream 
		   status to allow further queries */
		if( status == CRYPT_ERROR_NOTFOUND )
			sClearError( &httpInfo->stream );
		return( status );
		}

	/* Find out how much data we got and perform a general check that
	   everything is OK.  We rely on this rather than the read byte count
	   since checking the ASN.1, which is the data that will actually be
	   processed, avoids any vagaries of server implementation oddities,
	   which may send extra null bytes or CRLFs or do who knows what else */
	length = getLongObjectLength( keysetInfo->keyData, 
								  keysetInfo->keyDataSize );
	if( cryptStatusError( length ) )
		return( length );

	/* Create a certificate object from the returned data */
	setMessageCreateObjectIndirectInfo( &createInfo, keysetInfo->keyData,
										length, CRYPT_CERTTYPE_NONE );
	status = krnlSendMessage( SYSTEM_OBJECT_HANDLE,
							  IMESSAGE_DEV_CREATEOBJECT_INDIRECT,
							  &createInfo, OBJECT_TYPE_CERTIFICATE );
	if( cryptStatusOK( status ) )
		*iCryptHandle = createInfo.cryptHandle;
	return( status );
	}

/* Prepare to open a connection to an HTTP server */

static int initFunction( KEYSET_INFO *keysetInfo, const char *name,
						 const CRYPT_KEYOPT_TYPE options )
	{
	HTTP_INFO *httpInfo = keysetInfo->keysetHTTP;
	NET_CONNECT_INFO connectInfo;
	int status;

	/* Set up the HTTP connection */
	initNetConnectInfo( &connectInfo, keysetInfo->ownerHandle, CRYPT_ERROR, 
						CRYPT_ERROR, NET_OPTION_HOSTNAME );
	connectInfo.name = name;
	connectInfo.nameLength = strlen( name );
	connectInfo.port = 80;
	status = sNetConnect( &httpInfo->stream, STREAM_PROTOCOL_HTTP, 
						  &connectInfo, httpInfo->errorMessage, 
						  &httpInfo->errorCode );
	return( status );
	}

/* Close a previously-opened HTTP connection */

static void shutdownFunction( KEYSET_INFO *keysetInfo )
	{
	HTTP_INFO *httpInfo = keysetInfo->keysetHTTP;

	sNetDisconnect( &httpInfo->stream );
	if( keysetInfo->keyData != NULL )
		{
		zeroise( keysetInfo->keyData, keysetInfo->keyDataSize );
		clFree( "getItemFunction", keysetInfo->keyData );
		keysetInfo->keyData = NULL;
		}
	}

int setAccessMethodHTTP( KEYSET_INFO *keysetInfo )
	{
	/* Set the access method pointers */
	keysetInfo->initFunction = initFunction;
	keysetInfo->shutdownFunction = shutdownFunction;
	keysetInfo->getItemFunction = getItemFunction;

	return( CRYPT_OK );
	}
#endif /* USE_HTTP */
