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