⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 lib_dbms.c

📁 提供了很多种加密算法和CA认证及相关服务如CMP、OCSP等的开发
💻 C
📖 第 1 页 / 共 5 页
字号:
/****************************************************************************
*																			*
*							cryptlib DBMS Interface							*
*						Copyright Peter Gutmann 1996-2002					*
*																			*
****************************************************************************/

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

/* Prototypes for CA management routines */

STATIC_FN int updateCertLog( KEYSET_INFO *keysetInfo, const int action,
							 const char *certID, const char *reqCertID,
							 const char *subjCertID, const void *data,
							 const int dataLength,
							 const DBMS_UPDATE_TYPE updateType );

/* When we add or read informatin to/from a table we sometimes have to
   specify type information which is an integer value, however SQL requires
   the things be set out as character strings so we use the following defines
   to provide the string form of the value for insertion into an SQL query.
   Unfortunately we can't check this at compile time so we have to check it
   via an assertion in the CA dispatch function */

#define TEXT_CERTTYPE_REQUEST_CERT			"5"
#define TEXT_CERTTYPE_REQUEST_REVOCATION	"6"
#define TEXT_CERTACTION_REQUEST_CERT		"6"
#define TEXT_CERTACTION_REQUEST_RENEWAL		"7"

/* The ways in which we can add a cert object to a table.  Normally we just
   add the cert as is, however if we're awaiting confirmation from a user
   before we can complete the cert issue process we perform a partial add
   which marks the cert as not quite ready for use yet.  A variant of this
   is when we're renewing a cert (ie reissuing it with the same key, which
   is really bad but required by some cert mismanagement protocols), in which
   case we have to process the update as a multi-stage process because we're
   replacing an existing cert with one which is exactly the same (as far as
   the uniqueness constraints on the cert store are concerned) */

typedef enum {
	CERTADD_NORMAL,				/* Standard one-step add */
	CERTADD_PARTIAL,			/* Partial add */
	CERTADD_PARTIAL_RENEWAL,	/* Partial add with cert replacement to follow */
	CERTADD_RENEWAL_COMPLETE	/* Completion of renewal */
	} CERTADD_TYPE;

