cryptlib  3.4.1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros
dbx_misc.c
Go to the documentation of this file.
1 /****************************************************************************
2 * *
3 * cryptlib DBMS Misc Interface *
4 * Copyright Peter Gutmann 1996-2007 *
5 * *
6 ****************************************************************************/
7 
8 #if defined( INC_ALL )
9  #include "crypt.h"
10  #include "keyset.h"
11  #include "dbms.h"
12 #else
13  #include "crypt.h"
14  #include "keyset/keyset.h"
15  #include "keyset/dbms.h"
16 #endif /* Compiler-specific includes */
17 
18 #ifdef USE_DBMS
19 
20 /* The table structure for the various DBMS tables is (# = indexed,
21  * = unique, + = certificate store only):
22 
23  certificates:
24  C, SP, L, O, OU, CN, email#, validTo, nameID#, issuerID#*, keyID#*, certID#*, certData
25  CRLs:
26  expiryDate+, nameID+, issuerID#*, certID#+, crl.certData
27  pkiUsers+:
28  C, SP, L, O, OU, CN, nameID#*, user.keyID#*, user.certID, user.certData
29  certRequests+:
30  type, C, SP, L, O, OU, CN, email, req.certID, req.certData
31  certLog+:
32  action, date, certID#*, req.certID, subjCertID, log.certData
33 
34  Note that in the CRL table the certID is the ID of the certificate being
35  revoked and not of the per-entry CRL data, and in the PKIUsers table the
36  keyID isn't for a public key but a nonce used to identify the PKI user
37  and the nameID is used purely to ensure uniqueness of users.
38 
39  The certificate store contains a table for logging certificate management
40  operations (e.g. when issued, when revoked, etc etc). The operations are
41  tied together by the certID of the object that the log entry pertains to,
42  associated with this in the log are optional certIDs of the request that
43  caused the action to be taken and of the subject that was affected by the
44  request. For example in the most complex case of a revocation operation
45  the certID would be of the CRL entry that was created, the req.certID
46  would be for the revocation request, and the subjCertID would be for the
47  certificate being revoked. This allows a complete history of each item
48  to be built via the log. The certLog has a UNIQUE INDEX on the certID
49  that detects attempts to add duplicates, although this unfortunately
50  requires the addition of dummy nonce certIDs to handle certain types of
51  actions that don't produce objects with certIDs (see the comment further
52  down on the use of constraints).
53 
54  The handling for each type of CA management operation is:
55 
56  CERTACTION_REQUEST_CERT/CERTACTION_REQUEST_RENEWAL/
57  CERTACTION_REQUEST_REVOCATION: Stores the incoming requests and
58  generates a log entry. Duplicate issue requests are detected by the
59  certLog.certID uniqueness constraint. Available: request with certID:
60 
61  INSERT INTO certRequests VALUES ( <type>, <DN components>, <certID>, <request> );
62  INSERT INTO certLog VALUES
63  (ACTION_REQUEST_CERT/RENEWAL/REVOCATION, $date, <certID>, NULL, NULL,
64  <request>);
65 
66  CERTACTION_ISSUE_CERT/CERTACTION_CERT_CREATION: Add the certificate and
67  remove the issue request. Duplicate certificate issuance is detected by
68  the certLog.certID uniqueness constraint. Available: request with
69  req.certID, certificate with certID
70 
71  INSERT INTO certificates VALUES (<DN components>, <IDs>, <cert>);
72  INSERT INTO certLog VALUES
73  (ACTION_ISSUE_CERT/CERT_CREATION, $date, <certID>, <req.certID>, NULL,
74  <cert>);
75  DELETE FROM certRequests WHERE certID = <req.certID>;
76 
77  CERTACTION_ISSUE_CRL: Read each CRL entry with caCert.nameID and
78  assemble the full CRL. Requires an ongoing query:
79 
80  SELECT FROM CRLs WHERE nameID = <caCert.nameID>
81 
82  CERTACTION_REVOKE_CERT: Add the CRL entry that causes the revocation,
83  delete the certificate and the request that caused the action.
84  Available: request with req.certID, certificate with cert.certID, CRL
85  entry with certID
86 
87  INSERT INTO CRLs VALUES (<IDs>, <crlData>);
88  INSERT INTO certLog VALUES
89  (ACTION_REVOKE_CERT, $date, <nonce>, <req.certID>, <cert.certID>, <crlData>);
90  DELETE FROM certRequests WHERE certID = <req.certID>;
91  DELETE FROM certificates WHERE certID = <cert.certID>;
92 
93  CERTACTION_EXPIRE_CERT/CERTACTION_RESTART_CLEANUP: Delete each expired
94  entry or clean up leftover certificate requests after a restart. The
95  logging for these is a bit tricky, ideally we'd want to "INSERT INTO
96  certLog VALUES (ACTION_CERT_EXPIRE, $date, SELECT certID FROM
97  certificates WHERE validTo <= $date)" or the cleanup equivalent, however
98  this isn't possible both because it's not possible to mix static values
99  and a select result in an INSERT and because the certID is already
100  present from when the certificate/request was originally added. We can
101  fix the former by making the static values part of the select result,
102  i.e. "INSERT INTO certLog VALUES SELECT ACTION_CERT_EXPIRE, $date,
103  certID FROM certificates WHERE validTo <= $date" but this still doesn't
104  fix the problem with the duplicate IDs. In fact there isn't really a
105  certID present since it's an implicit action, but we can't make the
106  certID column null since it's not possible to index nullable columns.
107  As a result the only way that we can do it is to repetitively perform
108  "SELECT certID FROM certificates WHERE validTo <= $date" (or the
109  equivalent cleanup select) and for each time it succeeds follow it with:
110 
111  INSERT INTO certLog VALUES
112  (ACTION_EXPIRE_CERT, $date, <nonce>, NULL, <certID>);
113  DELETE FROM certificates WHERE certID = <certID>
114 
115  or
116 
117  INSERT INTO certLog VALUES
118  (ACTION_RESTART_CLEANUP, $date, <nonce>, NULL, <certID>);
119  DELETE FROM certRequests WHERE certID = <certID>
120 
121  This has the unfortunate side-effect that the update isn't atomic, we
122  could enforce this with "LOCK TABLE <name> IN EXCLUSIVE MODE" but the MS
123  databases don't support this and either require the use of baroque
124  mechanisms such as a "(TABLOCKX HOLDLOCK)" as a locking hint after the
125  table name in the first statement after the transaction is begun or
126  don't support this type of locking at all. Because of this it isn't
127  really possible to make the update atomic, in particular for the cleanup
128  operation we rely on the caller to perform it at startup before anyone
129  else accesses the certificate store. The fact that the update isn't
130  quite atomic isn't really a major problem, at worst it'll result in
131  either an expired certificate being visible or a leftover request
132  blocking a new request for a split second longer than they should.
133 
134  An additional feature that we could make use of for CA operations is the
135  use of foreign keys to ensure referential integrity, usually via entries
136  in the certificate log.
137 
138  It's not clear though what the primary key (or keys in different tables)
139  should be. We can't use certificates.certID as a foreign key for CRLs
140  because a general-purpose certificate keyset (rather than specifically a
141  CA certificate store) can store any CRLs and not just ones for
142  certificates in the keyset. In addition we want to return the CRL entry
143  even if the certificate is deleted (in fact the creation of the CRL
144  entry implies the deletion of the corresponding certificate entry). The
145  certificate store isn't something like a customer database where
146  everything is tied to a unique customer ID but more a transactional
147  system where an object appearing in one table requires the deletion of a
148  corresponding object in another table (e.g. certificate request ->
149  certificate, revocation request -> CRL + certificate deletion) which
150  can't be easily handled with something as simple as a foreign key but
151  has to be done with transactions.
152 
153  We could in theory require that all certificate requests be authorised
154  by adding an authCertID column to the certReq table and constraining it
155  with:
156 
157  FOREIGN KEY (authCertID) REFERENCES certLog.reqCertID
158 
159  but even then (apart from the overhead of adding extra indexed columns
160  just to ensure referential integrity) the syntax for this varies
161  somewhat between vendors so that it'd require assorted rewriting by the
162  back-end glue code to handle the different requirements for each
163  database type. In addition since the foreign key constraint is
164  specified at table create time we could experience strange failures on
165  table creation requiring special-purpose workarounds where we remove the
166  foreign-key constraint in the hope that the table create then succeeds.
167 
168  An easier way to handle this is via manual references to entries in the
169  certificate log. Since this is append-only, a manual presence check can
170  never return an incorrect result (an entry can't be removed between time
171  of check and time of use) so this provides the same result as using
172  referential integrity mechanisms.
173 
174  Another database feature that we could use is database triggers as a
175  backup for the access control settings. For example (using one
176  particular SQL dialect) we could say:
177 
178  CREATE TRIGGER checkLog ON certLog FOR UPDATE, DELETE AS
179  BEGIN
180  ROLLBACK
181  END
182 
183  However as the "dialect" reference in the above comment implies this
184  process is *extremely* back-end specific (far more so than access
185  controls and the use of foreign keys) so we can't really do much here
186  without ending up having to set different triggers for each back-end
187  type and even back-end version.
188 
189  A final feature that we could in theory use is the ability to impose
190  uniqueness constraints based on more than one column, for example the
191  certLog 'action' and 'certID' could be handled using a constraint of:
192 
193  UNIQUE (action, certID)
194 
195  however this isn't desirable for all combinations of (action, certID)
196  but only some, so that just the certID by itself must be unique in most
197  cases except for one or two where it's sufficient that (action, certID)
198  be unique. Because of this, as with foreign keys, we need to enforce
199  the constraint via transactions, which gives us more control over how
200  things are handled */
201 
202 /****************************************************************************
203 * *
204 * Utility Routines *
205 * *
206 ****************************************************************************/
207 
208 /* Set up key ID information for a query. There are two variations of
209  this, makeKeyID() encodes an existing keyID value and getKeyID() reads an
210  attribute from an object and encodes it using makeKeyID() */
211 
212 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3, 5 ) ) \
213 int makeKeyID( OUT_BUFFER( keyIdMaxLen, *keyIdLen ) char *keyID,
214  IN_LENGTH_SHORT_MIN( 16 ) const int keyIdMaxLen,
217  IN_BUFFER( idValueLength ) const void *idValue,
218  IN_LENGTH_SHORT const int idValueLength )
219  {
220  BYTE hashBuffer[ CRYPT_MAX_HASHSIZE + 8 ];
221  int idLength = idValueLength, status;
222 
223  assert( isWritePtr( keyID, keyIdMaxLen ) );
224  assert( isWritePtr( keyIdLen, sizeof( int ) ) );
225  assert( isReadPtr( keyID, idValueLength ) );
226 
227  REQUIRES( keyIdMaxLen >= 16 && keyIdMaxLen < MAX_INTLENGTH_SHORT );
228  REQUIRES( iDtype == CRYPT_KEYID_NAME || \
229  iDtype == CRYPT_KEYID_URI || \
230  iDtype == CRYPT_IKEYID_KEYID || \
231  iDtype == CRYPT_IKEYID_ISSUERID || \
232  iDtype == CRYPT_IKEYID_CERTID );
233  REQUIRES( idValueLength > 0 && idValueLength < MAX_INTLENGTH_SHORT );
234 
235  /* Clear return values */
236  memset( keyID, 0, min( 16, keyIdMaxLen ) );
237  *keyIdLen = 0;
238 
239  /* Name and email address are used as is */
240  if( iDtype == CRYPT_KEYID_NAME || iDtype == CRYPT_KEYID_URI )
241  {
242  if( idLength > CRYPT_MAX_TEXTSIZE * 2 )
243  {
244  /* Truncate to the database column size */
245  idLength = CRYPT_MAX_TEXTSIZE * 2;
246  }
247  if( idLength > keyIdMaxLen )
248  {
249  /* Truncate to the output buffer size */
250  idLength = keyIdMaxLen;
251  }
252  memcpy( keyID, idValue, idLength );
253  if( iDtype == CRYPT_KEYID_URI )
254  {
255  int i;
256 
257  /* Force the search URI to lowercase to make case-insensitive
258  matching easier. In most cases we could ask the back-end to
259  do this but this complicates indexing and there's no reason
260  why we can't do it here */
261  for( i = 0; i < idLength; i++ )
262  keyID[ i ] = intToByte( toLower( keyID[ i ] ) );
263  }
264  *keyIdLen = idLength;
265 
266  return( CRYPT_OK );
267  }
268 
269  /* A keyID is just a subjectKeyIdentifier, which is already supposed to
270  be an SHA-1 hash but which in practice can be almost anything so we
271  always hash it to a fixed-length value */
272  if( iDtype == CRYPT_IKEYID_KEYID )
273  {
274  HASHFUNCTION_ATOMIC hashFunctionAtomic;
275 
276  /* Get the hash algorithm information and hash the keyID to get
277  the fixed-length keyID */
278  getHashAtomicParameters( CRYPT_ALGO_SHA1, 0, &hashFunctionAtomic,
279  NULL );
281  idValue, idValueLength );
282  idValue = hashBuffer;
283  idLength = DBXKEYID_SIZE;
284  }
285 
286  ENSURES( idLength >= DBXKEYID_SIZE && idLength <= keyIdMaxLen );
287 
288  /* base64-encode the key ID so that we can use it with database queries.
289  Since we only store 128 bits of a (usually 160 bit) ID to save space
290  (particularly where it's used in indices) and to speed lookups, this
291  encoding step has the side-effect of truncating the ID down to the
292  correct size */
293  status = base64encode( keyID, keyIdMaxLen, keyIdLen, idValue,
295  if( cryptStatusError( status ) )
296  retIntError();
297  ENSURES( *keyIdLen == ENCODED_DBXKEYID_SIZE );
298 
299  return( CRYPT_OK );
300  }
301 
302 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3 ) ) \
303 int getKeyID( OUT_BUFFER( keyIdMaxLen, *keyIdLen ) char *keyID,
304  IN_LENGTH_SHORT_MIN( 16 ) const int keyIdMaxLen,
305  OUT_LENGTH_SHORT_Z int *keyIdLen,
308  {
309  BYTE hashBuffer[ CRYPT_MAX_HASHSIZE + 8 ];
310  int status;
311 
312  assert( isWritePtr( keyID, keyIdMaxLen ) );
313  assert( isWritePtr( keyIdLen, sizeof( int ) ) );
314 
315  REQUIRES( keyIdMaxLen >= 16 && keyIdMaxLen < MAX_INTLENGTH_SHORT );
316  REQUIRES( isHandleRangeValid( iCryptHandle ) );
317  REQUIRES( ( keyIDtype == CRYPT_CERTINFO_FINGERPRINT_SHA1 || \
318  keyIDtype == CRYPT_IATTRIBUTE_AUTHCERTID ) || \
319  ( keyIDtype == CRYPT_CERTINFO_SUBJECTKEYIDENTIFIER || \
320  keyIDtype == CRYPT_IATTRIBUTE_ISSUER || \
321  keyIDtype == CRYPT_IATTRIBUTE_SUBJECT || \
322  keyIDtype == CRYPT_IATTRIBUTE_ISSUERANDSERIALNUMBER || \
323  keyIDtype == CRYPT_IATTRIBUTE_SPKI ) );
324 
325  /* Clear return values */
326  memset( keyID, 0, min( 16, keyIdMaxLen ) );
327  *keyIdLen = 0;
328 
329  /* Get the attribute from the certificate and hash it, unless it's
330  already a hash */
331  if( keyIDtype == CRYPT_CERTINFO_FINGERPRINT_SHA1 || \
332  keyIDtype == CRYPT_IATTRIBUTE_AUTHCERTID )
333  {
335 
336  setMessageData( &msgData, hashBuffer, CRYPT_MAX_HASHSIZE );
337  status = krnlSendMessage( iCryptHandle, IMESSAGE_GETATTRIBUTE_S,
338  &msgData, keyIDtype );
339  if( cryptStatusError( status ) )
340  return( status );
341  ENSURES( msgData.length == KEYID_SIZE );
342  }
343  else
344  {
345  DYNBUF idDB;
346  HASHFUNCTION_ATOMIC hashFunctionAtomic;
347  int hashSize;
348 
349  /* Get the attribute data and hash it to get the ID */
350  status = dynCreate( &idDB, iCryptHandle, keyIDtype );
351  if( cryptStatusError( status ) )
352  return( status );
353  getHashAtomicParameters( CRYPT_ALGO_SHA1, 0, &hashFunctionAtomic,
354  &hashSize );
356  dynData( idDB ), dynLength( idDB ) );
357  ENSURES( hashSize == KEYID_SIZE );
358  dynDestroy( &idDB );
359  }
360 
361  return( makeKeyID( keyID, keyIdMaxLen, keyIdLen, CRYPT_IKEYID_CERTID,
362  hashBuffer, KEYID_SIZE ) );
363  }
364 
365 /* Get a certID for a certificate */
366 
367 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3 ) ) \
368 int getCertKeyID( OUT_BUFFER( keyIdMaxLen, *keyIdLen ) char *keyID,
369  IN_LENGTH_SHORT_MIN( 16 ) const int keyIdMaxLen,
370  OUT_LENGTH_SHORT_Z int *keyIdLen,
372  {
373  int status;
374 
375  assert( isWritePtr( keyID, keyIdMaxLen ) );
376  assert( isWritePtr( keyIdLen, sizeof( int ) ) );
377 
378  REQUIRES( keyIdMaxLen >= 16 && keyIdMaxLen < MAX_INTLENGTH_SHORT );
379  REQUIRES( isHandleRangeValid( iCryptCert ) );
380 
381  /* Clear return values */
382  memset( keyID, 0, min( 16, keyIdMaxLen ) );
383  *keyIdLen = 0;
384 
385  /* Certificate keyID handling isn't quite as simple as just reading an
386  attribute from the certificate since the subjectKeyIdentifier (if
387  present) may not be the same as the keyID if the certificate has come
388  from a CA that does strange things with the sKID. To resolve this we
389  try and build the key ID from the sKID, if this isn't present we use
390  the keyID (the sKID may have a nonstandard length since it's possible
391  to stuff anything in there, getKeyID() will hash it to the standard
392  size if the length is wrong) */
393  status = getKeyID( keyID, keyIdMaxLen, keyIdLen, iCryptCert,
395  if( cryptStatusOK( status ) )
396  return( status );
397 
398  /* There's no subjectKeyIdentifier, use the keyID. Note that we can't
399  just read the CRYPT_IATTRIBUTE_KEYID attribute directly since this
400  may be a data-only certificate (either a standalone certificate or
401  one from the middle of a chain) so we have to generate it indirectly
402  by hashing the SubjectPublicKeyInfo, which is equivalent to the keyID
403  and is always present in a certificate */
404  return( getKeyID( keyID, keyIdMaxLen, keyIdLen, iCryptCert,
405  CRYPT_IATTRIBUTE_SPKI ) );
406  }
407 
408 /* Extract certificate data from a certificate object. Note that the
409  formatType is given as an int rather than an enumerated type because
410  it can be either a CRYPT_CERTFORMAT_TYPE or a CRYPT_ATTRIBUTE_TYPE */
411 
412 CHECK_RETVAL STDC_NONNULL_ARG( ( 3, 5 ) ) \
413 int extractCertData( IN_HANDLE const CRYPT_CERTIFICATE iCryptCert,
414  IN_INT const int formatType,
415  OUT_BUFFER( certDataMaxLength, *certDataLength ) \
416  void *certDataBuffer,
418  const int certDataMaxLength,
420  {
422  int status;
423 
424  assert( isWritePtr( certDataBuffer, certDataMaxLength ) );
425  assert( isWritePtr( certDataLength, sizeof( int ) ) );
426 
427  REQUIRES( isHandleRangeValid( iCryptCert ) );
428  REQUIRES( formatType == CRYPT_CERTFORMAT_CERTIFICATE || \
429  formatType == CRYPT_ICERTFORMAT_DATA || \
430  formatType == CRYPT_IATTRIBUTE_CRLENTRY );
431  REQUIRES( certDataMaxLength >= MIN_CRYPT_OBJECTSIZE && \
432  certDataMaxLength < MAX_INTLENGTH_SHORT );
433 
434  /* Make sure that there's no collision in format type values (although
435  the switch() statement will also catch this by producing a compile
436  error */
437  static_assert( CRYPT_CERTFORMAT_CERTIFICATE != CRYPT_ICERTFORMAT_DATA && \
438  CRYPT_CERTFORMAT_CERTIFICATE != CRYPT_IATTRIBUTE_CRLENTRY, \
439  "Format type collision" );
440 
441  /* Clear return values */
442  memset( certDataBuffer, 0, min( 16, certDataMaxLength ) );
443  *certDataLength = 0;
444 
445  /* Extract the certificate object data */
446  setMessageData( &msgData, certDataBuffer, certDataMaxLength );
447  switch( formatType )
448  {
450  case CRYPT_ICERTFORMAT_DATA:
451  status = krnlSendMessage( iCryptCert, IMESSAGE_CRT_EXPORT,
452  &msgData, formatType );
453  break;
454 
455  case CRYPT_IATTRIBUTE_CRLENTRY:
456  status = krnlSendMessage( iCryptCert, IMESSAGE_GETATTRIBUTE_S,
457  &msgData, formatType );
458  break;
459 
460  default:
461  retIntError();
462  }
463  if( cryptStatusError( status ) )
464  return( status );
465  *certDataLength = msgData.length;
466 
467  return( CRYPT_OK );
468  }
469 
470 /* Some internal actions set extended error codes as a result of their
471  operation that the user shouldn't really see. For example performing a
472  certificate cleanup will return a no-data-found error once the last
473  certificate is reached, which will be read by the user the next time that
474  they read the CRYPT_ATTRIBUTE_INT_ERRORCODE/CRYPT_ATTRIBUTE_INT_ERRORMESSAGE
475  even though the error came from a previous internal operation. To avoid
476  this problem we clean up the error status information when it's been set
477  by an internal operation */
478 
479 RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
480 int resetErrorInfo( INOUT DBMS_INFO *dbmsInfo )
481  {
482  DBMS_STATE_INFO *dbmsStateInfo = dbmsInfo->stateInfo;
483  ERROR_INFO *errorInfo = &dbmsStateInfo->errorInfo;
484 
485  assert( isWritePtr( dbmsInfo, sizeof( DBMS_INFO ) ) );
486 
487  memset( errorInfo, 0, sizeof( ERROR_INFO ) );
488 
489  return( CRYPT_OK );
490  }
491 
492 /* Get names for various items */
493 
494 CHECK_RETVAL_PTR \
495 char *getKeyName( IN_KEYID const CRYPT_KEYID_TYPE keyIDtype )
496  {
497  REQUIRES_N( keyIDtype > CRYPT_KEYID_NONE && \
498  keyIDtype <= CRYPT_KEYID_LAST );
499  /* CRYPT_KEYID_LAST is mapped to the database-use-only
500  lookup value "nameID" */
501 
502  switch( keyIDtype )
503  {
504  case CRYPT_KEYID_NAME:
505  return( "CN" );
506 
507  case CRYPT_KEYID_URI:
508  return( "email" );
509 
510  case CRYPT_IKEYID_KEYID:
511  return( "keyID" );
512 
513  case CRYPT_IKEYID_ISSUERID:
514  return( "issuerID" );
515 
516  case CRYPT_IKEYID_CERTID:
517  return( "certID" );
518 
519  case CRYPT_KEYID_LAST:
520  return( "nameID" );
521  }
522 
524  }
525 
526 /****************************************************************************
527 * *
528 * Database Access Functions *
529 * *
530 ****************************************************************************/
531 
532 /* Create a new key database */
533 
534 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3 ) ) \
535 static int createDatabase( INOUT DBMS_INFO *dbmsInfo,
536  const BOOLEAN hasPermissions,
537  INOUT ERROR_INFO *errorInfo )
538  {
539  int updateProgress = 0, status;
540 
541  assert( isWritePtr( dbmsInfo, sizeof( DBMS_INFO ) ) );
542 
543  REQUIRES( errorInfo != NULL );
544 
545  /* Create tables for certificates, CRLs, certificate requests, PKI
546  users, and CA logs. We use CHAR rather than VARCHAR for the ID
547  fields since these always have a fixed length and CHAR is faster than
548  VARCHAR. In addition we make as many columns as possible NOT NULL
549  since these fields should always be present and because this is
550  faster for most databases. The BLOB type is nonstandard, this is
551  rewritten by the database interface layer to the type which is
552  appropriate for the database */
553  status = dbmsStaticUpdate(
554  "CREATE TABLE certificates ("
555  "C CHAR(2), "
556  "SP VARCHAR(64), "
557  "L VARCHAR(64), "
558  "O VARCHAR(64), "
559  "OU VARCHAR(64), "
560  "CN VARCHAR(64), "
561  "email VARCHAR(64), "
562  "validTo DATETIME NOT NULL, "
563  "nameID CHAR(" TEXT_DBXKEYID_SIZE ") NOT NULL, "
564  "issuerID CHAR(" TEXT_DBXKEYID_SIZE ") NOT NULL, "
565  "keyID CHAR(" TEXT_DBXKEYID_SIZE ") NOT NULL, "
566  "certID CHAR(" TEXT_DBXKEYID_SIZE ") NOT NULL, "
567  "certData BLOB NOT NULL)" );
568  if( cryptStatusError( status ) )
569  {
570  retExtErr( status,
571  ( status, errorInfo, getDbmsErrorInfo( dbmsInfo ),
572  "Couldn't create certificate database: " ) );
573  }
574  if( isCertStore( dbmsInfo ) )
575  {
576  /* The certificate store contains in addition to the other CRL
577  fields the certificate expiry time which is used to remove the
578  entry from the CRL table once the certificate has expired anyway,
579  the nameID which is used to force clustering of entries for each
580  CA, and the ID of the certificate being revoked, which isn't
581  available if we're creating it from a raw CRL */
582  status = dbmsStaticUpdate(
583  "CREATE TABLE CRLs ("
584  "expiryDate DATETIME NOT NULL, "
585  "nameID CHAR(" TEXT_DBXKEYID_SIZE ") NOT NULL, "
586  "issuerID CHAR(" TEXT_DBXKEYID_SIZE ") NOT NULL,"
587  "certID CHAR(" TEXT_DBXKEYID_SIZE ") NOT NULL, "
588  "certData BLOB NOT NULL)" );
589  if( cryptStatusOK( status ) )
590  {
591  updateProgress++;
592  status = dbmsStaticUpdate(
593  "CREATE TABLE pkiUsers ("
594  "C CHAR(2), "
595  "SP VARCHAR(64), "
596  "L VARCHAR(64), "
597  "O VARCHAR(64), "
598  "OU VARCHAR(64), "
599  "CN VARCHAR(64), "
600  "nameID CHAR(" TEXT_DBXKEYID_SIZE ") NOT NULL, "
601  "keyID CHAR(" TEXT_DBXKEYID_SIZE ") NOT NULL, "
602  "certID CHAR(" TEXT_DBXKEYID_SIZE ") NOT NULL, "
603  "certData BLOB NOT NULL)" );
604  }
605  if( cryptStatusOK( status ) )
606  {
607  updateProgress++;
608  status = dbmsStaticUpdate(
609  "CREATE TABLE certRequests ("
610  "type SMALLINT NOT NULL, "
611  "C CHAR(2), "
612  "SP VARCHAR(64), "
613  "L VARCHAR(64), "
614  "O VARCHAR(64), "
615  "OU VARCHAR(64), "
616  "CN VARCHAR(64), "
617  "email VARCHAR(64), "
618  "certID CHAR(" TEXT_DBXKEYID_SIZE ") NOT NULL, "
619  "certData BLOB NOT NULL)" );
620  }
621  if( cryptStatusOK( status ) )
622  {
623  updateProgress++;
624  status = dbmsStaticUpdate(
625  "CREATE TABLE certLog ("
626  "action SMALLINT NOT NULL, "
627  "actionTime DATETIME NOT NULL, "
628  "certID CHAR(" TEXT_DBXKEYID_SIZE ") NOT NULL, "
629  "reqCertID CHAR(" TEXT_DBXKEYID_SIZE "), "
630  "subjCertID CHAR(" TEXT_DBXKEYID_SIZE "), "
631  "certData BLOB)" );
632  }
633  }
634  else
635  {
636  status = dbmsStaticUpdate(
637  "CREATE TABLE CRLs ("
638  "issuerID CHAR(" TEXT_DBXKEYID_SIZE ") NOT NULL,"
639  "certData BLOB NOT NULL)" );
640  }
641  if( cryptStatusError( status ) )
642  {
643  /* Undo the previous table creations */
644  dbmsStaticUpdate( "DROP TABLE certificates" );
645  if( updateProgress > 0 )
646  dbmsStaticUpdate( "DROP TABLE CRLs" );
647  if( updateProgress > 1 )
648  dbmsStaticUpdate( "DROP TABLE pkiUsers" );
649  if( updateProgress > 2 )
650  dbmsStaticUpdate( "DROP TABLE certRequests" );
651  retExtErr( status,
652  ( status, errorInfo, getDbmsErrorInfo( dbmsInfo ),
653  "Couldn't create certificate%s database: ",
654  isCertStore( dbmsInfo ) ? " store" : "" ) );
655  }
656 
657  /* Create an index for the email address, nameID, issuerID, keyID, and
658  certID in the certificates table, the issuerID and certID in the CRLs
659  table (the CRL nameID isn't indexed since we only use it for linear
660  scans), the nameID and keyID in the PKI users table (the former isn't
661  used but is made a UNIQUE INDEX to ensure that the same entry can't
662  be added more than once) and the certID in the certificate log (this
663  also isn't used but is made a UNIQUE INDEX to ensure that the same
664  entry can't be added more than once). In theory we could force the
665  UNIQUE constraint on the column rather than indirectly via an index
666  but some databases will quietly create a unique index anyway in order
667  to do that so we make the creation of the index explicit rather than
668  leaving it as a maybe/maybe not option for the database. + CRLF
669 
670  We have to give these unique names since some databases don't allow
671  two indexes to have the same name even if they're in a different
672  table. Since most of the fields in the tables are supposed to be
673  unique we can specify this for the indexes that we're creating,
674  however we can't do it for the email address or the nameID in the
675  certificates table since there could be multiple certificates present
676  that differ only in key usage. We don't index the other tables since
677  indexes consume space and we don't expect to access any of these
678  much */
679  status = dbmsStaticUpdate(
680  "CREATE INDEX emailIdx ON certificates(email)" );
681  if( cryptStatusOK( status ) )
682  status = dbmsStaticUpdate(
683  "CREATE INDEX nameIDIdx ON certificates(nameID)" );
684  if( cryptStatusOK( status ) )
685  status = dbmsStaticUpdate(
686  "CREATE UNIQUE INDEX issuerIDIdx ON certificates(issuerID)" );
687  if( cryptStatusOK( status ) )
688  status = dbmsStaticUpdate(
689  "CREATE UNIQUE INDEX keyIDIdx ON certificates(keyID)" );
690  if( cryptStatusOK( status ) )
691  status = dbmsStaticUpdate(
692  "CREATE UNIQUE INDEX certIDIdx ON certificates(certID)" );
693  if( cryptStatusOK( status ) )
694  status = dbmsStaticUpdate(
695  "CREATE UNIQUE INDEX crlIssuerIDIdx ON CRLs (issuerID)" );
696  if( cryptStatusOK( status ) && isCertStore( dbmsInfo ) )
697  status = dbmsStaticUpdate(
698  "CREATE UNIQUE INDEX crlCertIDIdx ON CRLs (certID)" );
699  if( cryptStatusOK( status ) && isCertStore( dbmsInfo ) )
700  status = dbmsStaticUpdate(
701  "CREATE UNIQUE INDEX userKeyIDIdx ON pkiUsers (keyID)" );
702  if( cryptStatusOK( status ) && isCertStore( dbmsInfo ) )
703  status = dbmsStaticUpdate(
704  "CREATE UNIQUE INDEX userNameIDIdx ON pkiUsers (nameID)" );
705  if( cryptStatusOK( status ) && isCertStore( dbmsInfo ) )
706  status = dbmsStaticUpdate(
707  "CREATE UNIQUE INDEX logCertIDIdx ON certLog (certID)" );
708  if( cryptStatusOK( status ) && isCertStore( dbmsInfo ) )
709  {
710  char dummyCertID[ ENCODED_DBXKEYID_SIZE + 8 ];
711  const int dummyCertIDlength = ENCODED_DBXKEYID_SIZE;
712 
713  /* Create a special dummy certID with an out-of-band value to mark
714  the first entry in the log */
715  memset( dummyCertID, '-', ENCODED_DBXKEYID_SIZE );
716 
717  /* Add the initial log entry recording the creation of the log */
718  status = updateCertLog( dbmsInfo, CRYPT_CERTACTION_CREATE,
719  dummyCertID, dummyCertIDlength,
720  NULL, 0, NULL, 0, NULL, 0,
722  }
723  if( cryptStatusError( status ) )
724  {
725  /* Undo the creation of the various tables */
726  dbmsStaticUpdate( "DROP TABLE certificates" );
727  dbmsStaticUpdate( "DROP TABLE CRLs" );
728  if( isCertStore( dbmsInfo ) )
729  {
730  dbmsStaticUpdate( "DROP TABLE pkiUsers" );
731  dbmsStaticUpdate( "DROP TABLE certRequests" );
732  dbmsStaticUpdate( "DROP TABLE certLog" );
733  }
735  ( CRYPT_ERROR_WRITE, errorInfo, getDbmsErrorInfo( dbmsInfo ),
736  "Couldn't create indexes for certificate%s database: ",
737  isCertStore( dbmsInfo ) ? " store" : "" ) );
738  }
739 
740  /* If the back-end doesn't support access permissions (generally only
741  toy ones like Access and Paradox) or it's not a CA certificate
742  store, we're done */
743  if( !hasPermissions || !isCertStore( dbmsInfo ) )
744  return( CRYPT_OK );
745 
746  /* Set access controls for the certificate store tables:
747 
748  Users CAs
749  certRequests: - INS,SEL,DEL
750  certificates: SEL INS,SEL,DEL
751  CRLs: - INS,SEL,DEL
752  pkiUsers: - INS,SEL,DEL
753  certLog: - INS,SEL
754 
755  Once role-based access controls are enabled we can allow only the CA
756  user to update the certstore tables and allow others only read access
757  to the certificates table. In addition the revocation should be
758  phrased as REVOKE ALL, GRANT <permitted> rather than revoking specific
759  privileges since each database vendor has their own nonstandard
760  additional privileges that a specific revoke won't cover.
761  Unfortunately configuring this will be somewhat difficult since it
762  requires that cryptlib users create database user roles, which in turn
763  requires that they read the manual */
764 #if 1
765  dbmsStaticUpdate( "REVOKE UPDATE ON certificates FROM PUBLIC" );
766  dbmsStaticUpdate( "REVOKE UPDATE ON CRLs FROM PUBLIC" );
767  dbmsStaticUpdate( "REVOKE UPDATE ON pkiUsers FROM PUBLIC" );
768  dbmsStaticUpdate( "REVOKE UPDATE ON certRequests FROM PUBLIC" );
769  dbmsStaticUpdate( "REVOKE DELETE,UPDATE ON certLog FROM PUBLIC" );
770 #else
771  dbmsStaticUpdate( "REVOKE ALL ON certificates FROM PUBLIC" );
772  dbmsStaticUpdate( "GRANT INSERT,SELECT,DELETE ON certificates TO ca" );
773  dbmsStaticUpdate( "GRANT SELECT ON certificates TO PUBLIC" );
774  dbmsStaticUpdate( "REVOKE ALL ON CRLs FROM PUBLIC" );
775  dbmsStaticUpdate( "GRANT INSERT,SELECT,DELETE ON CRLs TO ca" );
776  dbmsStaticUpdate( "REVOKE ALL ON pkiUsers FROM PUBLIC" );
777  dbmsStaticUpdate( "GRANT INSERT,SELECT,DELETE ON pkiUsers TO ca" );
778  dbmsStaticUpdate( "REVOKE ALL ON certRequests FROM PUBLIC" );
779  dbmsStaticUpdate( "GRANT INSERT,SELECT,DELETE ON certRequests TO ca" );
780  dbmsStaticUpdate( "REVOKE ALL ON certLog FROM PUBLIC" );
781  dbmsStaticUpdate( "GRANT INSERT,SELECT ON certLog TO ca" );
782 #endif /* 1 */
783 
784  return( CRYPT_OK );
785  }
786 
787 /* Return status information for the keyset */
788 
790 static BOOLEAN isBusyFunction( KEYSET_INFO *keysetInfoPtr )
791  {
792  assert( isWritePtr( keysetInfoPtr, sizeof( KEYSET_INFO ) ) );
793 
794  return( ( keysetInfoPtr->keysetDBMS->flags & \
796  TRUE : FALSE );
797  }
798 
799 /* Open a connection to a database */
800 
801 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
802 static int initFunction( INOUT KEYSET_INFO *keysetInfoPtr,
803  IN_BUFFER( nameLen ) const char *name,
804  IN_LENGTH_NAME const int nameLen,
805  IN_ENUM( CRYPT_KEYOPT ) const CRYPT_KEYOPT_TYPE options )
806  {
807  DBMS_INFO *dbmsInfo = keysetInfoPtr->keysetDBMS;
808  DBMS_NAME_INFO nameInfo;
809  int featureFlags, status;
810 
811  assert( isWritePtr( keysetInfoPtr, sizeof( KEYSET_INFO ) ) );
812  assert( isReadPtr( name, nameLen ) );
813 
814  REQUIRES( nameLen >= MIN_NAME_LENGTH && nameLen < MAX_ATTRIBUTE_SIZE );
815  REQUIRES( options >= CRYPT_KEYOPT_NONE && options < CRYPT_KEYOPT_LAST );
816 
817  /* Parse the data source name. The parsed form isn't needed at this
818  point but it allows us to return a more meaningful error response */
819  status = dbmsParseName( &nameInfo, name, nameLen );
820  if( cryptStatusError( status ) )
821  retExt( status,
822  ( status, KEYSET_ERRINFO, "Invalid database name" ) );
823 
824  /* Perform a database back-end specific open */
825  status = dbmsOpen( name, nameLen,
826  ( options == CRYPT_KEYOPT_READONLY ) ? \
827  options : CRYPT_KEYOPT_NONE, &featureFlags );
828  if( cryptStatusError( status ) )
829  {
830  endDbxSession( keysetInfoPtr );
831  return( status );
832  }
833 
834  /* If the back-end is read-only (which would be extremely unusual,
835  usually related to misconfigured DBMS access permissions) and we're
836  not opening it in read-only mode, signal an error */
837  if( ( featureFlags & DBMS_FEATURE_FLAG_READONLY ) && \
838  options != CRYPT_KEYOPT_READONLY )
839  {
840  endDbxSession( keysetInfoPtr );
843  "Certificate database can only be accessed in read-only "
844  "mode" ) );
845  }
846 
847  /* If we're being asked to create a new database, create it and exit */
848  if( options == CRYPT_KEYOPT_CREATE )
849  {
850  status = createDatabase( dbmsInfo,
851  ( featureFlags & DBMS_FEATURE_FLAG_PRIVILEGES ) ? \
853  if( cryptStatusOK( status ) && isCertStore( dbmsInfo ) )
854  status = updateCertLog( dbmsInfo, CRYPT_CERTACTION_CONNECT,
855  NULL, 0, NULL, 0, NULL, 0, NULL, 0,
857  if( cryptStatusError( status ) )
858  {
859  dbmsClose();
860  endDbxSession( keysetInfoPtr );
861  }
862  return( status );
863  }
864 
865  /* Check to see whether it's a certificate store. We do this by
866  checking for the presence of the certificate store creation entry in
867  the log, this is always present with an action value of
868  CRYPT_CERTACTION_CREATE */
869  status = dbmsStaticQuery(
870  "SELECT certData FROM certLog WHERE action = "
873  if( cryptStatusOK( status ) )
874  {
875  /* It's a certificate store, if we're opening it as a non-certificate
876  store it has to be in read-only mode. We return an error rather
877  than quietly changing the access mode to read-only both to make it
878  explicit to the user at open time that they can't make changes
879  and because we need to have the read-only flag set when we open
880  the database to optimise the buffering and locking strategy,
881  setting it at this point is too late */
882  if( !isCertStore( dbmsInfo ) )
883  {
884  if( options != CRYPT_KEYOPT_READONLY )
885  {
886  dbmsClose();
887  endDbxSession( keysetInfoPtr );
890  "Certificate store can't be accessed as a normal "
891  "database except in read-only mode" ) );
892  }
893 
894  /* Remember that even though it's not functioning as a
895  certificate store we can still perform some extended queries
896  on it based on fields that are only present in certificate
897  stores */
898  dbmsInfo->flags |= DBMS_FLAG_CERTSTORE_FIELDS;
899 
900  return( CRYPT_OK );
901  }
902 
903  /* If this isn't a read-only open, record a connection to the
904  store */
905  if( options != CRYPT_KEYOPT_READONLY )
906  {
907  status = updateCertLog( dbmsInfo, CRYPT_CERTACTION_CONNECT,
908  NULL, 0, NULL, 0, NULL, 0, NULL, 0,
910  if( cryptStatusError( status ) )
911  {
912  /* This is a critical error, if we can't update the access
913  log at this point then it's not safe to use the
914  certificate store (if we fail during a general update
915  operation we give the caller the option of continuing
916  since the transaction itself will have been rolled back
917  so there's no permanent harm done) */
918  dbmsClose();
919  endDbxSession( keysetInfoPtr );
922  "Couldn't update certificate store log" ) );
923  }
924  }
925 
926  return( CRYPT_OK );
927  }
928 
929  /* It's not a certificate store, if the DBMS information indicates that
930  we're expecting to open it as one tell the caller */
931  if( isCertStore( dbmsInfo ) )
932  {
933  dbmsClose();
934  endDbxSession( keysetInfoPtr );
937  "Keyset isn't a certificate store" ) );
938  }
939 
940  /* Since the failure of the query above will set the extended error
941  information we have to explicitly clear it here to avoid making the
942  (invisible) query side-effects visible to the user */
943  resetErrorInfo( dbmsInfo );
944 
945  return( CRYPT_OK );
946  }
947 
948 /* Close the connection to a database */
949 
950 RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
951 static int shutdownFunction( INOUT KEYSET_INFO *keysetInfoPtr )
952  {
953  DBMS_INFO *dbmsInfo = keysetInfoPtr->keysetDBMS;
954 
955  assert( isWritePtr( keysetInfoPtr, sizeof( KEYSET_INFO ) ) );
956 
957  /* If it's a certificate store opened in read/write mode, record a
958  closed connection to the store */
959  if( isCertStore( dbmsInfo ) && \
960  keysetInfoPtr->options != CRYPT_KEYOPT_READONLY )
961  {
962  updateCertLog( dbmsInfo, CRYPT_CERTACTION_DISCONNECT,
963  NULL, 0, NULL, 0, NULL, 0, NULL, 0,
965  }
966 
967  /* If we're in the middle of a query, cancel it. We always use
968  DBMS_CACHEDQUERY_NONE because this is the only query type that can
969  remain active outside the keyset object */
970  if( dbmsInfo->flags & DBMS_FLAG_QUERYACTIVE )
972 
973  dbmsClose();
974  return( endDbxSession( keysetInfoPtr ) );
975  }
976 
977 /****************************************************************************
978 * *
979 * Database Access Routines *
980 * *
981 ****************************************************************************/
982 
983 /* Set up the function pointers to the keyset methods */
984 
986 int setAccessMethodDBMS( INOUT KEYSET_INFO *keysetInfoPtr,
988  {
989  int status;
990 
991  assert( isWritePtr( keysetInfoPtr, sizeof( KEYSET_INFO ) ) );
992 
994  "Cached query ID" );
995 
996  REQUIRES( type > CRYPT_KEYSET_NONE && type < CRYPT_KEYSET_LAST );
997 
998  /* Set up the lower-level interface functions */
999  status = initDbxSession( keysetInfoPtr, type );
1000  if( cryptStatusError( status ) )
1001  return( status );
1002 
1003  /* Set the access method pointers */
1004  keysetInfoPtr->initFunction = initFunction;
1005  keysetInfoPtr->shutdownFunction = shutdownFunction;
1006  status = initDBMSread( keysetInfoPtr );
1007  if( cryptStatusOK( status ) )
1008  status = initDBMSwrite( keysetInfoPtr );
1009  if( cryptStatusError( status ) )
1010  return( status );
1011  if( type == CRYPT_KEYSET_ODBC_STORE || \
1012  type == CRYPT_KEYSET_DATABASE_STORE )
1013  {
1014  status = initDBMSCA( keysetInfoPtr );
1015  if( cryptStatusError( status ) )
1016  return( status );
1017  }
1018  keysetInfoPtr->isBusyFunction = isBusyFunction;
1019 
1020  return( CRYPT_OK );
1021  }
1022 #endif /* USE_DBMS */