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

#if defined( INC_ALL )
  #include "crypt.h"
  #include "keyset.h"
  #include "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 certificates */

#define HTTP_BUFFER_SIZE	4096

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

/* Set up key information for a query */

CHECK_RETVAL_PTR \
static const char *getKeyName( IN_KEYID const CRYPT_KEYID_TYPE keyIDtype )
	{
	REQUIRES_N( keyIDtype > CRYPT_KEYID_NONE && \
				keyIDtype < CRYPT_KEYID_LAST );

	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" );
		}

	retIntError_Null();
	}

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

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

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2, 5 ) ) \
static int getItemFunction( INOUT KEYSET_INFO *keysetInfoPtr,
							OUT_HANDLE_OPT CRYPT_HANDLE *iCryptHandle,
							IN_ENUM( KEYMGMT_ITEM ) \
								const KEYMGMT_ITEM_TYPE itemType,
							IN_KEYID const CRYPT_KEYID_TYPE keyIDtype,
							IN_BUFFER( keyIDlength ) const void *keyID, 
							IN_LENGTH_KEYID const int keyIDlength,
							STDC_UNUSED void *auxInfo, 
							STDC_UNUSED int *auxInfoLength,
							IN_FLAGS_Z( KEYMGMT ) const int flags )
	{
	HTTP_INFO *httpInfo = keysetInfoPtr->keysetHTTP;
	HTTP_DATA_INFO httpDataInfo;
	HTTP_URI_INFO httpReqInfo;
	MESSAGE_CREATEOBJECT_INFO createInfo;
	BOOLEAN hasExplicitKeyID = FALSE;
	long length;
	int status;

	assert( isWritePtr( keysetInfoPtr, sizeof( KEYSET_INFO ) ) );
	assert( isWritePtr( iCryptHandle, sizeof( CRYPT_HANDLE ) ) );
	assert( isReadPtr( keyID, keyIDlength ) );

	REQUIRES( keysetInfoPtr->type == KEYSET_HTTP );
	REQUIRES( itemType == KEYMGMT_ITEM_PUBLICKEY );
	REQUIRES( keyIDtype == CRYPT_KEYID_NAME || keyIDtype == CRYPT_KEYID_URI );
	REQUIRES( keyIDlength >= MIN_NAME_LENGTH && \
			  keyIDlength < MAX_ATTRIBUTE_SIZE );
	REQUIRES( auxInfo == NULL ); assert( *auxInfoLength == 0 );
	REQUIRES( flags >= KEYMGMT_FLAG_NONE && flags < KEYMGMT_FLAG_MAX );

	/* Set the keyID as the query portion of the URL if necessary */
	if( keyIDlength != 6 || strCompare( keyID, "[none]", 6 ) )
		{
		/* Make sure that the keyID is of an appropriate size */
		if( keyIDlength > CRYPT_MAX_TEXTSIZE )
			return( CRYPT_ARGERROR_STR1 );

		hasExplicitKeyID = TRUE;
		}

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

	/* Set up the HTTP request information */
	if( hasExplicitKeyID )
		{
		const char *keyName = getKeyName( keyIDtype );
		int keyNameLen;

		ENSURES( keyName != NULL );
		keyNameLen = strlen( keyName );
		initHttpDataInfoEx( &httpDataInfo, keysetInfoPtr->keyData,
							keysetInfoPtr->keyDataSize, &httpReqInfo );
		memcpy( httpReqInfo.attribute, keyName, keyNameLen );
		httpReqInfo.attributeLen = keyNameLen;
		memcpy( httpReqInfo.value, keyID, keyIDlength );
		httpReqInfo.valueLen = keyIDlength;
		}
	else
		{
		initHttpDataInfo( &httpDataInfo, keysetInfoPtr->keyData,
						  keysetInfoPtr->keyDataSize );
		}

	/* Some errors like a CRYPT_ERROR_NOTFOUND (= HTTP 404) aren't fatal in
	   the same way as (say) a CRYPT_ERROR_BADDATA because while the latter
	   means that the stream has been corrupted and we can't continue the
	   former merely means that the requested certificate wasn't found but
	   we can still submit further requests.  To let the lower-level code
	   know this we set the HTTP_FLAG_SOFTERRORS flag */
	httpDataInfo.softErrors = TRUE;

	/* Send the request to the server.  Since we don't know the size of the 
	   data being read in advance we have to tell the stream I/O code to 
	   adjust the read buffer size if necessary */
	httpDataInfo.bufferResize = TRUE;
	status = sread( &httpInfo->stream, &httpDataInfo,
					sizeof( HTTP_DATA_INFO ) );
	if( httpDataInfo.bufferResize )
		{
		/* The read buffer may have been adjusted even though an error code
		   was returned from a later operation so we process the resized 
		   flag before we check for an error status */
		keysetInfoPtr->keyData = httpDataInfo.buffer;
		keysetInfoPtr->keyDataSize = httpDataInfo.bufSize;
		}
	if( cryptStatusError( status ) )
		{
		sNetGetErrorInfo( &httpInfo->stream, &keysetInfoPtr->errorInfo );

		/* If it's a not-found error this is non-fatal condition (it just
		   means that the requested certificate 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 */
	status = getLongObjectLength( keysetInfoPtr->keyData, 
								  httpDataInfo.bytesAvail, &length );
	if( cryptStatusError( status ) )
		return( status );

	/* Create a certificate object from the returned data */
	setMessageCreateObjectIndirectInfo( &createInfo, keysetInfoPtr->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 */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
static int initFunction( INOUT KEYSET_INFO *keysetInfoPtr, 
						 IN_BUFFER( nameLength ) const char *name, 
						 IN_LENGTH_SHORT_Z const int nameLength,
						 IN_ENUM( CRYPT_KEYOPT ) const CRYPT_KEYOPT_TYPE options )
	{
	HTTP_INFO *httpInfo = keysetInfoPtr->keysetHTTP;
	NET_CONNECT_INFO connectInfo;
	int status;

	assert( isWritePtr( keysetInfoPtr, sizeof( KEYSET_INFO ) ) );
	assert( isReadPtr( name, nameLength ) );

	REQUIRES( keysetInfoPtr->type == KEYSET_HTTP );
	REQUIRES( nameLength >= MIN_DNS_SIZE && nameLength < MAX_URL_SIZE );
	REQUIRES( options >= CRYPT_KEYOPT_NONE && options < CRYPT_KEYOPT_LAST );

	/* Set up the HTTP connection */
	initNetConnectInfo( &connectInfo, keysetInfoPtr->ownerHandle, CRYPT_ERROR, 
						CRYPT_ERROR, NET_OPTION_HOSTNAME );
	connectInfo.name = name;
	connectInfo.nameLength = nameLength;
	connectInfo.port = 80;
	status = sNetConnect( &httpInfo->stream, STREAM_PROTOCOL_HTTP, 
						  &connectInfo, &keysetInfoPtr->errorInfo );
	if( cryptStatusError( status ) )
		return( status );

	/* Since this isn't a general-purpose HTTP stream (of the kind used for 
	   the HTTP-as-a-substrate PKI protocols) but is only being used for 
	   HTTP 'GET' operations, we restrict the usage to just this operation */
	sioctl( &httpInfo->stream, STREAM_IOCTL_HTTPREQTYPES, NULL, 
			STREAM_HTTPREQTYPE_GET );
	return( CRYPT_OK );
	}

/* Close a previously-opened HTTP connection */

STDC_NONNULL_ARG( ( 1 ) ) \
static int shutdownFunction( INOUT KEYSET_INFO *keysetInfoPtr )
	{
	HTTP_INFO *httpInfo = keysetInfoPtr->keysetHTTP;

	assert( isWritePtr( keysetInfoPtr, sizeof( KEYSET_INFO ) ) );

	REQUIRES( keysetInfoPtr->type == KEYSET_HTTP );

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

	return( CRYPT_OK );
	}

CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int setAccessMethodHTTP( KEYSET_INFO *keysetInfoPtr )
	{
	assert( isWritePtr( keysetInfoPtr, sizeof( KEYSET_INFO ) ) );

	REQUIRES( keysetInfoPtr->type == KEYSET_HTTP );

	/* Set the access method pointers */
	keysetInfoPtr->initFunction = initFunction;
	keysetInfoPtr->shutdownFunction = shutdownFunction;
	keysetInfoPtr->getItemFunction = getItemFunction;

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