/* The table structure for the various DBMS tables is (* = unique, + = cert
   store only):

	CertReq: type, C, SP, L, O, OU, CN, email, certID, certData
	Cert: C, SP, L, O, OU, CN, email, validTo, nameID, issuerID*, keyID*, certID*, certData
	CRL: expiryDate+, nameID+, issuerID*, certID, certData
	CertLog: action, date, certID*, reqCertID, subjCertID, certData

   The cert store contains a table for logging cert management operations (eg
   when issued, when revoked, etc etc).  The operations are tied together by
   the certID of each object, associated with this in the log are optional
   certIDs of the request which caused the action to be taken and the subject
   which was affected by the request.  This allows a complete history of each
   item to be built via the log.  The certLog has a UNIQUE INDEX on the
   certID which detects attempts to add duplicates, although this
   unfortunately requires the addition of dummy nonce certIDs to handle
   certain types of actions such as connects and disconnects.

   The handling for each type of CA management operation is:

	CERTACTION_REQUEST_CERT/CERTACTION_REQUEST_RENEWAL/
	CERTACTION_REQUEST_REVOCATION: Stores the incoming requests and generates
	a log entry.  Duplicate issue requests are detected by the certLog.certID
	uniqueness constraint.  Available: request with certID:

	  INSERT INTO certRequests VALUES ( <type>, <DN components>, <certID>, <request> );
	  INSERT INTO certLog VALUES
		(ACTION_REQUEST_CERT/RENEWAL/REVOCATION, $date, <certID>, NULL, NULL,
		  <request>);

	CERTACTION_ISSUE_CERT/CERTACTION_CERT_CREATION: Add the cert and remove
	the issue request.  Duplicate cert issuance is detected by the
	certLog.certID uniqueness constraint.  Available: request with
	req.certID, certificate with certID

	  INSERT INTO certificates VALUES (<DN components>, <IDs>, <cert>);
	  INSERT INTO certLog VALUES
		(ACTION_ISSUE_CERT/CERT_CREATION, $date, <certID>, <req.certID>, NULL,
		  <cert>);
	  DELETE FROM certRequests WHERE certID = <req.certID>;

	CERTACTION_ISSUE_CRL: Read each CRL entry with caCert.nameID and assemble
	the full CRL.  Requires an ongoing query:

	  SELECT FROM CRLs WHERE nameID = <caCert.nameID>

	CERTACTION_REVOKE_CERT: Add the CRL entry which causes the revocation,
	delete the cert and the request which caused the action.  Available:
	request with req.certID, certificate with cert.certID, CRL entry with
	certID

	  INSERT INTO CRLs VALUES (<IDs>, <crlData>);
	  INSERT INTO certLog VALUES
		(ACTION_REVOKE_CERT, $date, <certID>, <req.certID>, <cert.certID>, <crlData>);
	  DELETE FROM certRequests WHERE certID = <req.certID>;
	  DELETE FROM certificates WHERE certID = <cert.certID>;

	CERTACTION_EXPIRE_CERT/CERTACTION_RESTART_CLEANUP: Delete each expired
	entry or clean up leftover cert requests after a restart.  The logging
	for these is a bit tricky, ideally we'd want to "INSERT INTO certLog
	VALUES (ACTION_CERT_EXPIRE, $date, SELECT certID FROM certificates WHERE
	validTo <= $date)" or the cleanup equivalent, however this isn't
	possible both because it's not possible to mix static values and a
	select result in an INSERT and because the certID is already present
	from when the cert/request was originally added.  We can fix the former
	by making the static values part of the select result, ie "INSERT INTO
	certLog VALUES SELECT ACTION_CERT_EXPIRE, $date, certID FROM
	certificates WHERE validTo <= $date" but this still doesn't fix the
	problem with the duplicate IDs.  In fact there isn't really a certID
	present since it's an implicit action, but we can't make the certID
	column null since it's not possible to index nullable columns.  As a
	result the only way we can do it is to repetitively perform 'SELECT
	certID FROM certificates WHERE validTo <= $date' (or the equivalent
	cleanup select) and for each time it succeeds follow it with:

	  INSERT INTO certLog VALUES
		(ACTION_EXPIRE_CERT, $date, <nonce>, NULL, <certID>);
	  DELETE FROM certificates WHERE certID = <certID>

	or

	  INSERT INTO certLog VALUES
		(ACTION_RESTART_CLEANUP, $date, <nonce>, NULL, <certID>);
	  DELETE FROM certRequests WHERE certID = <certID>

	This has the unfortunate side-effect that the update isn't atomic, we
	could enforce this with "LOCK TABLE <name> IN EXCLUSIVE MODE", however
	the MS databases don't support this and either require the use of
	baroque mechanisms such as a "(TABLOCKX HOLDLOCK)" as a locking hint
	after the table name in the first statement after the transaction is
	begun or don't support this type of locking at all.  Because of this it
	isn't really possible to make the update atomic, in particular for the
	cleanup operation we rely on the caller to perform it at startup before
	anyone else accesses the cert store.  The fact that the update isn't
	quite atomic isn't really a major problem, at worst it'll result in
	either an expired cert being visible or a leftover request blocking a
	new request for a split second longer than they should */

/****************************************************************************
*																			*
*						Network Database Interface Routines					*
*																			*
****************************************************************************/

#ifdef NET_TCP

static void netEncodeError( BYTE *buffer, const int status )
	{
	putMessageType( buffer, COMMAND_RESULT, 0, 1, 0 );
	putMessageLength( buffer + COMMAND_WORDSIZE, COMMAND_WORDSIZE );
	putMessageWord( buffer + COMMAND_WORD1_OFFSET, status );
	}

