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