cryptlib  3.4.1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros
ca_add.c
Go to the documentation of this file.
1 /****************************************************************************
2 * *
3 * cryptlib DBMS CA Certificate Add 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 /****************************************************************************
21 * *
22 * Utility Functions *
23 * *
24 ****************************************************************************/
25 
26 /* Check that the request that we've been passed is in order */
27 
28 CHECK_RETVAL_BOOL \
30  IN_ENUM( CRYPT_CERTACTION ) \
31  const CRYPT_CERTACTION_TYPE action )
32  {
34  int certType, value, status;
35 
36  REQUIRES_B( isHandleRangeValid( iCertRequest ) );
38  action == CRYPT_CERTACTION_ISSUE_CERT || \
39  action == CRYPT_CERTACTION_REVOKE_CERT || \
40  action == CRYPT_CERTACTION_NONE );
41 
42  /* Make sure that the request type is consistent with the operation
43  being performed */
44  status = krnlSendMessage( iCertRequest, IMESSAGE_GETATTRIBUTE,
45  &certType, CRYPT_CERTINFO_CERTTYPE );
46  if( cryptStatusError( status ) )
47  return( FALSE );
48  switch( action )
49  {
52  if( certType != CRYPT_CERTTYPE_CERTREQUEST && \
53  certType != CRYPT_CERTTYPE_REQUEST_CERT )
54  return( FALSE );
55  break;
56 
58  if( certType != CRYPT_CERTTYPE_REQUEST_REVOCATION )
59  return( FALSE );
60  break;
61 
63  /* We're performing a straight add of a request to the store,
64  any request type is permitted */
65  break;
66 
67  default:
69  }
70 
71  /* Make sure that the request is completed and valid. We don't check
72  the signature on revocation requests since they aren't signed, and
73  have to be careful with CRMF requests which can be unsigned for
74  encryption-only keys */
75  status = krnlSendMessage( iCertRequest, IMESSAGE_GETATTRIBUTE,
76  &value, CRYPT_CERTINFO_IMMUTABLE );
77  if( cryptStatusError( status ) || !value )
78  return( FALSE );
79  switch( certType )
80  {
82  status = krnlSendMessage( iCertRequest, IMESSAGE_GETATTRIBUTE,
83  &value, CRYPT_CERTINFO_SELFSIGNED );
84  if( cryptStatusOK( status ) && !value )
85  {
86  /* It's an unsigned CRMF request, make sure that it really
87  is an encryption-only key */
88  status = krnlSendMessage( iCertRequest, IMESSAGE_GETATTRIBUTE,
89  &value, CRYPT_CERTINFO_KEYUSAGE );
90  if( cryptStatusOK( status ) && ( value & KEYUSAGE_SIGN ) )
91  return( FALSE );
92  break;
93  }
94 
95  /* Fall through */
96 
98  status = krnlSendMessage( iCertRequest, IMESSAGE_CRT_SIGCHECK,
99  NULL, CRYPT_UNUSED );
100  if( cryptStatusError( status ) )
101  return( FALSE );
102  break;
103 
105  /* Revocation requests are unsigned so we can't perform a
106  signature check on them */
107  break;
108 
109  default:
111  }
112 
113  /* Check that required parameters are present. This is necessary for
114  CRMF requests where every single parameter is optional, for our use
115  we require that a certificate request contains at least a subject DN
116  and public key and a revocation request contains at least an issuer
117  DN and serial number */
118  switch( certType )
119  {
122  setMessageData( &msgData, NULL, 0 );
123  status = krnlSendMessage( iCertRequest, IMESSAGE_GETATTRIBUTE_S,
124  &msgData, CRYPT_IATTRIBUTE_SUBJECT );
125  if( cryptStatusError( status ) )
126  return( FALSE );
127  setMessageData( &msgData, NULL, 0 );
128  status = krnlSendMessage( iCertRequest, IMESSAGE_GETATTRIBUTE_S,
129  &msgData, CRYPT_IATTRIBUTE_SPKI );
130  if( cryptStatusError( status ) )
131  return( FALSE );
132  break;
133 
135  setMessageData( &msgData, NULL, 0 );
136  status = krnlSendMessage( iCertRequest, IMESSAGE_GETATTRIBUTE_S,
137  &msgData,
138  CRYPT_IATTRIBUTE_ISSUERANDSERIALNUMBER );
139  if( cryptStatusError( status ) )
140  return( FALSE );
141  break;
142 
143  default:
145  }
146 
147  return( TRUE );
148  }
149 
150 /* Check that a revocation request is consistent with information held in the
151  certificate store */
152 
154 static int checkRevRequest( INOUT DBMS_INFO *dbmsInfo,
155  IN_HANDLE const CRYPT_CERTIFICATE iCertRequest )
156  {
157  BOUND_DATA boundData[ BOUND_DATA_MAXITEMS ], *boundDataPtr = boundData;
158  char certID[ ENCODED_DBXKEYID_SIZE + 8 ];
159  char issuerID[ ENCODED_DBXKEYID_SIZE + 8 ];
160  int certIDlength, issuerIDlength, status;
161 
162  assert( isWritePtr( dbmsInfo, sizeof( DBMS_INFO ) ) );
163 
164  REQUIRES( isHandleRangeValid( iCertRequest ) );
165 
166  /* Check that the certificate being referred to in the request is
167  present and active */
168  status = getKeyID( issuerID, ENCODED_DBXKEYID_SIZE, &issuerIDlength,
169  iCertRequest, CRYPT_IATTRIBUTE_ISSUERANDSERIALNUMBER );
170  if( cryptStatusOK( status ) )
171  {
172  initBoundData( boundDataPtr );
173  setBoundData( boundDataPtr, 0, issuerID, issuerIDlength );
174  status = dbmsQuery(
175  "SELECT certData FROM certificates WHERE issuerID = ?",
176  NULL, 0, NULL, boundDataPtr,
178  }
179  if( cryptStatusOK( status ) )
180  return( CRYPT_OK );
181 
182  /* The certificate isn't an active certificate, it's either not present
183  or not active, return an appropriate error code. If this request has
184  been entered into the certificate store log then it's a duplicate
185  request, otherwise it's a request to revoke a non-present certificate
186  (either that or something really obscure which is best reported as a
187  non-present certificate problem) */
188  status = getKeyID( certID, ENCODED_DBXKEYID_SIZE, &certIDlength,
189  iCertRequest, CRYPT_CERTINFO_FINGERPRINT_SHA1 );
190  if( cryptStatusOK( status ) )
191  {
192  initBoundData( boundDataPtr );
193  setBoundData( boundDataPtr, 0, certID, certIDlength );
194  status = dbmsQuery(
195  "SELECT certData FROM certLog WHERE certID = ?",
196  NULL, 0, NULL, boundDataPtr,
198  }
199  return( cryptStatusOK( status ) ? \
201  }
202 
203 /****************************************************************************
204 * *
205 * Certificate Add Functions *
206 * *
207 ****************************************************************************/
208 
209 /* Add a new PKI user to the certificate store */
210 
211 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3 ) ) \
212 int caAddPKIUser( INOUT DBMS_INFO *dbmsInfo,
215  {
216  BYTE certData[ MAX_CERT_SIZE + 8 ];
217  char certID[ ENCODED_DBXKEYID_SIZE + 8 ];
218  int certDataLength, certIDlength = DUMMY_INIT, status;
219 
220  assert( isWritePtr( dbmsInfo, sizeof( DBMS_INFO ) ) );
221 
222  REQUIRES( isHandleRangeValid( iPkiUser ) );
223  REQUIRES( errorInfo != NULL );
224 
225  /* Extract the information that we need from the PKI user object. In
226  addition to simply obtaining the information for logging purposes we
227  also need to perform this action to tell the certificate management
228  code to fill in the remainder of the (implicitly-added) user
229  information before we start querying fields as we add it to the
230  certificate store. Because of this we also need to place the certID
231  fetch after the object export since it's in an incomplete state
232  before this point */
233  status = extractCertData( iPkiUser, CRYPT_ICERTFORMAT_DATA,
234  certData, MAX_CERT_SIZE, &certDataLength );
235  if( cryptStatusOK( status ) )
236  status = getKeyID( certID, ENCODED_DBXKEYID_SIZE, &certIDlength,
238  if( cryptStatusError( status ) )
239  {
240  retExt( status,
241  ( status, errorInfo,
242  "Couldn't extract PKI user data to add to certificate "
243  "store" ) );
244  }
245 
246  /* Update the certificate store */
247  status = addCert( dbmsInfo, iPkiUser, CRYPT_CERTTYPE_PKIUSER,
248  CERTADD_NORMAL, DBMS_UPDATE_BEGIN, errorInfo );
249  if( cryptStatusOK( status ) )
250  {
251  status = updateCertLog( dbmsInfo, CRYPT_CERTACTION_ADDUSER,
252  certID, certIDlength, NULL, 0, NULL, 0,
253  certData, certDataLength,
255  }
256  else
257  {
258  /* Something went wrong, abort the transaction */
259  dbmsUpdate( NULL, NULL, DBMS_UPDATE_ABORT );
260  retExtErr( status,
261  ( status, errorInfo, getDbmsErrorInfo( dbmsInfo ),
262  "PKI user add operation failed: " ) );
263  }
264 
265  return( status );
266  }
267 
268 /* Delete a PKI user from the certificate store */
269 
270 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3, 5 ) ) \
271 int caDeletePKIUser( INOUT DBMS_INFO *dbmsInfo,
273  IN_BUFFER( keyIDlength ) const void *keyID,
274  IN_LENGTH_KEYID const int keyIDlength,
275  INOUT ERROR_INFO *errorInfo )
276  {
278  BOUND_DATA boundData[ BOUND_DATA_MAXITEMS ], *boundDataPtr = boundData;
279  char certID[ ENCODED_DBXKEYID_SIZE + 8 ];
280  int certIDlength = DUMMY_INIT, dummy, status;
281 
282  assert( isWritePtr( dbmsInfo, sizeof( DBMS_INFO ) ) );
283  assert( isReadPtr( keyID, keyIDlength ) );
284 
285  REQUIRES( keyIDtype == CRYPT_KEYID_NAME || \
286  keyIDtype == CRYPT_KEYID_URI );
287  REQUIRES( keyIDlength >= MIN_NAME_LENGTH && \
288  keyIDlength < MAX_ATTRIBUTE_SIZE );
289  REQUIRES( errorInfo != NULL );
290 
291  /* Get information on the PKI user that we're about to delete */
292  status = getItemData( dbmsInfo, &iPkiUser, &dummy,
293  KEYMGMT_ITEM_PKIUSER, keyIDtype,
294  keyID, keyIDlength, KEYMGMT_FLAG_NONE,
295  errorInfo );
296  if( cryptStatusOK( status ) )
297  {
298  status = getKeyID( certID, ENCODED_DBXKEYID_SIZE, &certIDlength,
301  }
302  if( cryptStatusError( status ) )
303  {
304  retExtErr( status,
305  ( status, errorInfo, getDbmsErrorInfo( dbmsInfo ),
306  "Couldn't get information on PKI user to be "
307  "deleted: " ) );
308  }
309 
310  /* Delete the PKI user information and record the deletion */
311  initBoundData( boundDataPtr );
312  setBoundData( boundDataPtr, 0, certID, certIDlength );
313  status = dbmsUpdate(
314  "DELETE FROM pkiUsers WHERE certID = ?",
315  boundDataPtr, DBMS_UPDATE_BEGIN );
316  if( cryptStatusOK( status ) )
317  status = updateCertLog( dbmsInfo, CRYPT_CERTACTION_DELETEUSER,
318  NULL, 0, NULL, 0, certID, certIDlength,
319  NULL, 0, DBMS_UPDATE_COMMIT );
320  else
321  {
322  /* Something went wrong, abort the transaction */
323  dbmsUpdate( NULL, NULL, DBMS_UPDATE_ABORT );
324  retExtErr( status,
325  ( status, errorInfo, getDbmsErrorInfo( dbmsInfo ),
326  "PKI user delete operation failed: " ) );
327  }
328 
329  return( status );
330  }
331 
332 /* Add a certificate issue or revocation request to the certificate store */
333 
334 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 6 ) ) \
335 int caAddCertRequest( INOUT DBMS_INFO *dbmsInfo,
336  IN_HANDLE const CRYPT_CERTIFICATE iCertRequest,
337  IN_ENUM( CRYPT_CERTTYPE ) \
338  const CRYPT_CERTTYPE_TYPE requestType,
339  const BOOLEAN isRenewal, const BOOLEAN isInitialOp,
340  INOUT ERROR_INFO *errorInfo )
341  {
342  BYTE certData[ MAX_CERT_SIZE + 8 ];
343  char certID[ ENCODED_DBXKEYID_SIZE + 8 ];
344  char reqCertID[ ENCODED_DBXKEYID_SIZE + 8 ], *reqCertIDptr = reqCertID;
346  int certDataLength = DUMMY_INIT, status;
347 
348  assert( isWritePtr( dbmsInfo, sizeof( DBMS_INFO ) ) );
349 
350  REQUIRES( isHandleRangeValid( iCertRequest ) );
351  REQUIRES( requestType == CRYPT_CERTTYPE_CERTREQUEST || \
352  requestType == CRYPT_CERTTYPE_REQUEST_CERT || \
353  requestType == CRYPT_CERTTYPE_REQUEST_REVOCATION );
354  REQUIRES( errorInfo != NULL );
355 
356  /* Make sure that the request is OK and if it's a revocation request
357  make sure that it refers to a certificate which is both present in
358  the store and currently active */
359  if( !checkRequest( iCertRequest, CRYPT_CERTACTION_NONE ) )
360  {
362  ( CRYPT_ARGERROR_NUM1, errorInfo,
363  "Certificate request information "
364  "inconsistent/invalid" ) );
365  }
366  if( requestType == CRYPT_CERTTYPE_REQUEST_REVOCATION )
367  {
368  status = checkRevRequest( dbmsInfo, iCertRequest );
369  if( cryptStatusError( status ) )
370  retExt( status,
371  ( status, errorInfo,
372  "Revocation request doesn't correspond to a currently "
373  "active certificate" ) );
374  }
375 
376  /* Extract the information that we need from the certificate request */
377  status = getKeyID( certID, ENCODED_DBXKEYID_SIZE, &certIDlength,
378  iCertRequest, CRYPT_CERTINFO_FINGERPRINT_SHA1 );
379  if( cryptStatusOK( status ) )
380  {
381  status = extractCertData( iCertRequest,
382  ( requestType == CRYPT_CERTTYPE_REQUEST_REVOCATION ) ? \
383  CRYPT_ICERTFORMAT_DATA : \
385  certData, MAX_CERT_SIZE, &certDataLength );
386  }
387  if( cryptStatusOK( status ) )
388  {
389  status = getKeyID( reqCertID, ENCODED_DBXKEYID_SIZE, &reqCertIDlength,
390  iCertRequest, CRYPT_IATTRIBUTE_AUTHCERTID );
391  if( cryptStatusError( status ) )
392  {
393  /* If the request is being added directly by the user, there's no
394  authorising certificate/PKI user information present */
395  reqCertIDptr = NULL;
396  reqCertIDlength = 0;
397  status = CRYPT_OK;
398  }
399  }
400  if( cryptStatusError( status ) )
401  {
402  retExt( status,
403  ( status, errorInfo,
404  "Couldn't extract certificate request data to add to "
405  "certificate store" ) );
406  }
407 
408  /* Check that the PKI user who authorised this certificate issue still
409  exists. If the CA has deleted them all further requests for
410  certificates fail */
411  if( reqCertIDptr != NULL )
412  {
414 
415  status = caGetIssuingUser( dbmsInfo, &iPkiUser, reqCertID,
416  reqCertIDlength, errorInfo );
417  if( cryptStatusOK( status ) )
419  else
420  {
421  updateCertErrorLog( dbmsInfo, CRYPT_ERROR_DUPLICATE,
422  "Certificate request submitted for "
423  "nonexistent PKI user", NULL, 0,
424  reqCertID, reqCertIDlength, NULL, 0,
425  NULL, 0 );
427  ( CRYPT_ERROR_PERMISSION, errorInfo,
428  "Certificate request submitted for nonexistent PKI "
429  "user" ) );
430  }
431  }
432 
433  /* If there's an authorising PKI user present make sure that it hasn't
434  already been used to authorise the issuance of a certificate. This
435  is potentially vulnerable to the following race condition:
436 
437  1: check authCertID -> OK
438  2: check authCertID -> OK
439  1: add
440  2: add
441 
442  In theory we could detect this by requiring the reqCertID to be
443  unique, however a PKI user can be used to request both a certificate
444  and a revocation for the certificate and a signing certificate can
445  be used to request an update or revocation of both itself and one or
446  more associated encryption certificates. We could probably handle
447  this via the ID-mangling used for certIDs but this makes tracing
448  events through the audit log complex since there'll now be different
449  effective IDs for the authorising certificate depending on what it
450  was authorising.
451 
452  In addition it's not certain how many further operations a
453  certificate (rather than a PKI user) can authorise, in theory a
454  single signing certificate can authorise at least four further
455  operations, these being the update of itself, the update of an
456  associated encryption certificate, and the revocation of itself and
457  the encryption certificate. In addition its possible that a signing
458  certificate could be used to authorise a series of short-duration
459  encryption certificates or a variety of other combinations of
460  operations.
461 
462  Because of these issues we can't use a uniqueness constraint on the
463  reqCertID to enforce a single use of issuing authorisation by the
464  database ifself but have to do a manual check here, checking
465  specifically for the case where a PKI user authorises a certificate
466  issue. In addition we can't even perform a general-purpose check
467  because as soon as a single issue has been authorised for a user
468  there'll be a request for that user logged so that all further
469  attempts to submit a request (for example for a renewal, or an
470  encryption certificate to go with a signing one) will fail. Because
471  of this we only perform a check on the first attempt to issue a
472  certificate, indicated by the caller setting the isInitialOp flag */
473  if( isInitialOp && reqCertIDptr != NULL )
474  {
475  BOUND_DATA boundData[ BOUND_DATA_MAXITEMS ], *boundDataPtr = boundData;
476 
477  initBoundData( boundDataPtr );
478  setBoundData( boundDataPtr, 0, reqCertID, reqCertIDlength );
479  status = dbmsQuery(
480  "SELECT certID FROM certLog WHERE reqCertID = ? "
481  "AND action = " TEXT_CERTACTION_REQUEST_CERT,
482  NULL, 0, NULL, boundDataPtr,
484  if( cryptStatusOK( status ) )
485  {
486  /* This user has already authorised the issue of a certificate,
487  it can't be used to issue a second certificate */
488  updateCertErrorLog( dbmsInfo, CRYPT_ERROR_DUPLICATE,
489  "Attempt to authorise additional certificate "
490  "issue when a certificate for this user has "
491  "already been issued", NULL, 0,
492  reqCertID, reqCertIDlength, NULL, 0, NULL, 0 );
494  ( CRYPT_ERROR_DUPLICATE, errorInfo,
495  "Attempt to authorise additional certificate issue "
496  "when a certificate for this user has already been "
497  "issued" ) );
498  }
499  }
500 
501  /* Update the certificate store. Since a revocation request generally
502  won't have any fields of any significance set we have to use a
503  special cut-down insert statement that doesn't expect to find any
504  fields except the certificate ID */
505  if( requestType == CRYPT_CERTTYPE_REQUEST_REVOCATION )
506  {
507  BOUND_DATA boundData[ BOUND_DATA_MAXITEMS ], *boundDataPtr = boundData;
508  char encodedCertData[ MAX_ENCODED_CERT_SIZE + 8 ];
509 
510  initBoundData( boundDataPtr );
511  setBoundData( boundDataPtr, 0, certID, certIDlength );
512  if( hasBinaryBlobs( dbmsInfo ) )
513  {
514  setBoundDataBlob( boundDataPtr, 1, certData, certDataLength );
515  }
516  else
517  {
518  int encodedCertDataLength;
519 
520  status = base64encode( encodedCertData, MAX_ENCODED_CERT_SIZE,
521  &encodedCertDataLength, certData,
522  certDataLength, CRYPT_CERTTYPE_NONE );
523  if( cryptStatusError( status ) )
524  {
525  DEBUG_DIAG(( "Couldn't base64-encode data" ));
526  assert( DEBUG_WARN );
527  return( status );
528  }
529  setBoundData( boundDataPtr, 1, encodedCertData,
530  encodedCertDataLength );
531  }
532  status = dbmsUpdate(
533  "INSERT INTO certRequests VALUES ("
534  TEXT_CERTTYPE_REQUEST_REVOCATION ", '', '', '', '', '', '', "
535  "'', ?, ?)", boundDataPtr, DBMS_UPDATE_BEGIN );
536  }
537  else
538  {
539  status = addCert( dbmsInfo, iCertRequest, CRYPT_CERTTYPE_REQUEST_CERT,
540  CERTADD_NORMAL, DBMS_UPDATE_BEGIN, errorInfo );
541  }
542  if( cryptStatusOK( status ) )
543  {
544  status = updateCertLog( dbmsInfo,
545  ( requestType == CRYPT_CERTTYPE_REQUEST_REVOCATION ) ? \
547  ( isRenewal ) ? \
550  certID, certIDlength, reqCertIDptr, reqCertIDlength,
551  NULL, 0, certData, certDataLength,
553  }
554  else
555  {
556  /* Something went wrong, abort the transaction */
557  dbmsUpdate( NULL, NULL, DBMS_UPDATE_ABORT );
558  retExtErr( status,
559  ( status, errorInfo, getDbmsErrorInfo( dbmsInfo ),
560  "Certificate request add operation failed: " ) );
561  }
562 
563  return( status );
564  }
565 #endif /* USE_DBMS */