void netProcessCommand( void *stateInfo, BYTE *buffer )
	{
	DBMS_STATE_INFO *dbmsInfo = ( DBMS_STATE_INFO * ) stateInfo;
	COMMAND_INFO cmd;
	int length, status;

	memset( &cmd, 0, sizeof( COMMAND_INFO ) );

	/* Get the messge information from the header */
	getMessageType( buffer, cmd.type, cmd.flags,
					cmd.noArgs, cmd.noStrArgs );
	length = getMessageLength( buffer + COMMAND_WORDSIZE );
	if( cmd.type == DBX_COMMAND_OPEN )
		{
		BYTE *bufPtr = buffer + COMMAND_FIXED_DATA_SIZE + COMMAND_WORDSIZE;
		int nameLen;

		/* Get the length of the server name and null-terminate it */
		nameLen = getMessageWord( bufPtr );
		bufPtr += COMMAND_WORDSIZE;
		bufPtr[ nameLen ] = '\0';

		/* Connect to the plugin */
		status = sNetConnect( &dbmsInfo->stream, STREAM_PROTOCOL_TCPIP,
							  bufPtr, 0, 30, dbmsInfo->errorMessage,
							  &dbmsInfo->errorCode );
		if( cryptStatusError( status ) )
			{
			netEncodeError( buffer, status );
			return;
			}
		}

	/* Send the command to the plugin and read back the response */
	status = swrite( &dbmsInfo->stream, buffer,
					 COMMAND_FIXED_DATA_SIZE + COMMAND_WORDSIZE + length );
	if( cryptStatusOK( status ) )
		{
		/* Read back the result header and perform a consistency check */
		status = sread( &dbmsInfo->stream, buffer, COMMAND_FIXED_DATA_SIZE );
		getMessageType( buffer, cmd.type, cmd.flags,
						cmd.noArgs, cmd.noStrArgs );
		length = getMessageLength( buffer + COMMAND_WORDSIZE );
		if( !dbxCheckCommandInfo( &cmd, length ) || \
			cmd.type != COMMAND_RESULT )
			{
			netEncodeError( buffer, CRYPT_ERROR_BADDATA );
			return;
			}

		/* Read the rest of the message */
		status = sread( &dbmsInfo->stream, buffer + COMMAND_FIXED_DATA_SIZE,
						length );
		}

	/* If it's a close command, terminate the connection to the plugin.  We
	   don't do any error checking once we get this far since there's not
	   much we can do at this point */
	if( cmd.type == DBX_COMMAND_CLOSE )
		sNetDisconnect( &dbmsInfo->stream );
	}
#endif /* NET_TCP */

/****************************************************************************
*																			*
*							Database RPC Routines							*
*																			*
****************************************************************************/

/* Dispatch functions for various database types.  ODBC is the native keyset
   for Windows, MySQL is the native keyset for Unix, the rest are only
   accessible via database backend-specific plugins */

#ifdef __WINDOWS__
  void odbcProcessCommand( void *stateInfo, BYTE *buffer );
#else
  #define odbcProcessCommand	NULL
#endif /* __WINDOWS__ */
#ifdef DBX_MYSQL
  void mysqlProcessCommand( void *stateInfo, BYTE *buffer );
#else
  #define mysqlProcessCommand	NULL
#endif /* DBX_MYSQL */
#ifdef NET_TCP
  void netProcessCommand( void *stateInfo, BYTE *buffer );
#else
  #define netProcessCommand		NULL
#endif /* NET_TCP */

/* Make sure we can fit the largest possible SQL query into the RPC buffer */

#if MAX_SQL_QUERY_SIZE + 256 >= DBX_IO_BUFSIZE
  #error Database RPC buffer size is too small, increase DBX_IO_BUFSIZE and rebuild
#endif /* SQL query size larger than RPC buffer size */

/* Initialise and shut down a session with a database backend */

