cryptlib  3.4.1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros
scep.c
Go to the documentation of this file.
1 /****************************************************************************
2 * *
3 * cryptlib SCEP Session Management *
4 * Copyright Peter Gutmann 1999-2008 *
5 * *
6 ****************************************************************************/
7 
8 #if defined( INC_ALL )
9  #include "crypt.h"
10  #include "asn1.h"
11  #include "session.h"
12  #include "scep.h"
13 #else
14  #include "crypt.h"
15  #include "enc_dec/asn1.h"
16  #include "session/session.h"
17  #include "session/scep.h"
18 #endif /* Compiler-specific includes */
19 
20 #ifdef USE_SCEP
21 
22 /****************************************************************************
23 * *
24 * Utility Functions *
25 * *
26 ****************************************************************************/
27 
28 /* Initialise and clean up protocol information */
29 
30 STDC_NONNULL_ARG( ( 1 ) ) \
31 void initSCEPprotocolInfo( OUT SCEP_PROTOCOL_INFO *protocolInfo )
32  {
33  assert( isWritePtr( protocolInfo, sizeof( SCEP_PROTOCOL_INFO ) ) );
34 
35  memset( protocolInfo, 0, sizeof( SCEP_PROTOCOL_INFO ) );
36  protocolInfo->iScepCert = CRYPT_ERROR;
37  }
38 
39 STDC_NONNULL_ARG( ( 1 ) ) \
40 void destroySCEPprotocolInfo( INOUT SCEP_PROTOCOL_INFO *protocolInfo )
41  {
42  assert( isWritePtr( protocolInfo, sizeof( SCEP_PROTOCOL_INFO ) ) );
43 
44  if( protocolInfo->iScepCert != CRYPT_ERROR )
45  krnlSendNotifier( protocolInfo->iScepCert, IMESSAGE_DECREFCOUNT );
46 
47  zeroise( protocolInfo, sizeof( SCEP_PROTOCOL_INFO ) );
48  }
49 
50 /* Check that the CA's certificate can also sign and encrypt data. This is
51  normally a really bad idea for CA certificates but is required by the
52  SCEP protocol. We don't check for the object being a CA certificate
53  because we could be dealing with an RA, which isn't necessarily a CA */
54 
55 CHECK_RETVAL_BOOL \
56 BOOLEAN checkCACert( IN_HANDLE const CRYPT_CERTIFICATE iCaCert )
57  {
58  int status;
59 
60  REQUIRES_B( isHandleRangeValid( iCaCert ) );
61 
65  status = krnlSendMessage( iCaCert, IMESSAGE_CHECK, NULL,
67  if( cryptStatusOK( status ) )
68  status = krnlSendMessage( iCaCert, IMESSAGE_CHECK, NULL,
70  return( cryptStatusOK( status ) ? TRUE : FALSE );
71  }
72 
73 /* Generate/check the server certificate fingerprint */
74 
76 int processKeyFingerprint( INOUT SESSION_INFO *sessionInfoPtr )
77  {
78  const ATTRIBUTE_LIST *fingerprintPtr = \
79  findSessionInfo( sessionInfoPtr->attributeList,
82  int status;
83 
84  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
85 
86  /* If the caller has supplied a certificate fingerprint, compare it to
87  the received certificate's fingerprint to make sure that we're
88  talking to the right system */
89  if( fingerprintPtr != NULL )
90  {
91  setMessageData( &msgData, fingerprintPtr->value,
92  fingerprintPtr->valueLength );
93  status = krnlSendMessage( sessionInfoPtr->iAuthInContext,
94  IMESSAGE_COMPARE, &msgData,
96  if( cryptStatusError( status ) )
97  {
100  "Server certificate doesn't match key fingerprint" ) );
101  }
102  }
103  else
104  {
105  BYTE certFingerprint[ CRYPT_MAX_HASHSIZE + 8 ];
106 
107  /* Remember the certificate fingerprint in case the caller wants to
108  check it. We don't worry if the add fails, it's a minor thing
109  and not worth aborting the overall operation for */
110  setMessageData( &msgData, certFingerprint, CRYPT_MAX_HASHSIZE );
111  status = krnlSendMessage( sessionInfoPtr->iAuthInContext,
112  IMESSAGE_GETATTRIBUTE_S, &msgData,
114  if( cryptStatusOK( status ) )
115  {
116  ( void ) addSessionInfoS( &sessionInfoPtr->attributeList,
118  certFingerprint, msgData.length );
119  }
120  }
121 
122  return( CRYPT_OK );
123  }
124 
125 /* Create SCEP signing attributes */
126 
127 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2, 3 ) ) \
128 int createScepAttributes( INOUT SESSION_INFO *sessionInfoPtr,
129  INOUT SCEP_PROTOCOL_INFO *protocolInfo,
131  const BOOLEAN isInitiator,
132  IN_STATUS const int scepStatus )
133  {
134  const ATTRIBUTE_LIST *userNamePtr = \
135  findSessionInfo( sessionInfoPtr->attributeList,
137  CRYPT_CERTIFICATE iCmsAttributes;
138  MESSAGE_CREATEOBJECT_INFO createInfo;
140  int status;
141 
142  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
143  assert( isWritePtr( protocolInfo, sizeof( SCEP_PROTOCOL_INFO ) ) );
144  assert( isWritePtr( iScepAttributes, sizeof( CRYPT_CERTIFICATE ) ) );
145 
146  REQUIRES( scepStatus >= MAX_ERROR && scepStatus <= CRYPT_OK );
147  REQUIRES( userNamePtr != NULL );
148 
149  /* Clear return value */
150  *iScepAttributes = CRYPT_ERROR;
151 
152  /* Create the signing attributes needed by SCEP and add the user name/
153  transaction ID and message type. The transaction ID is a bit
154  complicated, it's used to tie together all messages related to a
155  certificate-issue transaction and acts more as a type of fixed nonce
156  than any real identifier (the specification suggests using "an MD5
157  hash on [sic] the public key", later extended to also encompass SHA1,
158  SHA-256, and SHA-512). This is complicated by the fact that the
159  encoding is a PrintableString so the hash can't actually be used, but
160  then another part of the spec says that it's encoded as a decimal
161  value (!!) within the string.
162 
163  The way we handle it is that the client sends it to the server as is
164  (after a pre-check in checkAttributeFunction() for validity) and the
165  server tries to decode it as a cryptlib-encoded PKI user ID that's
166  used to look up the PKI user information. If it can't be decoded by
167  the server then it's not a request meant for a cryptlib server, so we
168  can reject it immediately */
171  IMESSAGE_DEV_CREATEOBJECT, &createInfo,
173  if( cryptStatusError( status ) )
174  return( status );
175  iCmsAttributes = createInfo.cryptHandle;
176  setMessageData( &msgData, userNamePtr->value, userNamePtr->valueLength );
177  status = krnlSendMessage( iCmsAttributes, IMESSAGE_SETATTRIBUTE_S,
179  if( cryptStatusOK( status ) )
180  {
181  const char *messageType = isInitiator ? MESSAGETYPE_PKCSREQ : \
182  MESSAGETYPE_CERTREP;
183 
184  setMessageData( &msgData, ( MESSAGE_CAST ) messageType,
185  strlen( messageType ) );
186  status = krnlSendMessage( iCmsAttributes, IMESSAGE_SETATTRIBUTE_S,
188  }
189  if( cryptStatusError( status ) )
190  {
191  krnlSendNotifier( iCmsAttributes, IMESSAGE_DECREFCOUNT );
192  return( status );
193  }
194 
195  /* Add the message status */
196  if( !isInitiator && cryptStatusError( scepStatus ) )
197  {
198  /* SCEP provides an extremely limited set of error codes so there's
199  not much that we can return in the way of additional failure
200  information */
201  setMessageData( &msgData, ( scepStatus == CRYPT_ERROR_SIGNATURE ) ? \
205  krnlSendMessage( iCmsAttributes, IMESSAGE_SETATTRIBUTE_S,
206  &msgData, CRYPT_CERTINFO_SCEP_FAILINFO );
209  }
210  else
211  {
214  }
215  status = krnlSendMessage( iCmsAttributes, IMESSAGE_SETATTRIBUTE_S,
216  &msgData, CRYPT_CERTINFO_SCEP_PKISTATUS );
217  if( cryptStatusError( status ) )
218  {
219  krnlSendNotifier( iCmsAttributes, IMESSAGE_DECREFCOUNT );
220  return( status );
221  }
222 
223  /* Add the nonce, identified as a sender nonce if we're the initiator and
224  a recipient nonce if we're the responder */
225  if( isInitiator )
226  {
227  /* If we're the initiator, generate a new nonce */
228  setMessageData( &msgData, protocolInfo->nonce, SCEP_NONCE_SIZE );
230  &msgData, CRYPT_IATTRIBUTE_RANDOM_NONCE );
231  protocolInfo->nonceSize = SCEP_NONCE_SIZE;
232  }
233  else
234  {
235  /* We're the responder, use the initiator's nonce */
236  setMessageData( &msgData, protocolInfo->nonce,
237  protocolInfo->nonceSize );
238  }
239  status = krnlSendMessage( iCmsAttributes, IMESSAGE_SETATTRIBUTE_S,
240  &msgData, isInitiator ? \
243  if( cryptStatusError( status ) )
244  {
245  krnlSendNotifier( iCmsAttributes, IMESSAGE_DECREFCOUNT );
246  return( status );
247  }
248  *iScepAttributes = iCmsAttributes;
249 
250  return( CRYPT_OK );
251  }
252 
253 /* For some bizarre reason integer status values are encoded as strings,
254  so we have to convert them to numeric values before we can do anything
255  with them */
256 
258 int getScepStatusValue( IN_HANDLE const CRYPT_CERTIFICATE iCmsAttributes,
260  OUT int *value )
261  {
264  int numericValue, status;
265 
266  assert( isWritePtr( value, sizeof( int ) ) );
267 
268  REQUIRES( isHandleRangeValid( iCmsAttributes ) );
269  REQUIRES( attributeType == CRYPT_CERTINFO_SCEP_MESSAGETYPE || \
270  attributeType == CRYPT_CERTINFO_SCEP_PKISTATUS || \
271  attributeType == CRYPT_CERTINFO_SCEP_FAILINFO );
272 
273  /* Clear return value */
274  *value = CRYPT_ERROR;
275 
276  /* Get the status string and decode it into an integer */
277  setMessageData( &msgData, buffer, CRYPT_MAX_TEXTSIZE );
278  status = krnlSendMessage( iCmsAttributes, IMESSAGE_GETATTRIBUTE_S,
279  &msgData, attributeType );
280  if( cryptStatusError( status ) )
281  return( status );
282  status = strGetNumeric( buffer, msgData.length, &numericValue, 0, 20 );
283  if( cryptStatusError( status ) )
284  return( CRYPT_ERROR_BADDATA );
285  *value = numericValue;
286 
287  return( CRYPT_OK );
288  }
289 
290 /****************************************************************************
291 * *
292 * Control Information Management Functions *
293 * *
294 ****************************************************************************/
295 
296 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
297 static int setAttributeFunction( INOUT SESSION_INFO *sessionInfoPtr,
298  IN const void *data,
300  {
301  CRYPT_CERTIFICATE cryptCert = *( ( CRYPT_CERTIFICATE * ) data );
302  int value, status;
303 
304  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
305  assert( isReadPtr( data, sizeof( int ) ) );
306 
307  REQUIRES( type == CRYPT_SESSINFO_REQUEST || \
309 
310  /* Make sure that there aren't any conflicts with existing attributes */
311  if( !checkAttributesConsistent( sessionInfoPtr, type ) )
312  return( CRYPT_ERROR_INITED );
313 
314  if( type == CRYPT_SESSINFO_CMP_PRIVKEYSET )
315  {
316  CRYPT_CERTIFICATE privKeyset = *( ( CRYPT_CERTIFICATE * ) data );
317 
318  /* Remember that we're using plug-and-play PKI functionality */
319  sessionInfoPtr->sessionSCEP->flags |= SCEP_PFLAG_PNPPKI;
320 
321  krnlSendNotifier( privKeyset, IMESSAGE_INCREFCOUNT );
322  sessionInfoPtr->privKeyset = privKeyset;
323  return( CRYPT_OK );
324  }
325 
326  /* Make sure that everything is set up ready to go */
327  status = krnlSendMessage( cryptCert, IMESSAGE_GETATTRIBUTE, &value,
329  if( type == CRYPT_SESSINFO_CACERTIFICATE )
330  {
331  if( cryptStatusError( status ) || !value )
332  return( CRYPT_ARGERROR_NUM1 );
333  }
334  else
335  {
336  /* The PKCS #10 request has to be unsigned so that we can add the
337  challengePassword */
338  if( cryptStatusError( status ) || value )
339  return( CRYPT_ARGERROR_NUM1 );
340  }
341  if( type == CRYPT_SESSINFO_CACERTIFICATE )
342  {
343  /* Make sure that the CA certificate meets the SCEP protocol
344  requirements */
345  if( !checkCACert( cryptCert ) )
346  {
347  setErrorInfo( sessionInfoPtr, CRYPT_CERTINFO_KEYUSAGE,
349  return( CRYPT_ARGERROR_NUM1 );
350  }
351  }
352 
353  /* Add the request and increment its usage count */
355  if( type == CRYPT_SESSINFO_CACERTIFICATE )
356  {
357  sessionInfoPtr->iAuthInContext = cryptCert;
358  status = processKeyFingerprint( sessionInfoPtr );
359  }
360  else
361  sessionInfoPtr->iCertRequest = cryptCert;
362 
363  return( status );
364  }
365 
367 static int checkAttributeFunction( INOUT SESSION_INFO *sessionInfoPtr,
368  IN const void *data,
370  {
371  int status;
372 
373  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
374  assert( isReadPtr( data, sizeof( int ) ) );
375 
377 
378  if( type != CRYPT_SESSINFO_USERNAME && \
379  type != CRYPT_SESSINFO_PRIVATEKEY && \
380  type != CRYPT_SESSINFO_REQUEST )
381  return( CRYPT_OK );
382 
383  /* If it's a user name, used for the SCEP transaction ID, make sure that
384  it meets the SCEP requirements */
385  if( type == CRYPT_SESSINFO_USERNAME )
386  {
387  const BYTE *userName = ( ( MESSAGE_DATA * ) data )->data;
388  const int userNameLength = ( ( MESSAGE_DATA * ) data )->length;
389  int index;
390 
391  /* Because users won't necessarily have a user name/transaction ID
392  to use we allow them to specify the value as "[Autodetect]" and
393  replace it with a base64'd nonce (to meet the encoding
394  requirements below) */
395  if( userNameLength == 12 && !memcmp( userName, "[Autodetect]", 12 ) )
396  {
398  BYTE nonce[ 16 + 8 ];
399  char transID[ CRYPT_MAX_TEXTSIZE + 8 ];
400  int transIDlength;
401 
402  /* The caller has explicitly indicated that they want us to
403  generate a transaction ID for them, generate it as a random
404  text string */
405  setMessageData( &msgData, nonce, 16 );
407  IMESSAGE_GETATTRIBUTE_S, &msgData,
408  CRYPT_IATTRIBUTE_RANDOM_NONCE );
409  if( cryptStatusOK( status ) )
410  status = base64encode( transID, CRYPT_MAX_TEXTSIZE,
411  &transIDlength, nonce, 16,
413  if( cryptStatusOK( status ) )
414  status = addSessionInfoEx( &sessionInfoPtr->attributeList,
416  transID, 16, ATTR_FLAG_NONE );
417  if( cryptStatusError( status ) )
418  return( status );
419 
420  /* Tell the caller that the attribute was processed
421  internally */
422  return( OK_SPECIAL );
423  }
424 
425  /* For some bizarre reason SCEP encodes it's transaction ID (which
426  is our user name) as a PrintableString (even though the
427  specification suggests that the value be created from a hash of
428  the public key, which doesn't fit into a PrintableString) so we
429  have to filter any supplied value to make sure that it can be
430  encoded when we send it */
431  for( index = 0; index < userNameLength; index++ )
432  {
433  static const char allowedChars[] = "'\"()+,-./:=? \x00\x00";
434  const int ch = byteToInt( userName[ index ] );
435  BOOLEAN foundMatch = FALSE;
436  int i;
437 
438  if( isAlnum( ch ) )
439  continue;
440  for( i = 0; allowedChars[ i ] != '\0' && \
441  i < FAILSAFE_ARRAYSIZE( allowedChars, char ); i++ )
442  {
443  if( allowedChars[ i ] == ch )
444  {
445  foundMatch = TRUE;
446  break;
447  }
448  }
449  ENSURES( i < FAILSAFE_ARRAYSIZE( allowedChars, char ) );
450  if( !foundMatch )
451  return( CRYPT_ARGERROR_STR1 );
452  }
453 
454  return( CRYPT_OK );
455  }
456 
457  /* If it's a certificate request, make sure that the SCEP protocol
458  attributes, which get stuffed into the request alongside the standard
459  PKCS #10 data, haven't been set since we need to set them ourselves */
460  if( type == CRYPT_SESSINFO_REQUEST )
461  {
462  const CRYPT_CERTIFICATE cryptCertRequest = \
463  *( ( CRYPT_CERTIFICATE * ) data );
465 
466  /* Make sure that this attribute isn't already present */
467  setMessageData( &msgData, NULL, 0 );
468  status = krnlSendMessage( cryptCertRequest, IMESSAGE_GETATTRIBUTE_S,
470  if( cryptStatusOK( status ) )
471  {
474  return( CRYPT_ARGERROR_NUM1 );
475  }
476 
477  return( CRYPT_OK );
478  }
479 
481 
482  /* Make sure that there aren't any conflicts with existing attributes */
483  if( !checkAttributesConsistent( sessionInfoPtr, type ) )
484  return( CRYPT_ERROR_INITED );
485 
486  /* If it's a client key, make sure that there's no certificate
487  attached */
488  if( !isServer( sessionInfoPtr ) )
489  {
490  const CRYPT_CONTEXT cryptContext = *( ( CRYPT_CONTEXT * ) data );
491  int value;
492 
493  status = krnlSendMessage( cryptContext, IMESSAGE_GETATTRIBUTE,
494  &value, CRYPT_CERTINFO_CERTTYPE );
495  if( cryptStatusOK( status ) )
496  return( CRYPT_ARGERROR_NUM1 );
497  }
498 
499  return( CRYPT_OK );
500  }
501 
502 /****************************************************************************
503 * *
504 * Session Access Routines *
505 * *
506 ****************************************************************************/
507 
509 int setAccessMethodSCEP( INOUT SESSION_INFO *sessionInfoPtr )
510  {
511  static const PROTOCOL_INFO protocolInfo = {
512  /* General session information */
513  TRUE, /* Request-response protocol */
514  SESSION_ISHTTPTRANSPORT, /* Flags */
515  80, /* HTTP port */
516  SESSION_NEEDS_USERID | /* Client attributes */
518  SESSION_NEEDS_PRIVATEKEY | \
519  SESSION_NEEDS_PRIVKEYSIGN | \
520  SESSION_NEEDS_PRIVKEYCRYPT | \
521  SESSION_NEEDS_REQUEST,
522  SESSION_NEEDS_PRIVATEKEY | /* Server attributes */
524  SESSION_NEEDS_PRIVKEYCRYPT | \
525  SESSION_NEEDS_PRIVKEYCERT | \
526  SESSION_NEEDS_PRIVKEYCACERT | \
527  SESSION_NEEDS_CERTSTORE,
528  1, 1, 1 /* Version 1 */
529  };
530 
531  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
532 
533  /* Set the access method pointers */
534  sessionInfoPtr->protocolInfo = &protocolInfo;
535  if( isServer( sessionInfoPtr ) )
536  initSCEPserverProcessing( sessionInfoPtr );
537  else
538  initSCEPclientProcessing( sessionInfoPtr );
539  sessionInfoPtr->setAttributeFunction = setAttributeFunction;
540  sessionInfoPtr->checkAttributeFunction = checkAttributeFunction;
541 
542  return( CRYPT_OK );
543  }
544 #endif /* USE_SCEP */