/**************************************************************************** * * * cryptlib DBMS CA Cert Misc Interface * * Copyright Peter Gutmann 1996-2004 * * * ****************************************************************************/ #include /* For snprintf() */ #if defined( INC_ALL ) #include "crypt.h" #include "keyset.h" #include "dbms.h" #include "asn1.h" #include "rpc.h" #else #include "crypt.h" #include "keyset/keyset.h" #include "keyset/dbms.h" #include "misc/asn1.h" #include "misc/rpc.h" #endif /* Compiler-specific includes */ #ifdef USE_DBMS /**************************************************************************** * * * Utility Functions * * * ****************************************************************************/ #if 0 /* Get the ultimate successor cert for one that's been superseded */ static int getSuccessorCert( DBMS_INFO *dbmsInfo, CRYPT_CERTIFICATE *iCertificate, const char *initialCertID ) { char certID[ DBXKEYID_BUFFER_SIZE + 8 ]; int chainingLevel = 0, status; /* Walk through the chain of renewals in the cert log until we find the ultimate successor cert to the current one */ strlcpy_s( certID, DBXKEYID_BUFFER_SIZE, initialCertID ); do { BYTE keyCertID[ DBXKEYID_SIZE + BASE64_OVFL_SIZE + 8 ]; char certData[ MAX_QUERY_RESULT_SIZE + 8 ]; int certDataLength, length, dummy; /* Find the request to renew this certificate */ status = dbmsQuery( "SELECT certID FROM certLog WHERE subjCertID = ? " "AND action = " TEXT_CERTACTION_REQUEST_RENEWAL, certData, &certDataLength, certID, strlen( certID ), 0, DBMS_CACHEDQUERY_NONE, DBMS_QUERY_NORMAL ); if( cryptStatusError( status ) ) return( status ); /* Find the resulting certificate */ memcpy( certID, certData, min( certDataLength, MAX_ENCODED_DBXKEYID_SIZE + 1 ) ); certID[ MAX_ENCODED_DBXKEYID_SIZE ] = '\0'; status = dbmsQuery( "SELECT certID FROM certLog WHERE reqCertID = ? " "AND action = " TEXT_CERTACTION_CERT_CREATION, certData, &certDataLength, certID, strlen( certID ), 0, DBMS_CACHEDQUERY_NONE, DBMS_QUERY_NORMAL ); if( cryptStatusOK( status ) ) { status = length = \ base64decode( keyCertID, certData, min( certDataLength, MAX_ENCODED_DBXKEYID_SIZE ), CRYPT_CERTFORMAT_NONE ); assert( !cryptStatusError( status ) ); } if( cryptStatusError( status ) ) return( status ); /* Try and get the replacement cert */ status = getItemData( dbmsInfo, iCertificate, &dummy, getKeyName( CRYPT_IKEYID_CERTID ), keyCertID, length, KEYMGMT_ITEM_PUBLICKEY, KEYMGMT_FLAG_NONE ); } while( status == CRYPT_ERROR_NOTFOUND && \ chainingLevel++ < FAILSAFE_ITERATIONS_MED ); if( chainingLevel >= FAILSAFE_ITERATIONS_MED ) /* We've chained through too many entries, bail out */ return( CRYPT_ERROR_OVERFLOW ); return( status ); } #endif /* 0 */ /* Get the PKI user that originally authorised the issuance of a cert. This can involve chaining back through multiple generations of certificates, for example to check authorisation on a revocation request we might have to go through: rev_req: get reqCertID = update_req update_req: get reqCertID = cert_req cert_req: get reqCertID = init_req init_req: get reqCertID = pki_user */ static int getIssuingUser( DBMS_INFO *dbmsInfo, CRYPT_CERTIFICATE *iPkiUser, const char *initialCertID, const int initialCertIDlength ) { char certID[ DBXKEYID_BUFFER_SIZE + 8 ]; int certIDlength, chainingLevel, dummy, status; /* Walk through the chain of updates in the cert log until we find the PKI user that authorised the first cert issue */ memcpy( certID, initialCertID, initialCertIDlength ); certIDlength = initialCertIDlength; for( chainingLevel = 0; chainingLevel < FAILSAFE_ITERATIONS_MED; chainingLevel++ ) { char certData[ MAX_QUERY_RESULT_SIZE + 8 ]; int certDataLength; /* Find out whether this is a PKI user. The comparison for the action type is a bit odd since some back-ends will return the action as text and some as a binary numeric value. Rather than relying on the back-end glue code to perform the appropriate conversion we just check for either value type */ status = dbmsQuery( "SELECT action FROM certLog WHERE certID = ?", certData, &certDataLength, certID, certIDlength, 0, DBMS_CACHEDQUERY_NONE, DBMS_QUERY_NORMAL ); if( cryptStatusError( status ) ) return( status ); if( certData[ 0 ] == CRYPT_CERTACTION_ADDUSER || \ certData[ 0 ] == TEXTCH_CERTACTION_ADDUSER ) /* We've found the PKI user, we're done */ break; /* Find the certificate that was issued, recorded either as a CERTACTION_CERT_CREATION for a multi-phase CMP-based cert creation or a CERTACTION_ISSUE_CERT for a one-step creation */ status = dbmsQuery( "SELECT reqCertID FROM certLog WHERE certID = ?", certData, &certDataLength, certID, certIDlength, 0, DBMS_CACHEDQUERY_NONE, DBMS_QUERY_NORMAL ); if( cryptStatusError( status ) ) return( status ); certIDlength = min( certDataLength, MAX_ENCODED_DBXKEYID_SIZE ); memcpy( certID, certData, certIDlength ); /* Find the request to issue this certificate. For a CMP-based issue this will have an authorising object (found in the next iteration through the loop), for a one-step issue it won't */ status = dbmsQuery( "SELECT reqCertID FROM certLog WHERE certID = ?", certData, &certDataLength, certID, certIDlength, 0, DBMS_CACHEDQUERY_NONE, DBMS_QUERY_NORMAL ); if( cryptStatusError( status ) ) return( status ); certIDlength = min( certDataLength, MAX_ENCODED_DBXKEYID_SIZE ); memcpy( certID, certData, certIDlength ); } if( chainingLevel >= FAILSAFE_ITERATIONS_MED ) /* We've chained through too many entries, bail out */ return( CRYPT_ERROR_OVERFLOW ); /* We've found the original PKI user, get the user info */ return( getItemData( dbmsInfo, iPkiUser, &dummy, CRYPT_IKEYID_CERTID, certID, certIDlength, KEYMGMT_ITEM_PKIUSER, KEYMGMT_FLAG_NONE ) ); } /* Get a partially-issued certificate. We have to perform the import ourselves since it's marked as an incompletely-issued cert and so is invisible to access via the standard cert fetch routines */ static int getNextPartialCert( DBMS_INFO *dbmsInfo, CRYPT_CERTIFICATE *iCertificate, BYTE *prevCertData, const BOOLEAN isRenewal ) { MESSAGE_CREATEOBJECT_INFO createInfo; BYTE certificate[ MAX_QUERY_RESULT_SIZE + 8 ]; char encodedCertData[ MAX_QUERY_RESULT_SIZE + 8 ]; void *certPtr = hasBinaryBlobs( dbmsInfo ) ? \ ( void * ) certificate : encodedCertData; int certSize, status; /* Cast needed for gcc */ *iCertificate = CRYPT_ERROR; /* Find the next cert and import it. Although this would appear to be fetching the same cert over and over again, the caller will be deleting the currently-fetched cert after we return it to them, so in practice it fetches a new cert each time */ status = dbmsQuery( isRenewal ? \ "SELECT certData FROM certificates WHERE keyID LIKE '" KEYID_ESC2 "%'" : \ "SELECT certData FROM certificates WHERE keyID LIKE '" KEYID_ESC1 "%'", certPtr, &certSize, NULL, 0, 0, DBMS_CACHEDQUERY_NONE, DBMS_QUERY_NORMAL ); if( cryptStatusError( status ) ) return( status ); if( !hasBinaryBlobs( dbmsInfo ) ) { certSize = base64decode( certificate, MAX_CERT_SIZE, encodedCertData, certSize, CRYPT_CERTFORMAT_NONE ); if( cryptStatusError( certSize ) ) { assert( NOTREACHED ); return( certSize ); } } /* If we're stuck in a loop fetching the same value over and over, make an emergency exit */ if( !memcmp( prevCertData, certificate, 128 ) ) { assert( NOTREACHED ); return( CRYPT_ERROR_DUPLICATE ); } memcpy( prevCertData, certificate, 128 ); /* Reset the first byte of the cert data from the not-present magic value to allow it to be imported and create a certificate from it */ certificate[ 0 ] = BER_SEQUENCE; setMessageCreateObjectIndirectInfo( &createInfo, certificate, certSize, CRYPT_CERTTYPE_CERTIFICATE ); status = krnlSendMessage( SYSTEM_OBJECT_HANDLE, IMESSAGE_DEV_CREATEOBJECT_INDIRECT, &createInfo, OBJECT_TYPE_CERTIFICATE ); if( cryptStatusOK( status ) ) *iCertificate = createInfo.cryptHandle; return( status ); } /**************************************************************************** * * * Logging Functions * * * ****************************************************************************/ /* Add an entry to the CA log */ int updateCertLog( DBMS_INFO *dbmsInfo, const int action, const char *certID, const char *reqCertID, const char *subjCertID, const void *data, const int dataLength, const DBMS_UPDATE_TYPE updateType ) { char sqlFormatBuffer[ MAX_SQL_QUERY_SIZE + 8 ]; char sqlBuffer[ MAX_SQL_QUERY_SIZE + 8 ], actionString[ 16 + 8 ]; char certIDbuffer[ DBXKEYID_BUFFER_SIZE + 8 ]; char encodedCertData[ MAX_ENCODED_CERT_SIZE + 8 ]; char *certIDptr = ( char * ) certID; const void *dataPtr = data; const void *param1ptr, *param2ptr = "", *param3ptr = ""; const time_t boundDate = getApproxTime(); int dataPtrLength = dataLength; /* Build up the necessary SQL format string required to insert the log entry. This is complicated somewhat by the fact that some of the values may be NULL, so we have to insert them by naming the columns (some databases allow the use of the DEFAULT keyword but this isn't standardised enough to be safe) */ strlcpy_s( sqlFormatBuffer, MAX_SQL_QUERY_SIZE, "INSERT INTO certLog (action, actionTime, certID" ); if( reqCertID != NULL ) strlcat_s( sqlFormatBuffer, MAX_SQL_QUERY_SIZE, ", reqCertID" ); if( subjCertID != NULL ) strlcat_s( sqlFormatBuffer, MAX_SQL_QUERY_SIZE, ", subjCertID" ); if( data != NULL ) strlcat_s( sqlFormatBuffer, MAX_SQL_QUERY_SIZE, ", certData" ); strlcat_s( sqlFormatBuffer, MAX_SQL_QUERY_SIZE, ") VALUES ($, ?, '$'" ); if( reqCertID != NULL ) strlcat_s( sqlFormatBuffer, MAX_SQL_QUERY_SIZE, ", '$'" ); if( subjCertID != NULL ) strlcat_s( sqlFormatBuffer, MAX_SQL_QUERY_SIZE, ", '$'" ); if( data != NULL ) strlcat_s( sqlFormatBuffer, MAX_SQL_QUERY_SIZE, hasBinaryBlobs( dbmsInfo ) ? ", ?" : ", '$'" ); strlcat_s( sqlFormatBuffer, MAX_SQL_QUERY_SIZE, ")" ); /* Set up the appropriate parameter pointers to build the SQL command */ if( reqCertID == NULL ) { if( subjCertID == NULL ) param1ptr = encodedCertData; else { param1ptr = subjCertID; param2ptr = encodedCertData; } } else { param1ptr = reqCertID; if( subjCertID == NULL ) param2ptr = encodedCertData; else { param2ptr = subjCertID; param3ptr = encodedCertData; } } /* If we're not worried about the certID, we just insert a nonce value which is used to meet the constraints for a unique entry. In order to ensure that it doesn't clash with a real certID, we set the first four characters to an out-of-band value */ if( certID == NULL ) { MESSAGE_DATA msgData; BYTE nonce[ KEYID_SIZE + 8 ]; int status; certIDptr = certIDbuffer; setMessageData( &msgData, nonce, KEYID_SIZE ); status = krnlSendMessage( SYSTEM_OBJECT_HANDLE, IMESSAGE_GETATTRIBUTE_S, &msgData, CRYPT_IATTRIBUTE_RANDOM_NONCE ); if( cryptStatusOK( status ) ) { status = base64encode( certIDptr, DBXKEYID_BUFFER_SIZE, nonce, DBXKEYID_SIZE, CRYPT_CERTTYPE_NONE ); certIDptr[ MAX_ENCODED_DBXKEYID_SIZE ] = '\0'; } if( cryptStatusError( status ) ) { /* Normally this is a should-never-occur error, however if cryptlib has been shut down from another thread the kernel will fail all non shutdown-related calls with a permission error. To avoid false alarms, we mask out failures due to permission errors */ assert( ( status == CRYPT_ERROR_PERMISSION ) || NOTREACHED ); return( status ); } memset( certIDptr, '-', 4 ); } /* Update the cert log */ sprintf_s( actionString, 8, "%d", action ); if( data != NULL && !hasBinaryBlobs( dbmsInfo ) ) { dataPtrLength = base64encode( encodedCertData, MAX_ENCODED_CERT_SIZE, data, dataLength, CRYPT_CERTTYPE_NONE ); if( cryptStatusError( dataPtrLength ) ) { assert( NOTREACHED ); return( dataPtrLength ); } encodedCertData[ dataPtrLength ] = '\0'; dataPtr = encodedCertData; } dbmsFormatSQL( sqlBuffer, MAX_SQL_QUERY_SIZE, sqlFormatBuffer, actionString, certIDptr, param1ptr, param2ptr, param3ptr ); return( dbmsUpdate( sqlBuffer, dataPtr, dataPtrLength, boundDate, updateType ) ); } int updateCertErrorLog( DBMS_INFO *dbmsInfo, const int errorStatus, const char *errorString, const char *certID, const char *reqCertID, const char *subjCertID, const void *data, const int dataLength ) { STREAM stream; BYTE errorData[ MAX_CERT_SIZE + 8 ]; const int errorStringLength = strlen( errorString ); int errorDataLength, status; /* Encode the error information: SEQUENCE { errorStatus INTEGER, errorString UTF8String, certData ANY OPTIONAL } */ sMemOpen( &stream, errorData, MAX_CERT_SIZE ); writeSequence( &stream, sizeofShortInteger( -errorStatus ) + \ ( int ) sizeofObject( errorStringLength ) + \ dataLength ); writeShortInteger( &stream, -errorStatus, DEFAULT_TAG ); status = writeCharacterString( &stream, errorString, errorStringLength, BER_STRING_UTF8 ); if( dataLength > 0 ) status = swrite( &stream, data, dataLength ); errorDataLength = stell( &stream ); sMemDisconnect( &stream ); if( cryptStatusError( status ) ) { sMemOpen( &stream, errorData, MAX_CERT_SIZE ); writeSequence( &stream, ( 1 + 1 + 1 ) + ( 1 + 1 + 31 ) ); writeShortInteger( &stream, -( CRYPT_ERROR_FAILED ), DEFAULT_TAG ); writeCharacterString( &stream, "Error writing error information", 31, BER_STRING_UTF8 ); errorDataLength = stell( &stream ); sMemDisconnect( &stream ); } /* Update the cert log with the error information as the data value */ return( updateCertLog( dbmsInfo, CRYPT_CERTACTION_ERROR, certID, reqCertID, subjCertID, errorData, errorDataLength, DBMS_UPDATE_NORMAL ) ); } int updateCertErrorLogMsg( DBMS_INFO *dbmsInfo, const int errorStatus, const char *errorString ) { return( updateCertErrorLog( dbmsInfo, errorStatus, errorString, NULL, NULL, NULL, NULL, 0 ) ); } /**************************************************************************** * * * Miscellaneous CA Functions * * * ****************************************************************************/ /* Get the PKI user that originally authorised the issuing of a cert */ int caGetIssuingUser( DBMS_INFO *dbmsInfo, CRYPT_CERTIFICATE *iPkiUser, const char *initialCertID, const int initialCertIDlength ) { assert( isWritePtr( dbmsInfo, sizeof( DBMS_INFO ) ) ); assert( isWritePtr( iPkiUser, sizeof( CRYPT_CERTIFICATE ) ) ); assert( isReadPtr( initialCertID, MAX_ENCODED_DBXKEYID_SIZE ) ); assert( initialCertIDlength >= MAX_ENCODED_DBXKEYID_SIZE ); return( getIssuingUser( dbmsInfo, iPkiUser, initialCertID, initialCertIDlength ) ); } /* Perform a cleanup operation on the certificate store, removing incomplete, expired, and otherwise leftover certificates */ static int caCleanup( DBMS_INFO *dbmsInfo, const CRYPT_CERTACTION_TYPE action ) { BYTE prevCertData[ 128 + 8 ]; char sqlBuffer[ STANDARD_SQL_QUERY_SIZE + 8 ]; const time_t currentTime = getTime(); int errorCount, status; assert( isWritePtr( dbmsInfo, sizeof( DBMS_INFO ) ) ); assert( action == CRYPT_CERTACTION_EXPIRE_CERT || \ action == CRYPT_CERTACTION_CLEANUP ); /* If the time is screwed up we can't perform time-based cleanup actions */ if( action == CRYPT_CERTACTION_EXPIRE_CERT && \ currentTime <= MIN_TIME_VALUE ) return( CRYPT_ERROR_FAILED ); /* Rumble through the cert store either deleting leftover requests or expiring every cert which is no longer current. Since we're cleaning up the cert store we try and continue even if an error occurs, at least up to a limit */ memset( prevCertData, 0, 8 ); errorCount = 0; do { char certID[ MAX_QUERY_RESULT_SIZE + 8 ]; int certIDlength; /* Find the cert ID of the next expired cert or next cert request (revocation requests are handled later by completing the revocation). Note that the select requires that the database glue code be capable of returning a single result and then finishing the query, for some back-ends there may be a need to explicitly cancel the query after the first result is returned if the database returns an entire result set */ if( action == CRYPT_CERTACTION_EXPIRE_CERT ) status = dbmsQuery( "SELECT certID FROM certificates WHERE validTo < ?", certID, &certIDlength, NULL, 0, currentTime, DBMS_CACHEDQUERY_NONE, DBMS_QUERY_NORMAL ); else status = dbmsQuery( "SELECT certID FROM certRequests WHERE type = " TEXT_CERTTYPE_REQUEST_CERT, certID, &certIDlength, NULL, 0, 0, DBMS_CACHEDQUERY_NONE, DBMS_QUERY_NORMAL ); if( cryptStatusError( status ) || \ certIDlength > MAX_ENCODED_DBXKEYID_SIZE ) { errorCount++; continue; } if( !memcmp( prevCertData, certID, certIDlength ) ) /* We're stuck in a loop fetching the same value over and over, make an emergency exit */ break; memcpy( prevCertData, certID, certIDlength ); /* Clean up/expire the cert. Since CRYPT_CERTACTION_CLEANUP is a composite action that encompasses a whole series of operations, we replace it with a more specific action code */ certID[ certIDlength ] = '\0'; status = updateCertLog( dbmsInfo, ( action == CRYPT_CERTACTION_CLEANUP ) ? \ CRYPT_CERTACTION_RESTART_CLEANUP : action, NULL, NULL, certID, NULL, 0, DBMS_UPDATE_BEGIN ); if( cryptStatusOK( status ) ) { dbmsFormatSQL( sqlBuffer, STANDARD_SQL_QUERY_SIZE, ( action == CRYPT_CERTACTION_EXPIRE_CERT ) ? \ "DELETE FROM certificates WHERE certID = '$'" : \ "DELETE FROM certRequests WHERE certID = '$'", certID ); status = dbmsUpdate( sqlBuffer, NULL, 0, 0, DBMS_UPDATE_COMMIT ); } else { /* Something went wrong, abort the transaction */ dbmsUpdate( NULL, NULL, 0, 0, DBMS_UPDATE_ABORT ); errorCount++; } } while( status != CRYPT_ERROR_NOTFOUND && \ errorCount < FAILSAFE_ITERATIONS_SMALL ); /* If we ran into a problem, perform a fallback general delete of entries that caused the problem */ if( status != CRYPT_ERROR_NOTFOUND ) { if( action == CRYPT_CERTACTION_EXPIRE_CERT ) { updateCertErrorLogMsg( dbmsInfo, status, "Expire operation " "failed, performing fallback straight " "delete" ); status = dbmsUpdate( "DELETE FROM certificates WHERE validTo < ?", NULL, 0, currentTime, DBMS_UPDATE_NORMAL ); } else { updateCertErrorLogMsg( dbmsInfo, status, "Cert request " "cleanup operation failed, performing " "fallback straight delete" ); status = dbmsStaticUpdate( "DELETE FROM certRequests WHERE type = " TEXT_CERTTYPE_REQUEST_CERT ); } if( cryptStatusError( status ) ) updateCertErrorLogMsg( dbmsInfo, status, "Fallback straight " "delete failed" ); } /* If it's an expiry action we've done the expired certs, now remove any stale CRL entries and exit. If there are no CRL entries in the expiry period this isn't an error, so we remap the error code if necessary */ if( action == CRYPT_CERTACTION_EXPIRE_CERT ) { status = dbmsUpdate( "DELETE FROM CRLs WHERE expiryDate < ?", NULL, 0, currentTime, DBMS_UPDATE_NORMAL ); return( ( status == CRYPT_ERROR_NOTFOUND ) ? \ resetErrorInfo( dbmsInfo ) : status ); } /* It's a restart, process any incompletely-issued certificates in the same manner as the expiry/cleanup is handled. Since we don't know at what stage the issue process was interrupted, we have to make a worst- case assumption and do a full reversal as a compensating transaction for an aborted cert issue */ memset( prevCertData, 0, 8 ); errorCount = 0; do { CRYPT_CERTIFICATE iCertificate; /* Get the next partially-issued cert */ status = getNextPartialCert( dbmsInfo, &iCertificate, prevCertData, FALSE ); if( status == CRYPT_ERROR_DUPLICATE ) /* We're stuck in a loop fetching the same cert over and over, exit */ break; if( cryptStatusOK( status ) ) { /* We found a cert to revoke, complete the revocation */ status = revokeCertDirect( dbmsInfo, iCertificate, CRYPT_CERTACTION_CERT_CREATION_REVERSE ); krnlSendNotifier( iCertificate, IMESSAGE_DECREFCOUNT ); } else errorCount++; } while( status != CRYPT_ERROR_NOTFOUND && \ errorCount < FAILSAFE_ITERATIONS_SMALL ); /* If we ran into a problem, perform a fallback general delete of entries that caused the problem */ if( status != CRYPT_ERROR_NOTFOUND ) { updateCertErrorLogMsg( dbmsInfo, status, "Partially-issued " "certificate cleanup operation failed, " "performing fallback straight delete" ); status = dbmsStaticUpdate( "DELETE FROM certificates WHERE keyID LIKE '" KEYID_ESC1 "%'" ); if( cryptStatusError( status ) ) updateCertErrorLogMsg( dbmsInfo, status, "Fallback straight " "delete failed" ); } /* Now process any partially-completed renewals */ memset( prevCertData, 0, 8 ); errorCount = 0; do { CRYPT_CERTIFICATE iCertificate; /* Get the next partially-completed cert */ status = getNextPartialCert( dbmsInfo, &iCertificate, prevCertData, TRUE ); if( status == CRYPT_ERROR_DUPLICATE ) /* We're stuck in a loop fetching the same cert over and over, exit */ break; if( cryptStatusOK( status ) ) { /* We found a partially-completed cert, complete the renewal */ status = completeCertRenewal( dbmsInfo, iCertificate ); krnlSendNotifier( iCertificate, IMESSAGE_DECREFCOUNT ); } else errorCount++; } while( status != CRYPT_ERROR_NOTFOUND && \ errorCount < FAILSAFE_ITERATIONS_SMALL ); /* Finally, process any pending revocations */ memset( prevCertData, 0, 8 ); errorCount = 0; do { CRYPT_CERTIFICATE iCertRequest; char certID[ MAX_QUERY_RESULT_SIZE + 8 ]; int certIDlength, dummy; /* Find the next revocation request and import it. This is slightly ugly since we could grab it directly by fetching the data based on the request type field, but there's no way to easily get to the low-level import functions from here so we have to first fetch the cert ID and then pass that down to the lower-level functions to fetch the actual request */ status = dbmsQuery( "SELECT certID FROM certRequests WHERE type = " TEXT_CERTTYPE_REQUEST_REVOCATION, certID, &certIDlength, NULL, 0, 0, DBMS_CACHEDQUERY_NONE, DBMS_QUERY_NORMAL ); if( cryptStatusError( status ) || \ certIDlength > MAX_ENCODED_DBXKEYID_SIZE ) { errorCount++; continue; } if( !memcmp( prevCertData, certID, certIDlength ) ) /* We're stuck in a loop fetching the same value over and over, make an emergency exit */ break; memcpy( prevCertData, certID, certIDlength ); status = getItemData( dbmsInfo, &iCertRequest, &dummy, CRYPT_IKEYID_CERTID, certID, certIDlength, KEYMGMT_ITEM_REQUEST, KEYMGMT_FLAG_NONE ); if( cryptStatusError( status ) ) { errorCount++; continue; } /* Complete the revocation */ status = caRevokeCert( dbmsInfo, iCertRequest, CRYPT_UNUSED, CRYPT_CERTACTION_RESTART_REVOKE_CERT ); if( status == CRYPT_ERROR_NOTFOUND ) { /* This is an allowable error type since the cert may have expired or been otherwise removed after the revocation request was received, just delete the entry */ certID[ certIDlength ] = '\0'; dbmsFormatSQL( sqlBuffer, STANDARD_SQL_QUERY_SIZE, "DELETE FROM certRequests WHERE certID = '$'", certID ); status = dbmsStaticUpdate( sqlBuffer ); updateCertErrorLog( dbmsInfo, status, "Deleted revocation " "request for non-present certificate", NULL, NULL, certID, NULL, 0 ); } krnlSendNotifier( iCertRequest, IMESSAGE_DECREFCOUNT ); } while( status != CRYPT_ERROR_NOTFOUND && \ errorCount < FAILSAFE_ITERATIONS_SMALL ); /* If we ran into a problem, perform a fallback general delete of entries that caused the problem */ if( status != CRYPT_ERROR_NOTFOUND ) { updateCertErrorLogMsg( dbmsInfo, status, "Revocation request " "cleanup operation failed, performing " "fallback straight delete" ); status = dbmsStaticUpdate( "DELETE FROM certRequests WHERE type = " TEXT_CERTTYPE_REQUEST_REVOCATION ); if( cryptStatusError( status ) ) updateCertErrorLogMsg( dbmsInfo, status, "Fallback straight " "delete failed" ); return( status ); } return( resetErrorInfo( dbmsInfo ) ); } /**************************************************************************** * * * CA Cert Management Interface * * * ****************************************************************************/ /* Perform a cert management operation */ static int certMgmtFunction( KEYSET_INFO *keysetInfo, CRYPT_CERTIFICATE *iCertificate, const CRYPT_CERTIFICATE caKey, const CRYPT_CERTIFICATE request, const CRYPT_CERTACTION_TYPE action ) { DBMS_INFO *dbmsInfo = keysetInfo->keysetDBMS; char reqCertID[ DBXKEYID_BUFFER_SIZE + 8 ]; int length, status; /* In order for various SQL query strings to use the correct values the type values have to match their text equivalents defined at the start of this file. Since we can't check this at compile time we have to do it here via an assertion */ assert( TEXT_CERTTYPE_REQUEST_CERT[ 0 ] - '0' == \ CRYPT_CERTTYPE_REQUEST_CERT ); assert( TEXT_CERTTYPE_REQUEST_REVOCATION[ 0 ] - '0' == \ CRYPT_CERTTYPE_REQUEST_REVOCATION ); assert( TEXT_CERTACTION_CREATE[ 0 ] - '0' == \ CRYPT_CERTACTION_CREATE ); assert( TEXTCH_CERTACTION_ADDUSER - '0' == \ CRYPT_CERTACTION_ADDUSER ); assert( TEXT_CERTACTION_REQUEST_CERT[ 0 ] - '0' == \ CRYPT_CERTACTION_REQUEST_CERT ); assert( TEXTCH_CERTACTION_REQUEST_CERT - '0' == \ CRYPT_CERTACTION_REQUEST_CERT ); assert( TEXT_CERTACTION_REQUEST_RENEWAL[ 0 ] - '0' == \ CRYPT_CERTACTION_REQUEST_RENEWAL ); assert( TEXTCH_CERTACTION_REQUEST_RENEWAL - '0' == \ CRYPT_CERTACTION_REQUEST_RENEWAL ); assert( TEXT_CERTACTION_CERT_CREATION[ 0 ] - '0' == \ CRYPT_CERTACTION_CERT_CREATION / 10 ); assert( TEXT_CERTACTION_CERT_CREATION[ 1 ] - '0' == \ CRYPT_CERTACTION_CERT_CREATION % 10 ); /* Clear the return value */ if( iCertificate != NULL ) *iCertificate = CRYPT_ERROR; /* If it's a simple cert expire or cleanup, there are no parameters to check so we can perform the action immediately */ if( action == CRYPT_CERTACTION_EXPIRE_CERT || \ action == CRYPT_CERTACTION_CLEANUP ) { assert( caKey == CRYPT_UNUSED ); assert( request == CRYPT_UNUSED ); return( caCleanup( dbmsInfo, action ) ); } /* If it's the completion of a cert creation, process it */ if( action == CRYPT_CERTACTION_CERT_CREATION_COMPLETE || \ action == CRYPT_CERTACTION_CERT_CREATION_DROP || \ action == CRYPT_CERTACTION_CERT_CREATION_REVERSE ) { assert( caKey == CRYPT_UNUSED ); return( caIssueCertComplete( dbmsInfo, request, action ) ); } /* Check that the CA key that we've been passed is in order. These checks are performed automatically during the issue process by the kernel when we try and convert the request into a cert, however we perform them explicitly here so that we can return a more meaningful error message to the caller */ if( action == CRYPT_CERTACTION_ISSUE_CRL ) { int value; /* If we're issuing a CRL, the key must be capable of CRL signing */ status = krnlSendMessage( caKey, IMESSAGE_GETATTRIBUTE, &value, CRYPT_CERTINFO_KEYUSAGE ); if( cryptStatusError( status ) || \ !( value & CRYPT_KEYUSAGE_CRLSIGN ) ) return( CAMGMT_ARGERROR_CAKEY ); } else /* For anything other than a revocation action (which just updates the cert store without doing anything else), the key must be a CA key */ if( action != CRYPT_CERTACTION_REVOKE_CERT && \ cryptStatusError( \ krnlSendMessage( caKey, IMESSAGE_CHECK, NULL, MESSAGE_CHECK_CA ) ) ) return( CAMGMT_ARGERROR_CAKEY ); /* If it's a CRL issue, it's a read-only operation on the CRL store for which we only need the CA cert (there's no request involved) */ if( action == CRYPT_CERTACTION_ISSUE_CRL ) { assert( request == CRYPT_UNUSED ); return( caIssueCRL( dbmsInfo, iCertificate, caKey ) ); } /* We're processing an action that request an explicit cert request, perform further checks on the request */ if( !checkRequest( request, action ) ) return( CAMGMT_ARGERROR_REQUEST ); /* Make sure that the request is present in the request table in order to issue a certificate for it. Again, this will be checked later, but we can return a more meaningful error here */ status = length = getKeyID( reqCertID, request, CRYPT_CERTINFO_FINGERPRINT_SHA ); if( cryptStatusError( status ) ) return( CAMGMT_ARGERROR_REQUEST ); status = dbmsQuery( "SELECT certData FROM certRequests WHERE certID = ?", NULL, 0, reqCertID, length, 0, DBMS_CACHEDQUERY_NONE, DBMS_QUERY_CHECK ); if( cryptStatusError( status ) ) return( CRYPT_ERROR_NOTFOUND ); /* If it's a revocation request, process it */ if( action == CRYPT_CERTACTION_REVOKE_CERT ) { assert( caKey == CRYPT_UNUSED ); return( caRevokeCert( dbmsInfo, request, CRYPT_UNUSED, CRYPT_CERTACTION_REVOKE_CERT ) ); } /* It's a cert issue request, issue the certificate */ assert( action == CRYPT_CERTACTION_ISSUE_CERT || \ action == CRYPT_CERTACTION_CERT_CREATION ); assert( isHandleRangeValid( caKey ) ); return( caIssueCert( dbmsInfo, iCertificate, caKey, request, action ) ); } /* Set up the function pointers to the keyset methods */ int initDBMSCA( KEYSET_INFO *keysetInfo ) { keysetInfo->keysetDBMS->certMgmtFunction = certMgmtFunction; return( CRYPT_OK ); } #endif /* USE_DBMS */