static int initDbxSession( KEYSET_INFO *keysetInfo,
						   const CRYPT_KEYSET_TYPE type )
	{
	/* Select the appropriate dispatch function for the keyset type */
	switch( type )
		{
		case CRYPT_KEYSET_MYSQL:
		case CRYPT_KEYSET_MYSQL_STORE:
			keysetInfo->keysetDBMS.dispatchFunction = mysqlProcessCommand;
			break;
		case CRYPT_KEYSET_ODBC:
		case CRYPT_KEYSET_ODBC_STORE:
			keysetInfo->keysetDBMS.dispatchFunction = odbcProcessCommand;
			break;
		case CRYPT_KEYSET_DATABASE:
		case CRYPT_KEYSET_DATABASE_STORE:
			keysetInfo->keysetDBMS.dispatchFunction = netProcessCommand;
			break;
		default:
			assert( NOTREACHED );
		}
	if( keysetInfo->keysetDBMS.dispatchFunction == NULL )
		return( CRYPT_ARGERROR_NUM1 );

	/* Allocate the database session state information */
	if( ( keysetInfo->keyData = \
				malloc( sizeof( DBMS_STATE_INFO ) ) ) == NULL )
		return( CRYPT_ERROR_MEMORY );
	memset( keysetInfo->keyData, 0, sizeof( DBMS_STATE_INFO ) );
	keysetInfo->keyDataSize = sizeof( DBMS_STATE_INFO );
	keysetInfo->keysetDBMS.stateInfo = keysetInfo->keyData;
	if( type == CRYPT_KEYSET_MYSQL_STORE || \
		type == CRYPT_KEYSET_ODBC_STORE || \
		type == CRYPT_KEYSET_DATABASE_STORE )
		keysetInfo->keysetDBMS.isCertStore = TRUE;

	return( CRYPT_OK );
	}

static int endDbxSession( KEYSET_INFO *keysetInfo )
	{
	/* Free the database session state information if necessary */
	if( keysetInfo->keyData != NULL )
		{
		memset( keysetInfo->keyData, 0, keysetInfo->keyDataSize );
		free( keysetInfo->keyData );
		keysetInfo->keyData = NULL;
		}

	return( CRYPT_OK );
	}

/* Dispatch data to the backend */

static int dispatchCommand( COMMAND_INFO *cmd, void *stateInfo,
							DISPATCH_FUNCTION dispatchFunction )
	{
	COMMAND_INFO sentCmd = *cmd;
	BYTE buffer[ DBX_IO_BUFSIZE ], *bufPtr = buffer;
	BYTE header[ COMMAND_FIXED_DATA_SIZE ];
	const int payloadLength = ( cmd->noArgs * COMMAND_WORDSIZE ) + \
							  ( cmd->noStrArgs * COMMAND_WORDSIZE ) + \
							  cmd->strArgLen[ 0 ] + cmd->strArgLen[ 1 ] + \
							  cmd->strArgLen[ 2 ];
	long dataLength = cmd->strArgLen[ 0 ], resultLength;
	int i;

	assert( payloadLength + 32 < DBX_IO_BUFSIZE );
	assert( dispatchFunction != NULL );

	/* Clear the return value */
	memset( cmd, 0, sizeof( COMMAND_INFO ) );

	/* Write the header and message fields to the buffer */
	putMessageType( bufPtr, sentCmd.type, sentCmd.flags,
					sentCmd.noArgs, sentCmd.noStrArgs );
	putMessageLength( bufPtr + COMMAND_WORDSIZE, payloadLength );
	bufPtr += COMMAND_FIXED_DATA_SIZE;
	for( i = 0; i < sentCmd.noArgs; i++ )
		{
		putMessageWord( bufPtr, sentCmd.arg[ i ] );
		bufPtr += COMMAND_WORDSIZE;
		}
	for( i = 0; i < sentCmd.noStrArgs; i++ )
		{
		const int argLength = sentCmd.strArgLen[ i ];

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -