📄 dbx_misc.c
字号:
/****************************************************************************
* *
* cryptlib DBMS Misc Interface *
* Copyright Peter Gutmann 1996-2007 *
* *
****************************************************************************/
#if defined( INC_ALL )
#include "crypt.h"
#include "keyset.h"
#include "dbms.h"
#else
#include "crypt.h"
#include "keyset/keyset.h"
#include "keyset/dbms.h"
#endif /* Compiler-specific includes */
#ifdef USE_DBMS
/* The table structure for the various DBMS tables is (# = indexed,
* = unique, + = certificate store only):
certificates:
C, SP, L, O, OU, CN, email#, validTo, nameID#, issuerID#*, keyID#*, certID#*, certData
CRLs:
expiryDate+, nameID+, issuerID#*, certID#+, certData
pkiUsers+:
C, SP, L, O, OU, CN, nameID#*, keyID#*, certID, certData
certRequests+:
type, C, SP, L, O, OU, CN, email, certID, certData
certLog+:
action, date, certID#*, reqCertID, subjCertID, certData
Note that in the CRL table the certID is the ID of the certificate being
revoked and not of the per-entry CRL data, and in the PKIUsers table the
keyID isn't for a public key but a nonce used to identify the PKI user
and the nameID is used purely to ensure uniqueness of users.
The certificate store contains a table for logging certificate management
operations (e.g. 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 that caused the action to be
taken and the subject that 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 that detects attempts to add duplicates,
although this unfortunately requires the addition of dummy nonce certIDs
to handle certain types of actions that don't produce objects with
certIDs.
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 certificate and
remove the issue request. Duplicate certificate 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 that causes the revocation,
delete the certificate and the request that 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, <nonce>, <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 certificate 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 certificate/request was originally added. We can
fix the former by making the static values part of the select result,
i.e. "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 that 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" but 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 certificate 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 certificate being visible or a leftover request
blocking a new request for a split second longer than they should.
An additional feature that we could make use of for CA operations is the
use of foreign keys to ensure referential integrity, usually via entries
in the certificate log. For example we could require that all
certificate requests be authorised by adding an authCertID column to the
certReq table and constraining it with:
FOREIGN KEY (authCertID) REFERENCES certLog.reqCertID
however (apart from the overhead of adding extra indexed columns just to
ensure referential integrity) the syntax for this varies somewhat
between vendors so that it'd require assorted rewriting by the back-end
glue code to handle the different requirements for each database type.
In addition since the foreign key constraint is specified at table
create time we could experience strange failures on table creation
requiring special-purpose workarounds where we remove the foreign-key
constraint in the hope that the table create then succeeds.
An easier way to handle this is via manual references to entries in the
certificate log. Since this is append-only, a manual presence check can
never return an incorrect result (an entry can't be removed between time
of check and time of use) so this provides the same result as using
referential integrity mechanisms.
Another database feature that we could use is database triggers as a
backup for the access control settings. For example (using one
particular SQL dialect) we could say:
CREATE TRIGGER checkLog ON certLog FOR UPDATE, DELETE AS
BEGIN
ROLLBACK
END
However as the "dialect" reference in the above comment implies this
process is *extremely* back-end specific (far more so than access
controls and the use of foreign keys) so we can't really do much here
without ending up having to set different triggers for each back-end
type and even back-end version */
/****************************************************************************
* *
* Utility Routines *
* *
****************************************************************************/
/* Set up key ID information for a query. There are two variations of
this, makeKeyID() encodes an existing keyID value and getKeyID() reads an
attribute from an object and encodes it using makeKeyID() */
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3, 5 ) ) \
int makeKeyID( OUT_BUFFER( keyIdMaxLen, *keyIdLen ) char *keyID,
IN_LENGTH_SHORT_MIN( 16 ) const int keyIdMaxLen,
OUT_LENGTH_SHORT_Z int *keyIdLen,
IN_KEYID const CRYPT_KEYID_TYPE iDtype,
IN_BUFFER( idValueLength ) const void *idValue,
IN_LENGTH_SHORT const int idValueLength )
{
BYTE hashBuffer[ CRYPT_MAX_HASHSIZE + 8 ];
int idLength = idValueLength, status;
assert( isWritePtr( keyID, keyIdMaxLen ) );
assert( isWritePtr( keyIdLen, sizeof( int ) ) );
assert( isReadPtr( keyID, idValueLength ) );
REQUIRES( keyIdMaxLen >= 16 && keyIdMaxLen < MAX_INTLENGTH_SHORT );
REQUIRES( iDtype == CRYPT_KEYID_NAME || \
iDtype == CRYPT_KEYID_URI || \
iDtype == CRYPT_IKEYID_KEYID || \
iDtype == CRYPT_IKEYID_ISSUERID || \
iDtype == CRYPT_IKEYID_CERTID );
REQUIRES( idValueLength > 0 && idValueLength < MAX_INTLENGTH_SHORT );
/* Clear return values */
memset( keyID, 0, min( 16, keyIdMaxLen ) );
*keyIdLen = 0;
/* Name and email address are used as is */
if( iDtype == CRYPT_KEYID_NAME || iDtype == CRYPT_KEYID_URI )
{
if( idLength > CRYPT_MAX_TEXTSIZE * 2 )
{
/* Truncate to the database column size */
idLength = CRYPT_MAX_TEXTSIZE * 2;
}
if( idLength > keyIdMaxLen )
{
/* Truncate to the output buffer size */
idLength = keyIdMaxLen;
}
memcpy( keyID, idValue, idLength );
if( iDtype == CRYPT_KEYID_URI )
{
int i;
/* Force the search URI to lowercase to make case-insensitive
matching easier. In most cases we could ask the back-end to
do this but this complicates indexing and there's no reason
why we can't do it here */
for( i = 0; i < idLength; i++ )
keyID[ i ] = toLower( keyID[ i ] );
}
*keyIdLen = idLength;
return( CRYPT_OK );
}
/* A keyID is just a subjectKeyIdentifier, which is already supposed to
be an SHA-1 hash but which in practice can be almost anything so we
always hash it to a fixed-length value */
if( iDtype == CRYPT_IKEYID_KEYID )
{
HASHFUNCTION_ATOMIC hashFunctionAtomic;
/* Get the hash algorithm information and hash the keyID to get
the fixed-length keyID */
getHashAtomicParameters( CRYPT_ALGO_SHA1, &hashFunctionAtomic, NULL );
hashFunctionAtomic( hashBuffer, CRYPT_MAX_HASHSIZE,
idValue, idValueLength );
idValue = hashBuffer;
idLength = DBXKEYID_SIZE;
}
ENSURES( idLength >= DBXKEYID_SIZE && idLength <= keyIdMaxLen );
/* base64-encode the key ID so that we can use it with database queries.
Since we only store 128 bits of a (usually 160 bit) ID to save space
(particularly where it's used in indices) and to speed lookups, this
encoding step has the side-effect of truncating the ID down to the
correct size */
status = base64encode( keyID, keyIdMaxLen, keyIdLen, idValue,
DBXKEYID_SIZE, CRYPT_CERTTYPE_NONE );
if( cryptStatusError( status ) )
retIntError();
ENSURES( *keyIdLen == ENCODED_DBXKEYID_SIZE );
return( CRYPT_OK );
}
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3 ) ) \
int getKeyID( OUT_BUFFER( keyIdMaxLen, *keyIdLen ) char *keyID,
IN_LENGTH_SHORT_MIN( 16 ) const int keyIdMaxLen,
OUT_LENGTH_SHORT_Z int *keyIdLen,
IN_HANDLE const CRYPT_HANDLE iCryptHandle,
IN_ATTRIBUTE const CRYPT_ATTRIBUTE_TYPE keyIDtype )
{
BYTE hashBuffer[ CRYPT_MAX_HASHSIZE + 8 ];
int status;
assert( isWritePtr( keyID, keyIdMaxLen ) );
assert( isWritePtr( keyIdLen, sizeof( int ) ) );
REQUIRES( keyIdMaxLen >= 16 && keyIdMaxLen < MAX_INTLENGTH_SHORT );
REQUIRES( isHandleRangeValid( iCryptHandle ) );
REQUIRES( ( keyIDtype == CRYPT_CERTINFO_FINGERPRINT_SHA || \
keyIDtype == CRYPT_IATTRIBUTE_AUTHCERTID ) || \
( keyIDtype == CRYPT_CERTINFO_SUBJECTKEYIDENTIFIER || \
keyIDtype == CRYPT_IATTRIBUTE_ISSUER || \
keyIDtype == CRYPT_IATTRIBUTE_SUBJECT || \
keyIDtype == CRYPT_IATTRIBUTE_ISSUERANDSERIALNUMBER || \
keyIDtype == CRYPT_IATTRIBUTE_SPKI ) );
/* Clear return values */
memset( keyID, 0, min( 16, keyIdMaxLen ) );
*keyIdLen = 0;
/* Get the attribute from the certificate and hash it, unless it's
already a hash */
if( keyIDtype == CRYPT_CERTINFO_FINGERPRINT_SHA || \
keyIDtype == CRYPT_IATTRIBUTE_AUTHCERTID )
{
MESSAGE_DATA msgData;
setMessageData( &msgData, hashBuffer, CRYPT_MAX_HASHSIZE );
status = krnlSendMessage( iCryptHandle, IMESSAGE_GETATTRIBUTE_S,
&msgData, keyIDtype );
if( cryptStatusError( status ) )
return( status );
ENSURES( msgData.length == KEYID_SIZE );
}
else
{
DYNBUF idDB;
HASHFUNCTION_ATOMIC hashFunctionAtomic;
int hashSize;
/* Get the attribute data and hash it to get the ID */
status = dynCreate( &idDB, iCryptHandle, keyIDtype );
if( cryptStatusError( status ) )
return( status );
getHashAtomicParameters( CRYPT_ALGO_SHA1, &hashFunctionAtomic,
&hashSize );
hashFunctionAtomic( hashBuffer, CRYPT_MAX_HASHSIZE,
dynData( idDB ), dynLength( idDB ) );
ENSURES( hashSize == KEYID_SIZE );
dynDestroy( &idDB );
}
return( makeKeyID( keyID, keyIdMaxLen, keyIdLen, CRYPT_IKEYID_CERTID,
hashBuffer, KEYID_SIZE ) );
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -