cryptlib  3.4.1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros
scep_cli.c
Go to the documentation of this file.
1 /****************************************************************************
2 * *
3 * cryptlib SCEP Client 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 /* Prototypes for functions in pnppki.c */
21 
23 int pnpPkiSession( INOUT SESSION_INFO *sessionInfoPtr );
24 
25 #ifdef USE_SCEP
26 
27 /****************************************************************************
28 * *
29 * Additional Request Management Functions *
30 * *
31 ****************************************************************************/
32 
33 /* Process one of the bolted-on additions to the basic SCEP protocol */
34 
36 static int createAdditionalScepRequest( INOUT SESSION_INFO *sessionInfoPtr )
37  {
38  MESSAGE_CREATEOBJECT_INFO createInfo;
39  HTTP_DATA_INFO httpDataInfo;
41  int length, status;
42 
43  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
44 
45  REQUIRES( sessionInfoPtr->iAuthInContext == CRYPT_ERROR );
46 
47  /* Perform an HTTP GET with arguments "operation=GetCACert&message=*" */
48  sioctlSet( &sessionInfoPtr->stream, STREAM_IOCTL_HTTPREQTYPES,
50  initHttpDataInfoEx( &httpDataInfo, sessionInfoPtr->receiveBuffer,
51  sessionInfoPtr->receiveBufSize, &httpReqInfo );
52  memcpy( httpReqInfo.attribute, "operation", 9 );
53  httpReqInfo.attributeLen = 9;
54  memcpy( httpReqInfo.value, "GetCACert", 9 );
55  httpReqInfo.valueLen = 9;
56  memcpy( httpReqInfo.extraData, "message=*", 9 );
57  httpReqInfo.extraDataLen = 9;
58  status = sread( &sessionInfoPtr->stream, &httpDataInfo,
59  sizeof( HTTP_DATA_INFO ) );
60  sioctlSet( &sessionInfoPtr->stream, STREAM_IOCTL_HTTPREQTYPES,
62  if( cryptStatusError( status ) )
63  return( status );
64  length = httpDataInfo.bytesAvail;
65 
66  /* Since we can't use readPkiDatagram() because of the weird dual-
67  purpose HTTP transport used in SCEP we have to duplicate portions of
68  readPkiDatagram() here. See the readPkiDatagram() function for code
69  comments explaining the following operations */
70  if( length < 4 || length >= MAX_INTLENGTH )
71  {
74  "Invalid PKI message length %d", length ) );
75  }
76  status = length = \
77  checkObjectEncoding( sessionInfoPtr->receiveBuffer, length );
78  if( cryptStatusError( status ) )
79  {
80  retExt( status,
81  ( status, SESSION_ERRINFO,
82  "Invalid PKI message encoding" ) );
83  }
84 
85  /* Import the CA certificate and save it for later use */
87  sessionInfoPtr->receiveBuffer, length,
91  &createInfo, OBJECT_TYPE_CERTIFICATE );
92  if( cryptStatusError( status ) )
93  {
94  retExt( length,
95  ( length, SESSION_ERRINFO,
96  "Invalid SCEP CA certificate" ) );
97  }
98  sessionInfoPtr->iAuthInContext = createInfo.cryptHandle;
99 
100  /* Process the server's key fingerprint */
101  status = processKeyFingerprint( sessionInfoPtr );
102  if( cryptStatusError( status ) )
103  return( status );
104 
105  /* Make sure that the CA certificate meets the SCEP protocol
106  requirements */
107  if( !checkCACert( sessionInfoPtr->iAuthInContext ) )
108  {
111  "CA certificate usage restrictions prevent it from being "
112  "used for SCEP" ) );
113  }
114 
115  return( CRYPT_OK );
116  }
117 
118 /****************************************************************************
119 * *
120 * Request Management Functions *
121 * *
122 ****************************************************************************/
123 
124 /* Create a self-signed certificate for signing the request and decrypting
125  the response */
126 
127 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
128 static int createScepCert( INOUT SESSION_INFO *sessionInfoPtr,
130  {
131  CRYPT_CERTIFICATE iNewCert;
132  MESSAGE_CREATEOBJECT_INFO createInfo;
134  time_t currentTime = getTime();
135  int status;
136 
137  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
138  assert( isWritePtr( protocolInfo, sizeof( SCEP_PROTOCOL_INFO ) ) );
139 
140 #if 0 /* 20/9/10 This is problematic because if the certificate request
141  contains attributes then setting the
142  CRYPT_CERTINFO_CERTREQUEST copies them across to the
143  certificate, making it an X.509v3 certificate rather than
144  an X.509v1 one. To avoid this problem for now we stay
145  with X.509v3 certificates.
146 
147  To re-enable this, change the ACL entry for
148  CRYPT_CERTINFO_VERSION to
149  'MKPERM_SPECIAL_CERTIFICATES( Rxx_RWx_Rxx_Rxx )',
150  with the comment 'We have to be able to set the version
151  to 1 for SCEP, which creates a self-signed certificate as
152  part of the certificate-request process' */
153  /* Create a certificate, add the certificate request and other
154  information required by SCEP to it, and sign it. To avoid
155  complications over extension processing we make it an X.509v1
156  certificate, and to limit the exposure from having it floating around
157  out there we give it a validity of a day, which is somewhat longer
158  than required but may be necessary to get around time-zone issues in
159  which the CA checks the expiry time relative to the time zone that
160  it's in rather than GMT (although given some of the broken
161  certificates used with SCEP it seems likely that many CAs do little
162  to no checking at all) */
165  &createInfo, OBJECT_TYPE_CERTIFICATE );
166  if( cryptStatusError( status ) )
167  return( status );
168  status = krnlSendMessage( createInfo.cryptHandle, IMESSAGE_SETATTRIBUTE,
169  &sessionInfoPtr->iCertRequest,
171  if( cryptStatusOK( status ) )
172  {
173  static const int version = 1;
174 
175  status = krnlSendMessage( createInfo.cryptHandle,
177  ( MESSAGE_CAST ) &version,
179  }
180 #else
181  /* Create a certificate, add the certificate request and other
182  information required by SCEP to it, and sign it. To limit the
183  exposure from having it floating around out there we give it a
184  validity of a day, which is somewhat longer than required but may be
185  necessary to get around time-zone issues in which the CA checks the
186  expiry time relative to the time zone that it's in rather than GMT
187  (although given some of the broken certificates used with SCEP it
188  seems likely that many CAs do little to no checking at all)
189 
190  SCEP requires that the certificate serial number match the user name/
191  transaction ID, the spec actually says that the transaction ID should
192  be a hash of the public key but since it never specifies exactly what
193  is hashed ("MD5 hash on [sic] public key") this can probably be
194  anything. We use the user name, which is required to identify the
195  pkiUser entry in the CA certificate store */
198  &createInfo, OBJECT_TYPE_CERTIFICATE );
199  if( cryptStatusError( status ) )
200  return( status );
201  status = krnlSendMessage( createInfo.cryptHandle, IMESSAGE_SETATTRIBUTE,
202  &sessionInfoPtr->iCertRequest,
204 #if 0 /* 3/8/10 This seems to have vanished from SCEP drafts after about
205  draft 16. When restoring this functionality the special-
206  case attribute handling for SCEP in attr_acl.c has to be
207  restored as well */
208  if( cryptStatusOK( status ) )
209  {
210  const ATTRIBUTE_LIST *userNamePtr = \
211  findSessionInfo( sessionInfoPtr->attributeList,
213 
214  REQUIRES( userNamePtr != NULL );
215 
216  /* Set the serial number to the user name/transaction ID as
217  required by SCEP. This is the only time that we can write a
218  serial number to a certificate, normally it's set automagically
219  by the certificate-management code */
220  setMessageData( &msgData, userNamePtr->value,
221  userNamePtr->valueLength );
222  status = krnlSendMessage( createInfo.cryptHandle,
223  IMESSAGE_SETATTRIBUTE_S, &msgData,
225  }
226 #endif /* 0 */
227  if( cryptStatusOK( status ) )
228  {
229  static const int keyUsage = CRYPT_KEYUSAGE_DIGITALSIGNATURE | \
230  CRYPT_KEYUSAGE_KEYENCIPHERMENT;
231 
232  /* Set the certificate usage to signing (to sign the request) and
233  encryption (to decrypt the response). We've already checked that
234  these capabilities are available when the key was added to the
235  session.
236 
237  We delete the attribute before we try and set it in case there
238  was already one present in the request */
240  NULL, CRYPT_CERTINFO_KEYUSAGE );
241  status = krnlSendMessage( createInfo.cryptHandle,
243  ( MESSAGE_CAST ) &keyUsage,
245  }
246 #endif /* 1 */
247  if( cryptStatusOK( status ) )
248  {
249  setMessageData( &msgData, ( MESSAGE_CAST ) &currentTime,
250  sizeof( time_t ) );
251  status = krnlSendMessage( createInfo.cryptHandle,
252  IMESSAGE_SETATTRIBUTE_S, &msgData,
254  }
255  if( cryptStatusOK( status ) )
256  {
257  currentTime += 86400; /* 24 hours */
258  status = krnlSendMessage( createInfo.cryptHandle,
259  IMESSAGE_SETATTRIBUTE_S, &msgData,
261  }
262  if( cryptStatusOK( status ) )
263  status = krnlSendMessage( createInfo.cryptHandle,
266  if( cryptStatusOK( status ) )
267  status = krnlSendMessage( createInfo.cryptHandle,
268  IMESSAGE_CRT_SIGN, NULL,
269  sessionInfoPtr->privateKey );
270  if( cryptStatusError( status ) )
271  {
273  retExt( status,
274  ( status, SESSION_ERRINFO,
275  "Couldn't create ephemeral self-signed SCEP "
276  "certificate" ) );
277  }
278 
279  /* Now that we have a certificate, attach it to the private key. This
280  is somewhat ugly since it alters the private key by attaching a
281  certificate that (as far as the user is concerned) shouldn't really
282  exist, but we need to do this to allow signing and decryption. A
283  side-effect is that it constrains the private-key actions to make
284  them internal-only since it now has a certificate attached, hopefully
285  the user won't notice this since the key will have a proper CA-issued
286  certificate attached to it shortly.
287 
288  To further complicate things, we can't directly attach the newly-
289  created certificate because it already has a public-key context
290  attached to it, which would result in two keys being associated with
291  the single certificate. To resolve this, we create a second copy of
292  the certificate as a data-only certificate and attach that to the
293  private key */
294  status = krnlSendMessage( createInfo.cryptHandle, IMESSAGE_GETATTRIBUTE,
295  &iNewCert, CRYPT_IATTRIBUTE_CERTCOPY_DATAONLY );
296  if( cryptStatusOK( status ) )
297  krnlSendMessage( sessionInfoPtr->privateKey, IMESSAGE_SETDEPENDENT,
298  &iNewCert, SETDEP_OPTION_NOINCREF );
299  if( cryptStatusOK( status ) )
300  protocolInfo->iScepCert = createInfo.cryptHandle;
301  else
303  return( status );
304  }
305 
306 /* Complete the user-supplied PKCS #10 request by adding SCEP-internal
307  attributes and information */
308 
310 static int createScepCertRequest( INOUT SESSION_INFO *sessionInfoPtr )
311  {
313  findSessionInfo( sessionInfoPtr->attributeList,
316  int status = CRYPT_ERROR_NOTINITED;
317 
318  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
319 
320  /* Add the password to the PKCS #10 request as a ChallengePassword
321  attribute and sign the request. We always send this in its
322  ASCII string form even if it's an encoded value because the
323  ChallengePassword attribute has to be a text string */
324  if( attributeListPtr != NULL )
325  {
326  setMessageData( &msgData, attributeListPtr->value,
327  attributeListPtr->valueLength );
328  status = krnlSendMessage( sessionInfoPtr->iCertRequest,
329  IMESSAGE_SETATTRIBUTE_S, &msgData,
331  }
332  if( cryptStatusOK( status ) )
333  {
334  status = krnlSendMessage( sessionInfoPtr->iCertRequest,
335  IMESSAGE_CRT_SIGN, NULL,
336  sessionInfoPtr->privateKey );
337  }
338  if( cryptStatusError( status ) )
339  {
340  retExt( status,
341  ( status, SESSION_ERRINFO,
342  "Couldn't finalise PKCS #10 certificate request" ) );
343  }
344  return( CRYPT_OK );
345  }
346 
347 /* Create a SCEP request message */
348 
349 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
350 static int createScepRequest( INOUT SESSION_INFO *sessionInfoPtr,
351  INOUT SCEP_PROTOCOL_INFO *protocolInfo )
352  {
353  CRYPT_CERTIFICATE iCmsAttributes;
355  int dataLength, status;
356 
357  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
358  assert( isWritePtr( protocolInfo, sizeof( SCEP_PROTOCOL_INFO ) ) );
359 
360  /* Extract the request data into the session buffer */
361  setMessageData( &msgData, sessionInfoPtr->receiveBuffer,
362  sessionInfoPtr->receiveBufSize );
363  status = krnlSendMessage( sessionInfoPtr->iCertRequest,
364  IMESSAGE_CRT_EXPORT, &msgData,
366  if( cryptStatusError( status ) )
367  {
368  retExt( status,
369  ( status, SESSION_ERRINFO,
370  "Couldn't get PKCS #10 request data from SCEP request "
371  "object" ) );
372  }
373  DEBUG_DUMP_FILE( "scep_req0", sessionInfoPtr->receiveBuffer,
374  msgData.length );
375 
376  /* Phase 1: Encrypt the data using the CA's key */
377  status = envelopeWrap( sessionInfoPtr->receiveBuffer, msgData.length,
378  sessionInfoPtr->receiveBuffer,
379  sessionInfoPtr->receiveBufSize, &dataLength,
381  sessionInfoPtr->iAuthInContext );
382  if( cryptStatusError( status ) )
383  {
384  retExt( status,
385  ( status, SESSION_ERRINFO,
386  "Couldn't encrypt SCEP request data with CA key" ) );
387  }
388  DEBUG_DUMP_FILE( "scep_req1", sessionInfoPtr->receiveBuffer,
389  dataLength );
390 
391  /* Create the SCEP signing attributes */
392  status = createScepAttributes( sessionInfoPtr, protocolInfo,
393  &iCmsAttributes, TRUE, CRYPT_OK );
394  if( cryptStatusError( status ) )
395  {
396  retExt( status,
397  ( status, SESSION_ERRINFO,
398  "Couldn't create SCEP request signing attributes" ) );
399  }
400 
401  /* Phase 2: Sign the data using the self-signed certificate and SCEP
402  attributes */
403  status = envelopeSign( sessionInfoPtr->receiveBuffer, dataLength,
404  sessionInfoPtr->receiveBuffer,
405  sessionInfoPtr->receiveBufSize,
406  &sessionInfoPtr->receiveBufEnd,
407  CRYPT_CONTENT_NONE, sessionInfoPtr->privateKey,
408  iCmsAttributes );
409  krnlSendNotifier( iCmsAttributes, IMESSAGE_DECREFCOUNT );
410  if( cryptStatusError( status ) )
411  {
412  retExt( status,
413  ( status, SESSION_ERRINFO,
414  "Couldn't sign request data with ephemeral SCEP "
415  "certificate" ) );
416  }
417  DEBUG_DUMP_FILE( "scep_req2", sessionInfoPtr->receiveBuffer,
418  sessionInfoPtr->receiveBufEnd );
419 
420  return( CRYPT_OK );
421  }
422 
423 /****************************************************************************
424 * *
425 * Response Management Functions *
426 * *
427 ****************************************************************************/
428 
429 /* Check a SCEP response message */
430 
431 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
432 static int checkScepResponse( INOUT SESSION_INFO *sessionInfoPtr,
433  INOUT SCEP_PROTOCOL_INFO *protocolInfo )
434  {
435  CRYPT_CERTIFICATE iCmsAttributes;
436  MESSAGE_CREATEOBJECT_INFO createInfo;
439  int dataLength, sigResult, value, status;
440 
441  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
442  assert( isWritePtr( protocolInfo, sizeof( SCEP_PROTOCOL_INFO ) ) );
443 
444  /* Phase 1: Sig-check the data using the CA's key */
445  DEBUG_DUMP_FILE( "scep_resp2", sessionInfoPtr->receiveBuffer,
446  sessionInfoPtr->receiveBufEnd );
447  status = envelopeSigCheck( sessionInfoPtr->receiveBuffer,
448  sessionInfoPtr->receiveBufEnd,
449  sessionInfoPtr->receiveBuffer,
450  sessionInfoPtr->receiveBufSize, &dataLength,
451  sessionInfoPtr->iAuthInContext, &sigResult,
452  NULL, &iCmsAttributes );
453  if( cryptStatusError( status ) )
454  {
455  retExt( status,
456  ( status, SESSION_ERRINFO,
457  "Invalid CMS signed data in CA response" ) );
458  }
459  DEBUG_DUMP_FILE( "scep_res1", sessionInfoPtr->receiveBuffer,
460  dataLength );
461  if( cryptStatusError( sigResult ) )
462  {
463  /* The signed data was valid but the signature on it wasn't, this is
464  a different style of error than the previous one */
465  krnlSendNotifier( iCmsAttributes, IMESSAGE_DECREFCOUNT );
466  retExt( sigResult,
467  ( sigResult, SESSION_ERRINFO,
468  "Bad signature on CA response data" ) );
469  }
470 
471  /* Check that the returned nonce matches our initial nonce. It's now
472  identified as a recipient nonce since it's coming from the
473  responder */
474  setMessageData( &msgData, buffer, CRYPT_MAX_HASHSIZE );
475  status = krnlSendMessage( iCmsAttributes, IMESSAGE_GETATTRIBUTE_S,
477  if( cryptStatusError( status ) || \
478  msgData.length != protocolInfo->nonceSize || \
479  memcmp( buffer, protocolInfo->nonce, protocolInfo->nonceSize ) )
480  {
481  krnlSendNotifier( iCmsAttributes, IMESSAGE_DECREFCOUNT );
484  "Returned nonce doesn't match our original nonce" ) );
485  }
486 
487  /* Check that the operation succeeded */
488  status = getScepStatusValue( iCmsAttributes,
490  if( cryptStatusOK( status ) && value != MESSAGETYPE_CERTREP_VALUE )
491  status = CRYPT_ERROR_BADDATA;
492  if( cryptStatusOK( status ) )
493  status = getScepStatusValue( iCmsAttributes,
495  if( cryptStatusOK( status ) && value != MESSAGESTATUS_SUCCESS_VALUE )
496  {
497  int extValue;
498 
499  status = getScepStatusValue( iCmsAttributes,
500  CRYPT_CERTINFO_SCEP_FAILINFO, &extValue );
501  if( cryptStatusOK( status ) )
502  value = extValue;
503  status = CRYPT_ERROR_FAILED;
504  }
505  krnlSendNotifier( iCmsAttributes, IMESSAGE_DECREFCOUNT );
506  if( cryptStatusError( status ) )
507  {
508  retExt( status,
509  ( status, SESSION_ERRINFO,
510  "SCEP server reports that certificate issue operation "
511  "failed with error code %d", value ) );
512  }
513 
514  /* Phase 2: Decrypt the data using our self-signed key */
515  status = envelopeUnwrap( sessionInfoPtr->receiveBuffer, dataLength,
516  sessionInfoPtr->receiveBuffer,
517  sessionInfoPtr->receiveBufSize, &dataLength,
518  sessionInfoPtr->privateKey );
519  if( cryptStatusError( status ) )
520  {
521  retExt( status,
522  ( status, SESSION_ERRINFO,
523  "Couldn't decrypt CMS enveloped data in CA response" ) );
524  }
525  DEBUG_DUMP_FILE( "scep_res0", sessionInfoPtr->receiveBuffer,
526  dataLength );
527 
528  /* Finally, import the returned certificate(s) as a PKCS #7 chain */
530  sessionInfoPtr->receiveBuffer, dataLength,
534  &createInfo, OBJECT_TYPE_CERTIFICATE );
535  if( cryptStatusError( status ) )
536  {
537  retExt( status,
538  ( status, SESSION_ERRINFO,
539  "Invalid PKCS #7 certificate chain in CA response" ) );
540  }
541  sessionInfoPtr->iCertResponse = createInfo.cryptHandle;
542 
543  return( CRYPT_OK );
544  }
545 
546 /****************************************************************************
547 * *
548 * SCEP Client Functions *
549 * *
550 ****************************************************************************/
551 
552 /* Exchange data with a SCEP server */
553 
555 static int clientTransact( INOUT SESSION_INFO *sessionInfoPtr )
556  {
558  int status;
559 
560  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
561 
562  /* Get the issuing CA certificate via SCEP's bolted-on HTTP GET facility
563  if necessary */
564  if( sessionInfoPtr->iAuthInContext == CRYPT_ERROR )
565  {
566  status = createAdditionalScepRequest( sessionInfoPtr );
567  if( cryptStatusError( status ) )
568  return( status );
569  }
570 
571  /* Create the self-signed certificate that we need in order to sign and
572  decrypt messages */
573  initSCEPprotocolInfo( &protocolInfo );
574  status = createScepCertRequest( sessionInfoPtr );
575  if( cryptStatusOK( status ) )
576  status = createScepCert( sessionInfoPtr, &protocolInfo );
577  if( cryptStatusError( status ) )
578  return( status );
579 
580  /* Get a new certificate from the server */
581  status = createScepRequest( sessionInfoPtr, &protocolInfo );
582  if( cryptStatusOK( status ) )
583  {
584 #if 0 /* 7/9/10 Why is this commented out? */
585  sioctlSetString( &sessionInfoPtr->stream, STREAM_IOCTL_QUERY,
586  "operation=PKIOperation", 22 );
587 #endif
588  status = writePkiDatagram( sessionInfoPtr, SCEP_CONTENT_TYPE,
590  }
591  if( cryptStatusOK( status ) )
592  status = readPkiDatagram( sessionInfoPtr );
593  if( cryptStatusOK( status ) )
594  status = checkScepResponse( sessionInfoPtr, &protocolInfo );
596  protocolInfo.iScepCert = CRYPT_ERROR;
597  return( status );
598  }
599 
601 static int clientTransactWrapper( INOUT SESSION_INFO *sessionInfoPtr )
602  {
603  int status;
604 
605  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
606 
607  /* If it's not a plug-and-play PKI session, just pass the call on down
608  to the client transaction function */
609  if( !( sessionInfoPtr->sessionSCEP->flags & SCEP_PFLAG_PNPPKI ) )
610  return( clientTransact( sessionInfoPtr ) );
611 
612  /* We're doing plug-and-play PKI, point the transaction function at the
613  client-transact function to execute the PnP steps, then reset it back
614  to the PnP wrapper after we're done */
615  sessionInfoPtr->transactFunction = clientTransact;
616  status = pnpPkiSession( sessionInfoPtr );
617  sessionInfoPtr->transactFunction = clientTransactWrapper;
618  return( status );
619  }
620 
621 /****************************************************************************
622 * *
623 * Session Access Routines *
624 * *
625 ****************************************************************************/
626 
627 STDC_NONNULL_ARG( ( 1 ) ) \
628 void initSCEPclientProcessing( SESSION_INFO *sessionInfoPtr )
629  {
630  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
631 
632  sessionInfoPtr->transactFunction = clientTransactWrapper;
633  }
634 #endif /* USE_SCEP */