cryptlib  3.4.1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros
scorebrd.c
Go to the documentation of this file.
1 /****************************************************************************
2 * *
3 * cryptlib Session Scoreboard *
4 * Copyright Peter Gutmann 1998-2011 *
5 * *
6 ****************************************************************************/
7 
8 #if defined( INC_ALL )
9  #include "crypt.h"
10  #include "session.h"
11  #include "scorebrd.h"
12  #include "ssl.h"
13 #else
14  #include "crypt.h"
15  #include "session/session.h"
16  #include "session/scorebrd.h"
17  #include "session/ssl.h"
18 #endif /* Compiler-specific includes */
19 
20 #ifdef USE_SSL
21 
22 /* The minimum and maximum permitted number of entries in the scoreboard */
23 
24 #define SCOREBOARD_MIN_SIZE 8
25 #define SCOREBOARD_MAX_SIZE 128
26 
27 /* The maximum size of any identifier and data value to be stored in the
28  scoreboard. Since the scoreboard is currently only used for SSL session
29  resumption, these are MAX_SESSIONID_SIZE, 32 bytes, and SSL_SECRET_SIZE,
30  48 bytes */
31 
32 #define SCOREBOARD_KEY_SIZE MAX_SESSIONID_SIZE
33 #define SCOREBOARD_DATA_SIZE SSL_SECRET_SIZE
34 
35 /* An individual scoreboard entry containing index information and its
36  corresponding data. This is stored in separate memory blocks because one
37  is allocated in secure nonpageable storage and the other isn't, with
38  scoreboardIndex[] containing pointers into corresponding entries in
39  scoreboardData[] */
40 
41 typedef BYTE SCOREBOARD_DATA[ SCOREBOARD_DATA_SIZE ];
42 typedef struct {
43  /* Identification information: The checksum and hash of the session ID
44  (to locate an entry based on the sessionID sent by the client) and
45  checksum and hash of the FQDN (to locate an entry based on the server
46  FQDN) */
47  int sessionCheckValue;
49  BYTE sessionHash[ HASH_DATA_SIZE + 4 ];
50  int fqdnCheckValue;
52  BYTE fqdnHash[ HASH_DATA_SIZE + 4 ];
53 
54  /* Since a lookup may have to return a session ID value if we're going
55  from an FQDN to session a ID, we have to store the full session ID
56  value alongside its checksum and hash */
57  BUFFER( SCOREBOARD_KEY_SIZE, sessionIDlength ) \
58  BYTE sessionID[ SCOREBOARD_KEY_SIZE + 4 ];
59  int sessionIDlength;
60 
61  /* The scoreboard data, just a pointer into the secure SCOREBOARD_DATA
62  memory. The dataLength variable records how much data is actually
63  present out of the SCOREBOARD_DATA_SIZE bytes that are available for
64  use */
65  BUFFER( SCOREBOARD_DATA_SIZE, dataLength ) \
66  void *data;
67  int dataLength;
68 
69  /* Miscellaneous information. We record whether an entry corresponds to
70  server or client data in order to provide logically separate
71  namespaces for client and server */
72  time_t timeStamp; /* Time entry was added to the scoreboard */
73  BOOLEAN isServerData; /* Whether this is client or server value */
74  int uniqueID; /* Unique ID for this entry */
75  } SCOREBOARD_INDEX;
76 
77 /* The maximum amount of time that an entry is retained in the scoreboard,
78  1 hour */
79 
80 #define SCOREBOARD_TIMEOUT 3600
81 
82 /* Overall scoreboard information. Note that the SCOREBOARD_STATE size
83  define in scorebrd.h will need to be updated if this structure is
84  changed */
85 
86 typedef struct {
87  /* Scoreboard index and data storage, and the total number of entries in
88  the scoreboard */
89  void *index, *data; /* Scoreboard index and data */
90  int noEntries; /* Total scoreboard entries */
91 
92  /* The last used entry in the scoreboard, and a unique ID for each
93  scoreboard entry. This is incremented for each index entry added,
94  so that even if an entry is deleted and then another one with the
95  same index value added, the uniqueID for the two will be different */
96  int lastEntry; /* Last used entry in scoreboard */
97  int uniqueID; /* Unique ID for scoreboard entry */
98  } SCOREBOARD_INFO;
99 
100 /****************************************************************************
101 * *
102 * Utility Functions *
103 * *
104 ****************************************************************************/
105 
106 /* Sanity-check the scoreboard state */
107 
109 static BOOLEAN sanityCheck( const SCOREBOARD_INFO *scoreboardInfo )
110  {
111  assert( isReadPtr( scoreboardInfo, sizeof( SCOREBOARD_INFO ) ) );
112 
113  /* Make sure that the general state is in order */
114  if( scoreboardInfo->noEntries < SCOREBOARD_MIN_SIZE || \
115  scoreboardInfo->noEntries > SCOREBOARD_MAX_SIZE )
116  return( FALSE );
117  if( scoreboardInfo->lastEntry < 0 || \
118  scoreboardInfo->lastEntry > scoreboardInfo->noEntries )
119  return( FALSE );
120  if( scoreboardInfo->uniqueID < 0 )
121  return( FALSE );
122 
123  return( TRUE );
124  }
125 
126 /* Clear a scoreboard entry */
127 
128 STDC_NONNULL_ARG( ( 1 ) ) \
129 static void clearScoreboardEntry( SCOREBOARD_INDEX *scoreboardEntryPtr )
130  {
131  void *savedDataPtr = scoreboardEntryPtr->data;
132 
133  assert( isWritePtr( scoreboardEntryPtr, \
134  sizeof( SCOREBOARD_INDEX ) ) );
135  assert( isWritePtr( scoreboardEntryPtr->data, SCOREBOARD_DATA_SIZE ) );
136 
137  REQUIRES_V( scoreboardEntryPtr->data != NULL );
138 
139  zeroise( scoreboardEntryPtr->data, SCOREBOARD_DATA_SIZE );
140  memset( scoreboardEntryPtr, 0, sizeof( SCOREBOARD_INDEX ) );
141  scoreboardEntryPtr->data = savedDataPtr;
142  scoreboardEntryPtr->dataLength = 0;
143  }
144 
145 /* Add a scoreboard entry */
146 
147 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3, 8 ) ) \
148 static int addEntryData( INOUT SCOREBOARD_INDEX *scoreboardEntryPtr,
149  IN_INT_Z const int keyCheckValue,
150  IN_BUFFER( keyLength ) const void *key,
151  IN_LENGTH_SHORT_MIN( 8 ) const int keyLength,
152  IN_INT_Z const int altKeyCheckValue,
153  IN_BUFFER_OPT( altKeyLength ) const void *altKey,
154  IN_LENGTH_SHORT_Z const int altKeyLength,
155  IN_BUFFER( valueLength ) const void *value,
156  IN_LENGTH_SHORT const int valueLength,
157  const time_t currentTime )
158  {
159  int status;
160 
161  assert( isWritePtr( scoreboardEntryPtr, sizeof( SCOREBOARD_INDEX ) ) );
162  assert( isReadPtr( key, keyLength ) );
163  assert( ( altKey == NULL && altKeyLength == 0 ) || \
164  isReadPtr( altKey, altKeyLength ) );
165  assert( isReadPtr( value, valueLength ) );
166 
167  REQUIRES( keyCheckValue >= 0 );
168  REQUIRES( keyLength >= 8 && keyLength < MAX_INTLENGTH_SHORT );
169  REQUIRES( ( altKey == NULL && altKeyLength == 0 && \
170  altKeyCheckValue == 0 ) || \
171  ( altKey != NULL && \
172  altKeyLength >= 2 && altKeyLength < MAX_INTLENGTH_SHORT && \
173  altKeyCheckValue >= 0 ) );
174  REQUIRES( valueLength > 0 && valueLength <= SCOREBOARD_DATA_SIZE );
175  REQUIRES( currentTime > MIN_TIME_VALUE );
176 
177  /* Clear the existing data in the entry */
178  clearScoreboardEntry( scoreboardEntryPtr );
179 
180  /* Copy across the key and value (Amicitiae nostrae memoriam spero
181  sempiternam fore - Cicero) */
182  scoreboardEntryPtr->sessionCheckValue = keyCheckValue;
183  hashData( scoreboardEntryPtr->sessionHash, HASH_DATA_SIZE,
184  key, keyLength );
185  if( altKey != NULL )
186  {
187  scoreboardEntryPtr->fqdnCheckValue = altKeyCheckValue;
188  hashData( scoreboardEntryPtr->fqdnHash, HASH_DATA_SIZE,
189  altKey, altKeyLength );
190  }
191  status = attributeCopyParams( scoreboardEntryPtr->sessionID,
192  SCOREBOARD_KEY_SIZE,
193  &scoreboardEntryPtr->sessionIDlength,
194  key, keyLength );
195  ENSURES( cryptStatusOK( status ) );
196  status = attributeCopyParams( scoreboardEntryPtr->data,
197  SCOREBOARD_DATA_SIZE,
198  &scoreboardEntryPtr->dataLength,
199  value, valueLength );
200  ENSURES( cryptStatusOK( status ) );
201  scoreboardEntryPtr->isServerData = ( altKey == NULL ) ? TRUE : FALSE;
202  scoreboardEntryPtr->timeStamp = currentTime;
203 
204  return( CRYPT_OK );
205  }
206 
207 /****************************************************************************
208 * *
209 * Scoreboard Management Functions *
210 * *
211 ****************************************************************************/
212 
213 /* Find an entry, returning its position in the scoreboard. This function
214  currently uses a straightforward linear search with entries clustered
215  towards the start of the scoreboard. Although this may seem somewhat
216  suboptimal, since cryptlib isn't running as a high-performance web server
217  the scoreboard will rarely contain more than a handful of entries (if
218  any). In any case a quick scan through a small number of integers is
219  probably still faster than the complex in-memory database lookup schemes
220  used by many servers, and is also required to handle things like
221  scoreboard LRU management */
222 
223 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3, 6 ) ) \
224 static int findEntry( INOUT SCOREBOARD_INFO *scoreboardInfo,
225  IN_ENUM( SCOREBOARD_KEY ) \
227  IN_BUFFER( keyLength ) const void *key,
228  IN_LENGTH_SHORT_MIN( 2 ) const int keyLength,
229  const time_t currentTime,
231  {
232  SCOREBOARD_INDEX *scoreboardIndex = scoreboardInfo->index;
234  const BOOLEAN keyIsSessionID = \
235  ( keyType == SCOREBOARD_KEY_SESSIONID_CLI || \
236  keyType == SCOREBOARD_KEY_SESSIONID_SVR ) ? TRUE : FALSE;
237  const BOOLEAN isServerMatch = \
238  ( keyType == SCOREBOARD_KEY_SESSIONID_SVR ) ? TRUE : FALSE;
239  BOOLEAN dataHashed = FALSE;
240  time_t oldestTime = currentTime;
241  const int checkValue = checksumData( key, keyLength );
242  int nextFreeEntry = CRYPT_ERROR, lastUsedEntry = 0, oldestEntry = 0;
243  int matchPosition = CRYPT_ERROR, i;
244 
245  assert( isWritePtr( scoreboardInfo, sizeof( SCOREBOARD_INFO ) ) );
246  assert( isReadPtr( key, keyLength ) );
247  assert( isWritePtr( position, sizeof( int ) ) );
248  assert( isWritePtr( scoreboardIndex,
249  scoreboardInfo->noEntries * sizeof( SCOREBOARD_INDEX ) ) );
250 
251  REQUIRES( keyType > SCOREBOARD_KEY_NONE && \
252  keyType < SCOREBOARD_KEY_LAST );
253  REQUIRES( keyLength >= 2 && keyLength < MAX_INTLENGTH_SHORT);
254  REQUIRES( currentTime > MIN_TIME_VALUE );
255 
256  /* Clear return value */
257  *position = CRYPT_ERROR;
258 
259  /* Scan the scoreboard expiring old entries, looking for a match
260  (indicated by matchPosition), and keeping a record of the oldest
261  entry (recorded by oldestEntry) in case we need to expire an entry to
262  make room for a new one */
263  for( i = 0; i < scoreboardInfo->lastEntry && \
264  i < FAILSAFE_ITERATIONS_MAX; i++ )
265  {
266  SCOREBOARD_INDEX *scoreboardEntryPtr = &scoreboardIndex[ i ];
267 
268  /* If this entry has expired, delete it */
269  if( scoreboardEntryPtr->timeStamp + SCOREBOARD_TIMEOUT < currentTime )
270  clearScoreboardEntry( scoreboardEntryPtr );
271 
272  /* Check for a free entry and the oldest non-free entry. We could
273  perform an early-out once we find a free entry but this would
274  prevent any following expired entries from being deleted */
275  if( scoreboardEntryPtr->timeStamp <= MIN_TIME_VALUE )
276  {
277  /* We've found a free entry, remember it for future use if
278  required and continue */
279  if( nextFreeEntry == CRYPT_ERROR )
280  nextFreeEntry = i;
281  continue;
282  }
283  lastUsedEntry = i;
284  if( scoreboardEntryPtr->timeStamp < oldestTime )
285  {
286  /* We've found an older entry than the current oldest entry,
287  remember it */
288  oldestTime = scoreboardEntryPtr->timeStamp;
289  oldestEntry = i;
290  }
291 
292  /* If we've already found a match then we're just scanning for LRU
293  purposes and we don't need to go any further */
294  if( matchPosition != CRYPT_ERROR )
295  continue;
296 
297  /* Make sure that this entry is appropriate for the match type that
298  we're performing */
299  if( scoreboardEntryPtr->isServerData != isServerMatch )
300  continue;
301 
302  /* Perform a quick check using a checksum of the name to weed out
303  most entries */
304  if( ( keyIsSessionID && \
305  scoreboardEntryPtr->sessionCheckValue == checkValue ) || \
306  ( !keyIsSessionID && \
307  scoreboardEntryPtr->fqdnCheckValue == checkValue ) )
308  {
309  void *hashPtr = keyIsSessionID ? \
310  scoreboardEntryPtr->sessionHash : \
311  scoreboardEntryPtr->fqdnHash;
312 
313  if( !dataHashed )
314  {
315  hashData( hashValue, HASH_DATA_SIZE, key, keyLength );
316  dataHashed = TRUE;
317  }
318  if( !memcmp( hashPtr, hashValue, HASH_DATA_SIZE ) )
319  {
320  /* Remember the match position. We can't immediately exit
321  at this point because we still need to look for the last
322  used entry and potentually shrink the scoreboard-used
323  size */
324  matchPosition = i;
325  }
326  }
327  }
328  ENSURES( i < FAILSAFE_ITERATIONS_MAX );
329 
330  /* If the total number of entries has shrunk due to old entries expiring,
331  reduce the overall scoreboard-used size */
332  if( lastUsedEntry + 1 < scoreboardInfo->lastEntry )
333  scoreboardInfo->lastEntry = lastUsedEntry + 1;
334 
335  /* If we've found a match, we're done */
336  if( matchPosition >= 0 )
337  {
338  *position = matchPosition;
339  return( CRYPT_OK );
340  }
341 
342  /* The entry wasn't found, return the location where we can add a new
343  entry */
344  if( nextFreeEntry >= 0 )
345  {
346  /* We've found a freed-up existing position (which will be before
347  any remaining free entries), add the new entry there */
348  *position = nextFreeEntry;
349  }
350  else
351  {
352  /* If there are still free positions in the scoreboard, use the next
353  available one */
354  if( scoreboardInfo->lastEntry < scoreboardInfo->noEntries )
355  *position = scoreboardInfo->lastEntry;
356  else
357  {
358  /* There are no free positions, overwrite the oldest entry */
359  *position = oldestEntry;
360  }
361  }
362  ENSURES( *position >= 0 && *position < scoreboardInfo->noEntries );
363 
364  /* Let the caller know that this is an indication of a free position
365  rather than a match */
366  return( OK_SPECIAL );
367  }
368 
369 /* Add an entry to the scoreboard. The strategy for updating entries can
370  get quite complicated. In the following the server-side cases are
371  denoted with -S and the client-side cases with -C:
372 
373  Case | key | altKey | Action
374  | (sessID) | (FQDN) |
375  --------+-----------+-----------+---------------------------------------
376  1-S | no match | absent | Add entry
377  --------+-----------+-----------+---------------------------------------
378  2-S | match | absent | Add-special (see below)
379  --------+-----------+-----------+---------------------------------------
380  3-C | no match | no match | Add entry
381  --------+-----------+-----------+---------------------------------------
382  4-C | no match | match | Replace existing match. This situation
383  | | | has presumably occurred because we've
384  | | | re-connected to a server with a full
385  | | | handshake and were allocated a new
386  | | | session ID.
387  --------+-----------+-----------+---------------------------------------
388  5-C | match | no match | Clear entry. This situation shouldn't
389  | | | occur, it means that we've somehow
390  | | | acquired a session ID with a different
391  | | | server.
392  --------+-----------+-----------+---------------------------------------
393  6-C | match | match | Add-special (see below)
394  --------+-----------+-----------+---------------------------------------
395  7-C | match-1 | match-2 | Match, but at different locations,
396  | | | clear both entries (variant of case
397  | | | 5-C).
398 
399  Add-special is a conditional add, if the data value that we're trying to
400  add corresponds to the existing one (and the search keys matched as well)
401  then it's an update of an existing entry and we update its timestamp. If
402  the data value doesn't match (but the search keys did) then something
403  funny is going on and we clear the existing entry. If we simply ignore
404  the add attempt then it'll appear to the caller that we've added the new
405  value when in fact we've retained the existing one. If on the other hand
406  we overwrite the old value with the new one then it'll allow an attacker
407  to replace existing scoreboard contents with attacker-controlled ones.
408 
409  In theory not every case listed above can occur because information is
410  only added for new (non-resumed) sessions, so for example case 2-S
411  wouldn't occur because if there's already a match for the session ID then
412  it'd result in a resumed session and so the information wouldn't be added
413  a second time. However there are situations in which these oddball cases
414  can occur, in general not for servers (even with two threads racing each
415  other for scoreboard access) because it'd require that the cryptlib
416  server allocate the same session ID twice, but it can occur for clients
417  if (case 5-C) two servers allocate us the same session ID or (case 4-C)
418  two threads simultaneously connect to the same server, with FQDNs the
419  same but session IDs different */
420 
421 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2, 6, 8 ) ) \
422 static int addEntry( INOUT SCOREBOARD_INFO *scoreboardInfo,
423  IN_BUFFER( keyLength ) const void *key,
424  IN_LENGTH_SHORT_MIN( 8 ) const int keyLength,
425  IN_BUFFER_OPT( altKeyLength ) const void *altKey,
426  IN_LENGTH_SHORT_Z const int altKeyLength,
427  IN_BUFFER( valueLength ) const void *value,
428  IN_LENGTH_SHORT const int valueLength,
429  OUT_INT_Z int *uniqueID )
430  {
431  SCOREBOARD_INDEX *scoreboardIndex = scoreboardInfo->index;
432  SCOREBOARD_INDEX *scoreboardEntryPtr = NULL;
433  const time_t currentTime = getTime();
434  const BOOLEAN isClient = ( altKey != NULL ) ? TRUE : FALSE;
435  const int checkValue = checksumData( key, keyLength );
436  int altCheckValue = 0, altPosition = DUMMY_INIT;
437  int position, altStatus = CRYPT_ERROR, status;
438 
439  assert( isWritePtr( scoreboardInfo, sizeof( SCOREBOARD_INFO ) ) );
440  assert( isReadPtr( key, keyLength ) );
441  assert( ( altKey == NULL && altKeyLength == 0 ) || \
442  isReadPtr( altKey, altKeyLength ) );
443  assert( isReadPtr( value, valueLength ) );
444  assert( isWritePtr( uniqueID, sizeof( int ) ) );
445  assert( isWritePtr( scoreboardIndex,
446  scoreboardInfo->noEntries * sizeof( SCOREBOARD_INDEX ) ) );
447 
448  REQUIRES( keyLength >= 8 && keyLength < MAX_INTLENGTH_SHORT );
449  REQUIRES( ( altKey == NULL && altKeyLength == 0 ) || \
450  ( altKey != NULL && \
451  altKeyLength >= 2 && altKeyLength < MAX_INTLENGTH_SHORT ) );
452  REQUIRES( valueLength > 0 && valueLength <= SCOREBOARD_DATA_SIZE );
453 
454  REQUIRES( sanityCheck( scoreboardInfo ) );
455 
456  /* Clear return value */
457  *uniqueID = CRYPT_ERROR;
458 
459  /* If there's something wrong with the time then we can't perform (time-
460  based) scoreboard management */
461  if( currentTime <= MIN_TIME_VALUE )
462  return( CRYPT_ERROR_NOTFOUND );
463 
464  /* Try and find this entry in the scoreboard */
465  status = findEntry( scoreboardInfo, isClient ? \
468  key, keyLength, currentTime, &position );
469  if( cryptStatusError( status ) && status != OK_SPECIAL )
470  return( status );
471  ENSURES( position >= 0 && position < scoreboardInfo->noEntries );
472  if( altKey != NULL )
473  {
474  altCheckValue = checksumData( altKey, altKeyLength );
475  altStatus = findEntry( scoreboardInfo, SCOREBOARD_KEY_FQDN,
476  altKey, altKeyLength, currentTime,
477  &altPosition );
478  if( cryptStatusError( altStatus ) && altStatus != OK_SPECIAL )
479  return( altStatus );
480  ENSURES( altPosition >= 0 && \
481  altPosition < scoreboardInfo->noEntries );
482  }
483  ENSURES( cryptStatusOK( status ) || status == OK_SPECIAL );
484  ENSURES( altKey == NULL || \
485  cryptStatusOK( altStatus ) || altStatus == OK_SPECIAL );
486 
487  /* We've done the match-checking, now we have to act on the results.
488  The different result-value settings and corresponding actions are:
489 
490  Case | sessID | FQDN | Action
491  --------+-------------------+-------------------+-----------------
492  1 | s = MT, pos = x | !altK | Add at x
493  --------+-------------------+-------------------+-----------------
494  2 | s = OK, pos = x | !altK | Add-special at x
495  --------+-------------------+-------------------+-----------------
496  3 | s = MT, pos = x | aS = MT, aPos = x | Add at x
497  --------+-------------------+-------------------+-----------------
498  4 | s = MT, pos = x | aS = OK, aPos = y | Replace at y
499  --------+-------------------+-------------------+-----------------
500  5 | s = OK, pos = x | aS = MT, aPos = y | Clear at x
501  --------+-------------------+-------------------+-----------------
502  6 | s = OK, pos = x | aS = OK, aPos = x | Add-special at x
503  --------+-------------------+-------------------+-----------------
504  7 | s = OK, pos = x | aS = OK, aPos = y | Clear at x and y */
505  if( cryptStatusOK( status ) )
506  {
507  /* We matched on the main key (session ID), handle cases 2-S, 5-C,
508  6-C and 7-C */
509  if( altKey != NULL && position != altPosition )
510  {
511  /* Cases 5-C + 7-C, clear */
512  clearScoreboardEntry( &scoreboardIndex[ position ] );
513  return( CRYPT_ERROR_NOTFOUND );
514  }
515 
516  /* Cases 2-S + 6-C, add-special */
517  ENSURES( altKey == NULL || ( cryptStatusOK( altStatus ) && \
518  position == altPosition ) );
519  scoreboardEntryPtr = &scoreboardIndex[ position ];
520  if( scoreboardEntryPtr->dataLength != valueLength || \
521  memcmp( scoreboardEntryPtr->data, value, valueLength ) )
522  {
523  /* The search keys match but the data doesn't, something funny
524  is going on */
525  clearScoreboardEntry( &scoreboardIndex[ position ] );
526  assert( DEBUG_WARN );
527  return( CRYPT_ERROR_NOTFOUND );
528  }
529  scoreboardEntryPtr->timeStamp = currentTime;
530 
531  return( CRYPT_OK );
532  }
533  REQUIRES( status == OK_SPECIAL );
534 
535  /* We didn't match on the main key (session ID), check for a match on
536  the alt.key (FQDN) */
537  if( cryptStatusOK( altStatus ) )
538  {
539  /* Case 4-C, add at location 'altPosition' */
540  ENSURES( position != altPosition );
541  scoreboardEntryPtr = &scoreboardIndex[ altPosition ];
542  }
543  else
544  {
545  /* Cases 1-S + 3-C, add at location 'position' */
546  ENSURES( altKey == NULL || \
547  ( altStatus == OK_SPECIAL && position == altPosition ) )
548  scoreboardEntryPtr = &scoreboardIndex[ position ];
549  }
550  ENSURES( scoreboardEntryPtr != NULL );
551 
552  /* Add the data to the new scoreboard entry position */
553  status = addEntryData( scoreboardEntryPtr, checkValue, key, keyLength,
554  altCheckValue, altKey, altKeyLength, value,
555  valueLength, currentTime );
556  if( cryptStatusError( status ) )
557  {
558  clearScoreboardEntry( scoreboardEntryPtr );
559  return( status );
560  }
561  *uniqueID = scoreboardEntryPtr->uniqueID = scoreboardInfo->uniqueID++;
562 
563  /* If we've used a new entry, update the position-used index */
564  if( position >= scoreboardInfo->lastEntry )
565  scoreboardInfo->lastEntry = position + 1;
566 
567  return( CRYPT_OK );
568  }
569 
570 /* Look up data in the scoreboard */
571 
572 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3, 5, 6 ) ) \
573 static int lookupScoreboard( INOUT SCOREBOARD_INFO *scoreboardInfo,
574  IN_ENUM( SCOREBOARD_KEY ) \
575  const SCOREBOARD_KEY_TYPE keyType,
576  IN_BUFFER( keyLength ) const void *key,
577  IN_LENGTH_SHORT_MIN( 8 ) const int keyLength,
578  OUT SCOREBOARD_LOOKUP_RESULT *lookupResult,
579  OUT_INT_Z int *uniqueID )
580  {
581  SCOREBOARD_INDEX *scoreboardIndex = scoreboardInfo->index;
582  SCOREBOARD_INDEX *scoreboardEntryPtr;
583  const time_t currentTime = getTime();
584  int position, status;
585 
586  assert( isWritePtr( scoreboardInfo, sizeof( SCOREBOARD_INFO ) ) );
587  assert( isReadPtr( key, keyLength ) );
588  assert( isWritePtr( lookupResult, sizeof( SCOREBOARD_LOOKUP_RESULT ) ) );
589  assert( isWritePtr( uniqueID, sizeof( int ) ) );
590  assert( isWritePtr( scoreboardIndex,
591  scoreboardInfo->noEntries * sizeof( SCOREBOARD_INDEX ) ) );
592 
593  REQUIRES( keyType > SCOREBOARD_KEY_NONE && \
594  keyType < SCOREBOARD_KEY_LAST );
595  REQUIRES( keyLength >= 8 && keyLength < MAX_INTLENGTH_SHORT );
596  REQUIRES( sanityCheck( scoreboardInfo ) );
597 
598  /* Clear return values */
599  memset( lookupResult, 0, sizeof( SCOREBOARD_LOOKUP_RESULT ) );
600  *uniqueID = CRYPT_ERROR;
601 
602  /* If there's something wrong with the time then we can't perform (time-
603  based) scoreboard management */
604  if( currentTime <= MIN_TIME_VALUE )
605  return( CRYPT_ERROR_NOTFOUND );
606 
607  /* Try and find this entry in the scoreboard */
608  status = findEntry( scoreboardInfo, keyType, key, keyLength,
609  currentTime, &position );
610  if( cryptStatusError( status ) )
611  {
612  /* An OK_SPECIAL status means that the search found an unused entry
613  position but not a matching entry (this is used by addEntry()),
614  anything else is an error */
615  return( ( status == OK_SPECIAL ) ? CRYPT_ERROR_NOTFOUND : status );
616  }
617  ENSURES( position >= 0 && position < scoreboardInfo->noEntries );
618  scoreboardEntryPtr = &scoreboardIndex[ position ];
619 
620  /* We've found a match, return a pointer to the data (which avoids
621  copying it out of secure memory) and the unique ID for it */
622  lookupResult->key = scoreboardEntryPtr->sessionID;
623  lookupResult->keySize = scoreboardEntryPtr->sessionIDlength;
624  lookupResult->data = scoreboardEntryPtr->data;
625  lookupResult->dataSize = scoreboardEntryPtr->dataLength;
626  *uniqueID = scoreboardEntryPtr->uniqueID;
627 
628  /* Update the entry's last-access date */
629  scoreboardEntryPtr->timeStamp = currentTime;
630 
631  ENSURES( sanityCheck( scoreboardInfo ) );
632 
633  return( CRYPT_OK );
634  }
635 
636 /****************************************************************************
637 * *
638 * Scoreboard Access Functions *
639 * *
640 ****************************************************************************/
641 
642 /* Add and delete entries to/from the scoreboard. These are just wrappers
643  for the local scoreboard-access function, for use by external code */
644 
646 int lookupScoreboardEntry( INOUT TYPECAST( SCOREBOARD_INFO * ) \
647  void *scoreboardInfoPtr,
648  IN_ENUM( SCOREBOARD_KEY ) \
649  const SCOREBOARD_KEY_TYPE keyType,
650  IN_BUFFER( keyLength ) const void *key,
651  IN_LENGTH_SHORT_MIN( 2 ) const int keyLength,
652  OUT SCOREBOARD_LOOKUP_RESULT *lookupResult )
653  {
654  SCOREBOARD_INFO *scoreboardInfo = scoreboardInfoPtr;
655  int uniqueID, status;
656 
657  assert( isWritePtr( scoreboardInfo, sizeof( SCOREBOARD_INFO ) ) );
658  assert( isReadPtr( key, keyLength ) );
659  assert( isWritePtr( lookupResult, sizeof( SCOREBOARD_LOOKUP_RESULT ) ) );
660 
661  REQUIRES( keyType > SCOREBOARD_KEY_NONE && \
662  keyType < SCOREBOARD_KEY_LAST );
663  REQUIRES( keyLength >= 2 && keyLength < MAX_INTLENGTH_SHORT );
664 
665  /* Clear return values */
666  memset( lookupResult, 0, sizeof( SCOREBOARD_LOOKUP_RESULT ) );
667 
668  status = krnlEnterMutex( MUTEX_SCOREBOARD );
669  if( cryptStatusError( status ) )
670  return( status );
671  status = lookupScoreboard( scoreboardInfo, keyType, key, keyLength,
672  lookupResult, &uniqueID );
674  return( cryptStatusError( status ) ? status : uniqueID );
675  }
676 
677 CHECK_RETVAL_RANGE( 0, MAX_INTLENGTH ) STDC_NONNULL_ARG( ( 1, 2, 4 ) ) \
678 int addScoreboardEntry( INOUT TYPECAST( SCOREBOARD_INFO * ) \
679  void *scoreboardInfoPtr,
680  IN_BUFFER( keyLength ) const void *key,
681  IN_LENGTH_SHORT_MIN( 8 ) const int keyLength,
682  IN_BUFFER( valueLength ) const void *value,
683  IN_LENGTH_SHORT const int valueLength )
684  {
685  SCOREBOARD_INFO *scoreboardInfo = scoreboardInfoPtr;
686  int uniqueID, status;
687 
688  assert( isWritePtr( scoreboardInfo, sizeof( SCOREBOARD_INFO ) ) );
689  assert( isReadPtr( key, keyLength ) );
690  assert( isReadPtr( value, valueLength ) );
691 
692  REQUIRES( keyLength >= 8 && keyLength < MAX_INTLENGTH_SHORT );
693  REQUIRES( valueLength > 0 && valueLength <= SCOREBOARD_DATA_SIZE );
694 
695  /* Add the entry to the scoreboard */
696  status = krnlEnterMutex( MUTEX_SCOREBOARD );
697  if( cryptStatusError( status ) )
698  return( status );
699  status = addEntry( scoreboardInfo, key, keyLength, NULL, 0,
700  ( void * ) value, valueLength, &uniqueID );
702  return( cryptStatusError( status ) ? status : uniqueID );
703  }
704 
705 CHECK_RETVAL_RANGE( 0, MAX_INTLENGTH ) STDC_NONNULL_ARG( ( 1, 2, 4, 6 ) ) \
706 int addScoreboardEntryEx( INOUT TYPECAST( SCOREBOARD_INFO * ) \
707  void *scoreboardInfoPtr,
708  IN_BUFFER( keyLength ) const void *key,
709  IN_LENGTH_SHORT_MIN( 8 ) const int keyLength,
710  IN_BUFFER( keyLength ) const void *altKey,
711  IN_LENGTH_SHORT_MIN( 2 ) const int altKeyLength,
712  IN_BUFFER( valueLength ) const void *value,
713  IN_LENGTH_SHORT const int valueLength )
714  {
715  SCOREBOARD_INFO *scoreboardInfo = scoreboardInfoPtr;
716  int uniqueID, status;
717 
718  assert( isWritePtr( scoreboardInfo, sizeof( SCOREBOARD_INFO ) ) );
719  assert( isReadPtr( key, keyLength ) );
720  assert( isReadPtr( altKey, altKeyLength ) );
721  assert( isReadPtr( value, valueLength ) );
722 
723  REQUIRES( keyLength >= 8 && keyLength < MAX_INTLENGTH_SHORT );
724  REQUIRES( altKeyLength >= 2 && altKeyLength < MAX_INTLENGTH_SHORT );
725  REQUIRES( valueLength > 0 && valueLength <= SCOREBOARD_DATA_SIZE );
726 
727  /* Add the entry to the scoreboard */
728  status = krnlEnterMutex( MUTEX_SCOREBOARD );
729  if( cryptStatusError( status ) )
730  return( status );
731  status = addEntry( scoreboardInfo, key, keyLength, altKey, altKeyLength,
732  ( void * ) value, valueLength, &uniqueID );
734  return( cryptStatusError( status ) ? status : uniqueID );
735  }
736 
737 STDC_NONNULL_ARG( ( 1 ) ) \
738 void deleteScoreboardEntry( INOUT TYPECAST( SCOREBOARD_INFO * ) \
739  void *scoreboardInfoPtr,
740  IN_INT_Z const int uniqueID )
741  {
742  SCOREBOARD_INFO *scoreboardInfo = scoreboardInfoPtr;
743  SCOREBOARD_INDEX *scoreboardIndex = scoreboardInfo->index;
744  int lastUsedEntry = -1, i, status;
745 
746  assert( isWritePtr( scoreboardInfo, sizeof( SCOREBOARD_INFO ) ) );
747 
748  REQUIRES_V( uniqueID >= 0 && \
749  uniqueID < MAX_INTLENGTH );
750 
751  status = krnlEnterMutex( MUTEX_SCOREBOARD );
752  if( cryptStatusError( status ) )
753  return;
754 
755  /* Search the scoreboard for the entry with the given ID */
756  for( i = 0; i < scoreboardInfo->lastEntry && \
757  i < FAILSAFE_ITERATIONS_MAX; i++ )
758  {
759  SCOREBOARD_INDEX *scoreboardEntryPtr = &scoreboardIndex[ i ];
760 
761  /* If it's an empty entry (due to it having expired or being
762  deleted), skip it and continue */
763  if( scoreboardEntryPtr->timeStamp <= MIN_TIME_VALUE )
764  continue;
765 
766  /* If we've found the entry that we're after, clear it and exit */
767  if( scoreboardEntryPtr->uniqueID == uniqueID )
768  {
769  clearScoreboardEntry( scoreboardEntryPtr );
770  continue;
771  }
772 
773  /* Remember how far we got */
774  lastUsedEntry = i;
775  }
776  ENSURES_V( i < FAILSAFE_ITERATIONS_MAX );
777 
778  /* Since we may have deleted entries at the end of the scoreboard, we
779  can reduce the lastEntry value to the highest remaining entry */
780  scoreboardInfo->lastEntry = lastUsedEntry + 1;
781 
783  }
784 
785 /****************************************************************************
786 * *
787 * Scoreboard Init/Shutdown *
788 * *
789 ****************************************************************************/
790 
791 /* Perform a self-test of the scoreboard functions */
792 
794 static BOOLEAN selfTest( INOUT SCOREBOARD_INFO *scoreboardInfo )
795  {
796  SCOREBOARD_LOOKUP_RESULT lookupResult;
797  int uniqueID1, uniqueID2, foundUniqueID;
798 
799  uniqueID1 = addScoreboardEntry( scoreboardInfo, "test key 1", 10,
800  "test value 1", 12 );
801  if( cryptStatusError( uniqueID1 ) )
802  return( FALSE );
803  uniqueID2 = addScoreboardEntry( scoreboardInfo, "test key 2", 10,
804  "test value 2", 12 );
805  if( cryptStatusError( uniqueID2 ) )
806  return( FALSE );
807  foundUniqueID = lookupScoreboardEntry( scoreboardInfo,
808  SCOREBOARD_KEY_SESSIONID_SVR, "test key 1", 10,
809  &lookupResult );
810  if( cryptStatusError( foundUniqueID ) )
811  return( FALSE );
812  if( foundUniqueID != uniqueID1 || \
813  lookupResult.keySize != 10 || \
814  memcmp( lookupResult.key, "test key 1", 10 ) || \
815  lookupResult.dataSize != 12 || \
816  memcmp( lookupResult.data, "test value 1", 12 ) )
817  return( FALSE );
818  deleteScoreboardEntry( scoreboardInfo, uniqueID1 );
819  foundUniqueID = lookupScoreboardEntry( scoreboardInfo,
820  SCOREBOARD_KEY_SESSIONID_SVR, "test key 1", 10,
821  &lookupResult );
822  if( foundUniqueID != CRYPT_ERROR_NOTFOUND )
823  return( FALSE );
824  deleteScoreboardEntry( scoreboardInfo, uniqueID2 );
825  if( scoreboardInfo->lastEntry != 0 || \
826  scoreboardInfo->uniqueID != 2 )
827  return( FALSE );
828 
829  return( TRUE );
830  }
831 
832 /* Initialise and shut down the scoreboard */
833 
835 int initScoreboard( INOUT TYPECAST( SCOREBOARD_INFO * ) \
836  void *scoreboardInfoPtr,
837  IN_LENGTH_SHORT_MIN( SCOREBOARD_MIN_SIZE ) \
838  const int scoreboardEntries )
839  {
840  SCOREBOARD_INFO *scoreboardInfo = scoreboardInfoPtr;
841  SCOREBOARD_INDEX *scoreboardIndex;
842  SCOREBOARD_DATA *scoreboardData;
843  int i, status;
844 
845  assert( isWritePtr( scoreboardInfo, sizeof( SCOREBOARD_INFO ) ) );
846 
847  static_assert( sizeof( SCOREBOARD_STATE ) >= sizeof( SCOREBOARD_INFO ), \
848  "Scoreboard size" );
849 
850  REQUIRES( scoreboardEntries >= SCOREBOARD_MIN_SIZE && \
851  scoreboardEntries <= SCOREBOARD_MAX_SIZE );
852 
853  status = krnlEnterMutex( MUTEX_SCOREBOARD );
854  if( cryptStatusError( status ) )
855  return( status );
856 
857  /* Initialise the scoreboard */
858  memset( scoreboardInfo, 0, sizeof( SCOREBOARD_INFO ) );
859  scoreboardInfo->uniqueID = 0;
860  scoreboardInfo->lastEntry = 0;
861  scoreboardInfo->noEntries = scoreboardEntries;
862 
863  /* Initialise the scoreboard data */
864  if( ( scoreboardInfo->index = clAlloc( "initScoreboard", \
865  scoreboardEntries * sizeof( SCOREBOARD_INDEX ) ) ) == NULL )
866  return( CRYPT_ERROR_MEMORY );
867  status = krnlMemalloc( &scoreboardInfo->data, \
868  scoreboardEntries * sizeof( SCOREBOARD_DATA ) );
869  if( cryptStatusError( status ) )
870  {
871  clFree( "initScoreboard", scoreboardInfo->index );
872  memset( scoreboardInfo, 0, sizeof( SCOREBOARD_INFO ) );
873  return( status );
874  }
875  scoreboardIndex = scoreboardInfo->index;
876  scoreboardData = scoreboardInfo->data;
877  memset( scoreboardIndex, 0, \
878  scoreboardEntries * sizeof( SCOREBOARD_INDEX ) );
879  for( i = 0; i < scoreboardEntries; i++ )
880  {
881  scoreboardIndex[ i ].data = &scoreboardData[ i ];
882  scoreboardIndex[ i ].dataLength = 0;
883  }
884  memset( scoreboardInfo->data, 0, \
885  scoreboardEntries * sizeof( SCOREBOARD_DATA ) );
886 
887  /* Make sure that everything's working as intended */
888  if( !selfTest( scoreboardInfo ) )
889  {
890  status = krnlMemfree( ( void ** ) &scoreboardInfo->data );
891  ENSURES( cryptStatusOK( status ) );
892  clFree( "initScoreboard", scoreboardInfo->index );
893  memset( scoreboardInfo, 0, sizeof( SCOREBOARD_INFO ) );
894 
895  retIntError();
896  }
897 
899  return( CRYPT_OK );
900  }
901 
902 STDC_NONNULL_ARG( ( 1 ) ) \
903 void endScoreboard( INOUT TYPECAST( SCOREBOARD_INFO * ) \
904  void *scoreboardInfoPtr )
905  {
906  SCOREBOARD_INFO *scoreboardInfo = scoreboardInfoPtr;
907  int status;
908 
909  assert( isWritePtr( scoreboardInfo, sizeof( SCOREBOARD_INFO ) ) );
910 
911  /* Shut down the scoreboard. We acquire the mutex while we're doing
912  this to ensure that any threads still using it have exited before we
913  destroy it. Exactly what to do if we can't acquire the mutex is a
914  bit complicated because failing to acquire the mutex is a special-
915  case exception condition so it's not even possible to plan for this
916  since it's uncertain under which conditions (if ever) it would
917  occur. For now we play it by the book and don't do anything if we
918  can't acquire the mutex, which is at least consistent */
919  status = krnlEnterMutex( MUTEX_SCOREBOARD );
920  ENSURES_V( cryptStatusOK( status ) ); /* See comment above */
921 
922  /* Clear and free the scoreboard */
923  status = krnlMemfree( ( void ** ) &scoreboardInfo->data );
924  ENSURES_V( cryptStatusOK( status ) ); /* See comment above */
925  zeroise( scoreboardInfo->index, \
926  scoreboardInfo->noEntries * sizeof( SCOREBOARD_INDEX ) );
927  clFree( "endScoreboard", scoreboardInfo->index );
928  zeroise( scoreboardInfo, sizeof( SCOREBOARD_INFO ) );
929 
931  }
932 #endif /* USE_SSL */