1 /****************************************************************************
2 *																			*
3 *			cryptlib SSHv2 Client-side Authentication Management			*
4 *						Copyright Peter Gutmann 1998-2008					*
5 *																			*
6 ****************************************************************************/
7 
8 #if defined( INC_ALL )
9   #include "crypt.h"
10   #include "misc_rw.h"
11   #include "session.h"
12   #include "ssh.h"
13 #else
14   #include "crypt.h"
15   #include "enc_dec/misc_rw.h"
16   #include "session/session.h"
17   #include "session/ssh.h"
18 #endif /* Compiler-specific includes */
19 
20 /* The SSH authentication process is awkward and complex and even with the
21    significant simplifications applied here still ends up a lot more
22    convoluted than it should be, particularly when we have to add all sorts
23    of special-case handling for valid but unusual interpretations of the
24    spec.  The overall control flow (excluding handling of special-case
25    peculiarities) is:
26 
27 	reportAuthFailure:
28 		read allowed methods;
29 		if( method == PAM && !no_more_PAM )
30 			return( processPAM() );
31 		return error;
32 
33 	processPAM:
34 		perform PAM authentication;
35 		if( fail )
36 			return( reportAuthFailure( no_more_PAM ) );
37 		return error/success;
38 
39 	processClientAuth:
40 		send auth;
41 		if( success )
42 			return success;
43 		return( reportAuthFailure() ); */
44 
45 #ifdef USE_SSH
46 
47 /* Tables mapping SSH algorithm names to cryptlib algorithm IDs, in
48    preferred algorithm order.  There are two of these, one that favours
49    password-based authentication and one that favours PKC-based
50    authentication, depending on whether the user has specified a password
51    or a PKC as their authentication choice.  This is required in order to
52    handle SSH's weird way of reporting authentication failures, see the
53    comment in reportAuthFailure() for details */
54 
55 static const ALGO_STRING_INFO FAR_BSS algoStringUserauthentPWTbl[] = {
56 	{ "password", 8, MK_ALGO( PSEUDOALGO_PASSWORD ) },
57 	{ "keyboard-interactive", 20, MK_ALGO( PSEUDOALGO_PAM ) },
58 	{ "publickey", 9, CRYPT_ALGO_RSA },
59 	{ NULL, 0, CRYPT_ALGO_NONE }, { NULL, 0, CRYPT_ALGO_NONE }
60 	};
61 static const ALGO_STRING_INFO FAR_BSS algoStringUserauthentPKCTbl[] = {
62 	{ "publickey", 9, CRYPT_ALGO_RSA },
63 	{ "password", 8, MK_ALGO( PSEUDOALGO_PASSWORD ) },
64 	{ "keyboard-interactive", 20, MK_ALGO( PSEUDOALGO_PAM ) },
65 	{ NULL, 0, CRYPT_ALGO_NONE }, { NULL, CRYPT_ALGO_NONE }
66 	};
67 
68 /* Forward declaration for authentication function */
69 
70 CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
71 static int processPamAuthentication( INOUT SESSION_INFO *sessionInfoPtr );
72 
73 /****************************************************************************
74 *																			*
75 *							Utility Functions								*
76 *																			*
77 ****************************************************************************/
78 
79 /* Send a dummy authentication request, needed under various circumstances
80    for some buggy servers */
81 
82 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
sendDummyAuth(INOUT SESSION_INFO * sessionInfoPtr,IN_BUFFER (userNameLength)const char * userName,IN_LENGTH_TEXT const int userNameLength)83 static int sendDummyAuth( INOUT SESSION_INFO *sessionInfoPtr,
84 						  IN_BUFFER( userNameLength ) const char *userName,
85 						  IN_LENGTH_TEXT const int userNameLength )
86 	{
87 	STREAM stream;
88 	int status;
89 
90 	assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
91 	assert( isReadPtr( userName, userNameLength ) );
92 
93 	REQUIRES( userNameLength > 0 && userNameLength <= CRYPT_MAX_TEXTSIZE );
94 
95 	/* Send the dummy authentication request to the server */
96 	status = openPacketStreamSSH( &stream, sessionInfoPtr,
97 								  SSH_MSG_USERAUTH_REQUEST );
98 	if( cryptStatusError( status ) )
99 		return( status );
100 	writeString32( &stream, userName, userNameLength );
101 	writeString32( &stream, "ssh-connection", 14 );
102 	status = writeString32( &stream, "none", 4 );
103 	if( cryptStatusOK( status ) )
104 		status = wrapPacketSSH2( sessionInfoPtr, &stream, 0, FALSE, TRUE );
105 	if( cryptStatusOK( status ) )
106 		status = sendPacketSSH2( sessionInfoPtr, &stream, TRUE );
107 	sMemDisconnect( &stream );
108 	if( cryptStatusError( status ) )
109 		return( status );
110 
111 	/* Wait for the server's ack of the authentication.  In theory since
112 	   this is just something used to de-confuse the buggy Tectia/ssh.com
113 	   server we can ignore the content (as long as the packet's valid) as
114 	   any authentication problems will be resolved by the real
115 	   authentication process below, but in some rare cases we can encounter
116 	   oddball devices that don't perform any authentication at the SSH
117 	   level but instead ask for a password via the protocol being tunneled
118 	   over SSH, which means that we'll get a USERAUTH_SUCCESS at this point
119 	   even if we haven't actually authenticated ourselves */
120 	status = readHSPacketSSH2( sessionInfoPtr, SSH_MSG_SPECIAL_USERAUTH,
121 							   ID_SIZE );
122 	if( cryptStatusError( status ) )
123 		return( status );
124 	if( sessionInfoPtr->sessionSSH->packetType == SSH_MSG_USERAUTH_SUCCESS )
125 		{
126 		/* We're (non-)authenticated, we don't have to do anything
127 		   further */
128 		return( OK_SPECIAL );
129 		}
130 
131 	return( CRYPT_OK );
132 	}
133 
134 /* Report specific details on an authentication failure to the caller */
135 
136 CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
reportAuthFailure(INOUT SESSION_INFO * sessionInfoPtr,IN_LENGTH_SHORT const int length,const BOOLEAN isPamAuth)137 static int reportAuthFailure( INOUT SESSION_INFO *sessionInfoPtr,
138 							  IN_LENGTH_SHORT const int length,
139 							  const BOOLEAN isPamAuth )
140 	{
141 	STREAM stream;
142 	CRYPT_ALGO_TYPE authentAlgo;
143 	const BOOLEAN hasPassword = \
144 			( findSessionInfo( sessionInfoPtr->attributeList,
145 							   CRYPT_SESSINFO_PASSWORD ) != NULL ) ? \
146 			TRUE : FALSE;
147 	int status;
148 
149 	assert( isReadPtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
150 
151 	REQUIRES( length > 0 && length < MAX_INTLENGTH_SHORT );
152 
153 	/* The authentication failed, pick apart the response to see if we can
154 	   return more meaningful error information:
155 
156 		byte	type = SSH_MSG_USERAUTH_FAILURE
157 		string	available_auth_types
158 		boolean	partial_success
159 
160 	  We decode the response to favour password- or PKC-based
161 	  authentication depending on whether the user specified a password
162 	  or a PKC as their authentication choice.
163 
164 	  God knows how the partial_success flag is really meant to be applied
165 	  (there are a whole pile of odd conditions surrounding changed
166 	  passwords and similar issues), according to the spec it means that the
167 	  authentication was successful, however the packet type indicates that
168 	  the authentication failed and something else is needed.  This whole
169 	  section of the protocol winds up in an extremely complex state machine
170 	  with all sorts of special-case conditions, several of which require
171 	  manual intervention by the user in order to continue.  It's easiest to
172 	  not even try and handle this stuff */
173 	ENSURES( rangeCheckZ( 0, length, sessionInfoPtr->receiveBufSize ) );
174 	sMemConnect( &stream, sessionInfoPtr->receiveBuffer, length );
175 	if( hasPassword )
176 		{
177 		status = readAlgoString( &stream, algoStringUserauthentPWTbl,
178 								 FAILSAFE_ARRAYSIZE( algoStringUserauthentPWTbl, \
179 													 ALGO_STRING_INFO ),
180 								 &authentAlgo, FALSE, SESSION_ERRINFO );
181 		}
182 	else
183 		{
184 		status = readAlgoString( &stream, algoStringUserauthentPKCTbl,
185 								 FAILSAFE_ARRAYSIZE( algoStringUserauthentPKCTbl, \
186 													 ALGO_STRING_INFO ),
187 								 &authentAlgo, FALSE, SESSION_ERRINFO );
188 		}
189 	sMemDisconnect( &stream );
190 	if( cryptStatusError( status ) )
191 		{
192 		/* If the problem is due to lack of a compatible algorithm, make the
193 		   error message a bit more specific to tell the user that we got
194 		   through most of the handshake but failed at the authentication
195 		   stage */
196 		if( status == CRYPT_ERROR_NOTAVAIL )
197 			{
198 			retExt( CRYPT_ERROR_NOTAVAIL,
199 					( CRYPT_ERROR_NOTAVAIL, SESSION_ERRINFO,
200 					  "Remote system supports neither password nor "
201 					  "public-key authentication" ) );
202 			}
203 
204 		/* Some buggy implementations return an authentication failure with
205 		   the available-authentication types string empty to indicate that
206 		   no authentication is required rather than returning an
207 		   authentication success status as required by the spec (although
208 		   the problem is really in the spec and not in the interpretation
209 		   since there's no way to say that "no-auth" is a valid
210 		   authentication type).  If we find one of these then we return an
211 		   OK_SPECIAL status to let the caller know that they should retry
212 		   the authentication.  Because this is a broken packet format we
213 		   have to check the encoded form rather than being able to read it
214 		   as normal */
215 		if( ( sessionInfoPtr->protocolFlags & SSH_PFLAG_EMPTYUSERAUTH ) && \
216 			length >= LENGTH_SIZE && \
217 			!memcmp( sessionInfoPtr->receiveBuffer,
218 					 "\x00\x00\x00\x00", LENGTH_SIZE ) )
219 			{
220 			/* It's a garbled attempt to tell us that no authentication is
221 			   required, tell the caller to try again without
222 			   authentication */
223 			return( OK_SPECIAL );
224 			}
225 
226 		/* There was some other problem with the returned information, we
227 		   still report it as a failed-authentication error but leave the
228 		   extended error information in place to let the caller see what
229 		   the underlying cause was */
230 		return( CRYPT_ERROR_WRONGKEY );
231 		}
232 
233 	/* If we tried straight password authentication and the server requested
234 	   keyboard-interactive (== misnamed PAM) authentication try again using
235 	   PAM authentication unless we've already been called as a result of
236 	   failed PAM authentication */
237 	if( !isPamAuth && hasPassword && authentAlgo == CRYPT_PSEUDOALGO_PAM )
238 		return( processPamAuthentication( sessionInfoPtr ) );
239 
240 	/* SSH reports authentication failures in a somewhat bizarre way,
241 	   instead of saying "authentication failed" it returns a list of
242 	   allowed authentication methods, one of which may be the one that we
243 	   just used.  To figure out whether a problem occurred because we used
244 	   the wrong authentication method or because we submitted the wrong
245 	   authentication value we have to perform a complex decode and match of
246 	   the information in the returned packet with what we sent */
247 	if( !hasPassword )
248 		{
249 		/* If we used a PKC and the server wants a password, report the
250 		   error as a missing password */
251 		if( authentAlgo == CRYPT_PSEUDOALGO_PASSWORD || \
252 			authentAlgo == CRYPT_PSEUDOALGO_PAM )
253 			{
254 			setErrorInfo( sessionInfoPtr, CRYPT_SESSINFO_PASSWORD,
255 						  CRYPT_ERRTYPE_ATTR_ABSENT );
256 			retExt( CRYPT_ERROR_NOTINITED,
257 					( CRYPT_ERROR_NOTINITED, SESSION_ERRINFO,
258 					  "Server requested password authentication but only a "
259 					  "public/private key was available" ) );
260 			}
261 
262 		retExt( CRYPT_ERROR_WRONGKEY,
263 				( CRYPT_ERROR_WRONGKEY, SESSION_ERRINFO,
264 				  "Server reported: Invalid public-key authentication" ) );
265 		}
266 
267 	/* If we used a password and the server wants a PKC, report the error
268 	   as a missing private key */
269 	if( isSigAlgo( authentAlgo ) )
270 		{
271 		setErrorInfo( sessionInfoPtr, CRYPT_SESSINFO_PRIVATEKEY,
272 					  CRYPT_ERRTYPE_ATTR_ABSENT );
273 		retExt( CRYPT_ERROR_NOTINITED,
274 				( CRYPT_ERROR_NOTINITED, SESSION_ERRINFO,
275 				  "Server requested public-key authentication but only a "
276 				  "password was available" ) );
277 		}
278 
279 	retExt( CRYPT_ERROR_WRONGKEY,
280 			( CRYPT_ERROR_WRONGKEY, SESSION_ERRINFO,
281 			  "Server reported: Invalid password" ) );
282 	}
283 
284 /* Create a public-key authentication packet */
285 
286 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2, 3, 4 ) ) \
createPubkeyAuth(const SESSION_INFO * sessionInfoPtr,const SSH_HANDSHAKE_INFO * handshakeInfo,INOUT STREAM * stream,const ATTRIBUTE_LIST * userNamePtr)287 static int createPubkeyAuth( const SESSION_INFO *sessionInfoPtr,
288 							 const SSH_HANDSHAKE_INFO *handshakeInfo,
289 							 INOUT STREAM *stream,
290 							 const ATTRIBUTE_LIST *userNamePtr )
291 	{
292 	MESSAGE_CREATEOBJECT_INFO createInfo;
293 	void *sigDataPtr, *packetDataPtr;
294 	int sigDataLength, packetDataLength;
295 	int sigOffset, sigLength DUMMY_INIT, pkcAlgo, status;
296 
297 	assert( isReadPtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
298 	assert( isReadPtr( handshakeInfo, sizeof( SSH_HANDSHAKE_INFO ) ) );
299 	assert( isWritePtr( stream, sizeof( STREAM ) ) );
300 	assert( isReadPtr( userNamePtr, sizeof( ATTRIBUTE_LIST ) ) );
301 
302 	status = krnlSendMessage( sessionInfoPtr->privateKey,
303 							  IMESSAGE_GETATTRIBUTE, &pkcAlgo,
304 							  CRYPT_CTXINFO_ALGO );
305 	if( cryptStatusError( status ) )
306 		return( status );
307 
308 	/*	byte	type = SSH_MSG_USERAUTH_REQUEST
309 		string	user_name
310 		string	service_name = "ssh-connection"
311 		string	method-name = "publickey"
312 		boolean	TRUE
313 		string		"ssh-rsa"	"ssh-dss"
314 		string		[ client key/certificate ]
315 			string	"ssh-rsa"	"ssh-dss"
316 			mpint	e			p
317 			mpint	n			q
318 			mpint				g
319 			mpint				y
320 		string		[ client signature ]
321 			string	"ssh-rsa"	"ssh-dss"
322 			string	signature	signature.
323 
324 	   Note the doubled-up algorithm name, the spec first requires that the
325 	   public-key authentication packet send the algorithm name and then
326 	   includes it a second time as part of the client key information (and
327 	   then just for good measure specifies it a third time in the
328 	   signature) */
329 	streamBookmarkSetFullPacket( stream, packetDataLength );
330 	writeString32( stream, userNamePtr->value, userNamePtr->valueLength );
331 	writeString32( stream, "ssh-connection", 14 );
332 	writeString32( stream, "publickey", 9 );
333 	sputc( stream, 1 );
334 	status = writeAlgoString( stream, pkcAlgo );
335 	if( cryptStatusError( status ) )
336 		return( status );
337 	status = exportAttributeToStream( stream, sessionInfoPtr->privateKey,
338 									  CRYPT_IATTRIBUTE_KEY_SSH );
339 	if( cryptStatusError( status ) )
340 		return( status );
341 	status = streamBookmarkComplete( stream, &packetDataPtr,
342 									 &packetDataLength, packetDataLength );
343 	if( cryptStatusError( status ) )
344 		return( status );
345 
346 	/* Hash the authentication request data, composed of:
347 
348 		string		exchange hash
349 		[ SSH_MSG_USERAUTH_REQUEST packet payload up to signature start ] */
350 	setMessageCreateObjectInfo( &createInfo, handshakeInfo->hashAlgo );
351 	status = krnlSendMessage( SYSTEM_OBJECT_HANDLE,
352 							  IMESSAGE_DEV_CREATEOBJECT, &createInfo,
353 							  OBJECT_TYPE_CONTEXT );
354 	if( cryptStatusError( status ) )
355 		return( status );
356 	if( sessionInfoPtr->protocolFlags & SSH_PFLAG_NOHASHLENGTH )
357 		{
358 		/* Some implementations erroneously omit the length when hashing the
359 		   exchange hash */
360 		status = krnlSendMessage( createInfo.cryptHandle, IMESSAGE_CTX_HASH,
361 								  ( MESSAGE_CAST ) handshakeInfo->sessionID,
362 								  handshakeInfo->sessionIDlength );
363 		}
364 	else
365 		{
366 		status = hashAsString( createInfo.cryptHandle,
367 							   handshakeInfo->sessionID,
368 							   handshakeInfo->sessionIDlength );
369 		}
370 	if( cryptStatusOK( status ) )
371 		status = krnlSendMessage( createInfo.cryptHandle, IMESSAGE_CTX_HASH,
372 								  packetDataPtr, packetDataLength );
373 	if( cryptStatusOK( status ) )
374 		status = krnlSendMessage( createInfo.cryptHandle,
375 								  IMESSAGE_CTX_HASH, "", 0 );
376 	if( cryptStatusError( status ) )
377 		{
378 		krnlSendNotifier( createInfo.cryptHandle, IMESSAGE_DECREFCOUNT );
379 		return( status );
380 		}
381 
382 	/* Sign the hash.  The reason for the min() part of the expression is
383 	   that iCryptCreateSignature() gets suspicious of very large buffer
384 	   sizes, for example when the user has specified the use of a huge send
385 	   buffer */
386 	sigOffset = stell( stream );	/* Needed for later bug workaround */
387 	status = sMemGetDataBlockRemaining( stream, &sigDataPtr, &sigDataLength );
388 	if( cryptStatusOK( status ) )
389 		{
390 		status = iCryptCreateSignature( sigDataPtr,
391 						min( sigDataLength, MAX_INTLENGTH_SHORT - 1 ),
392 						&sigLength, CRYPT_IFORMAT_SSH,
393 						sessionInfoPtr->privateKey, createInfo.cryptHandle,
394 						NULL );
395 		}
396 	if( cryptStatusOK( status ) )
397 		status = sSkip( stream, sigLength, MAX_INTLENGTH_SHORT );
398 	krnlSendNotifier( createInfo.cryptHandle, IMESSAGE_DECREFCOUNT );
399 	if( cryptStatusError( status ) )
400 		return( status );
401 
402 	/* Some buggy implementations require that RSA signatures be padded with
403 	   zeroes to the full modulus size, mysteriously failing the
404 	   authentication in a small number of randomly-distributed cases when
405 	   the signature format happens to be less than the modulus size.  To
406 	   handle this we have to rewrite the signature to include the extra
407 	   padding bytes */
408 	if( ( sessionInfoPtr->protocolFlags & SSH_PFLAG_RSASIGPAD ) && \
409 		pkcAlgo == CRYPT_ALGO_RSA )
410 		{
411 		BYTE sigDataBuffer[ CRYPT_MAX_PKCSIZE + 8 ];
412 		int sigSize, keySize, delta, i;
413 
414 		status = krnlSendMessage( sessionInfoPtr->privateKey,
415 								  IMESSAGE_GETATTRIBUTE, &keySize,
416 								  CRYPT_CTXINFO_KEYSIZE );
417 		if( cryptStatusError( status ) )
418 			return( status );
419 
420 		/* Read the signature length and check whether it needs padding.
421 		   Note that we read the signature data with readString32() rather
422 		   than readInteger32() to ensure that we get the raw signature data
423 		   exactly as written rather than the cleaned-up integer value */
424 		sseek( stream, sigOffset );
425 		readUint32( stream );
426 		readUniversal32( stream );
427 		status = readString32( stream, sigDataBuffer, CRYPT_MAX_PKCSIZE,
428 							   &sigSize );
429 		ENSURES( cryptStatusOK( status ) );
430 		if( sigSize >= keySize )
431 			{
432 			/* The signature size is the same as the modulus size, there's
433 			   nothing to do.  The reads above have reset the stream-
434 			   position indicator to the end of the signature data so
435 			   there's no need to perform an explicit seek before exiting */
436 			return( CRYPT_OK );
437 			}
438 
439 		/* We've got a signature that's shorter than the RSA modulus, we
440 		   need to rewrite it to pad it out to the modulus size:
441 
442 		  sigOfs				  sigDataBuffer
443 			|							|
444 			v uint32	string32		v
445 			+-------+---------------+---+-------------------+
446 			| length|	algo-name	|pad|	sigData			|
447 			+-------+---------------+---+-------------------+
448 			|						|<+>|<---- sigSize ---->|
449 			|						delta					|
450 			|						|<------ keySize ------>|
451 			|<----------------- sigLength ----------------->| */
452 		delta = keySize - sigSize;
453 		if( sigLength + delta > sigDataLength )
454 			return( CRYPT_ERROR_OVERFLOW );
455 		sseek( stream, sigOffset );
456 		writeUint32( stream, sizeofString32( 7 ) + \
457 							 sizeofString32( keySize ) );
458 		writeString32( stream, "ssh-rsa", 7 );
459 		writeUint32( stream, keySize );
460 		for( i = 0; i < delta; i++ )
461 			sputc( stream, 0 );
462 		return( swrite( stream, sigDataBuffer, sigSize ) );
463 		}
464 
465 	return( CRYPT_OK );
466 	}
467 
468 /****************************************************************************
469 *																			*
470 *							Perform PAM Authentication						*
471 *																			*
472 ****************************************************************************/
473 
474 /* Perform a single round of PAM authentication */
475 
476 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
pamAuthenticate(INOUT SESSION_INFO * sessionInfoPtr,IN_BUFFER (pamRequestDataLength)const void * pamRequestData,IN_LENGTH_SHORT const int pamRequestDataLength)477 static int pamAuthenticate( INOUT SESSION_INFO *sessionInfoPtr,
478 							IN_BUFFER( pamRequestDataLength ) \
479 								const void *pamRequestData,
480 							IN_LENGTH_SHORT const int pamRequestDataLength )
481 	{
482 	const ATTRIBUTE_LIST *passwordPtr = \
483 				findSessionInfo( sessionInfoPtr->attributeList,
484 								 CRYPT_SESSINFO_PASSWORD );
485 	STREAM stream;
486 	BYTE nameBuffer[ CRYPT_MAX_TEXTSIZE + 8 ];
487 	BYTE promptBuffer[ CRYPT_MAX_TEXTSIZE + 8 ];
488 	int nameLength, promptLength = -1, noPrompts = -1;
489 	int i, iterationCount, status;
490 
491 	assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
492 	assert( isReadPtr( pamRequestData, pamRequestDataLength ) );
493 
494 	REQUIRES( pamRequestDataLength > 0 && \
495 			  pamRequestDataLength < MAX_INTLENGTH_SHORT );
496 
497 	/* Process the PAM user-auth request:
498 
499 		byte	type = SSH_MSG_USERAUTH_INFO_REQUEST
500 		string	name
501 		string	instruction
502 		string	language = {}
503 		int		num_prompts
504 			string	prompt[ n ]
505 			boolean	echo[ n ]
506 
507 	   Exactly who or what's name is supplied or what the instruction field
508 	   is for is left unspecified by the spec (and they may indeed be left
509 	   empty) so we just skip it.  Many implementations feel similarly about
510 	   this and leave the fields empty.  In addition the spec allows
511 	   num_prompts to be zero in one location (stating that the usually-
512 	   absent name and instruction field should be displayed to the user)
513 	   but then prohibits the prompt from being empty in another location,
514 	   so we require a nonzero number of prompts.
515 
516 	   If the PAM authentication (from a previous iteration) fails or
517 	   succeeds then the server is supposed to send back a standard user-
518 	   auth success or failure status but could also send another
519 	   SSH_MSG_USERAUTH_INFO_REQUEST even if it contains no payload (an
520 	   OpenSSH bug) so we have to handle this as a special case */
521 	sMemConnect( &stream, pamRequestData, pamRequestDataLength );
522 	status = readString32( &stream, nameBuffer, CRYPT_MAX_TEXTSIZE,
523 						   &nameLength );		/* Name */
524 	if( cryptStatusOK( status ) )
525 		{
526 		readUniversal32( &stream );				/* Instruction */
527 		readUniversal32( &stream );				/* Language */
528 		status = noPrompts = readUint32( &stream );	/* No.prompts */
529 		if( !cryptStatusError( status ) )
530 			{
531 			status = CRYPT_OK;	/* readUint32() returns a count value */
532 			if( noPrompts <= 0 || noPrompts > 4 )
533 				{
534 				/* Requesting zero or more than a small number of prompts is
535 				   suspicious */
536 				status = CRYPT_ERROR_BADDATA;
537 				}
538 			}
539 		}
540 	if( cryptStatusOK( status ) )
541 		{
542 		status = readString32( &stream, promptBuffer,
543 							   CRYPT_MAX_TEXTSIZE, &promptLength );
544 		if( cryptStatusOK( status ) && promptLength <= 0 )
545 			{
546 			/* We must have at least some sort of prompt given that we
547 			   require num_prompts to be nonzero */
548 			status = CRYPT_ERROR_BADDATA;
549 			}
550 		}
551 	sMemDisconnect( &stream );
552 	if( cryptStatusError( status ) )
553 		{
554 		retExt( status,
555 				( status, SESSION_ERRINFO,
556 				  "Invalid PAM authentication request packet" ) );
557 		}
558 
559 	/* Make sure that we're being asked for some form of password
560 	   authentication.  This assumes that the prompt string begins with the
561 	   word "password" (which always seems to be the case), if it isn't then
562 	   it may be necessary to do a substring search */
563 	if( promptLength < 8 || \
564 		!strIsPrintable( promptBuffer, promptLength ) || \
565 		strCompare( promptBuffer, "Password", 8 ) )
566 		{
567 		/* The following may produce somewhat inconsistent results in terms
568 		   of what it reports because it's unclear what 'name' actually is,
569 		   on the off chance that something fills this in it could produce
570 		   a less appropriate error message than the prompt, but we
571 		   opportunistically try it in case it contains something useful */
572 		retExt( CRYPT_ERROR_BADDATA,
573 				( CRYPT_ERROR_BADDATA, SESSION_ERRINFO,
574 				  "Server requested unknown PAM authentication type '%s'",
575 				  ( nameLength > 0 ) ? \
576 				  sanitiseString( nameBuffer, CRYPT_MAX_TEXTSIZE, \
577 								  nameLength ) : \
578 				  sanitiseString( promptBuffer, CRYPT_MAX_TEXTSIZE, \
579 								  promptLength ) ) );
580 		}
581 
582 	REQUIRES( passwordPtr != NULL && \
583 			  passwordPtr->valueLength > 0 && \
584 			  passwordPtr->valueLength <= CRYPT_MAX_TEXTSIZE );
585 
586 	/* Send back the PAM user-auth response:
587 
588 		byte	type = SSH_MSG_USERAUTH_INFO_RESPONSE
589 		int		num_responses = num_prompts
590 		string	response
591 
592 	   What to do if there's more than one prompt is a bit tricky, usually
593 	   PAM is used as a form of (awkward) password authentication and
594 	   there's only a single prompt, if we ever encounter a situation where
595 	   there's more than one prompt then it's probably a request to confirm
596 	   the password so we just send it again for successive prompts */
597 	status = openPacketStreamSSH( &stream, sessionInfoPtr,
598 								  SSH_MSG_USERAUTH_INFO_RESPONSE );
599 	if( cryptStatusError( status ) )
600 		return( status );
601 	status = writeUint32( &stream, noPrompts );
602 	for( i = 0, iterationCount = 0;
603 		 cryptStatusOK( status ) && i < noPrompts && \
604 			iterationCount < FAILSAFE_ITERATIONS_MED;
605 		 i++, iterationCount++ )
606 		{
607 		status = writeString32( &stream, passwordPtr->value,
608 								passwordPtr->valueLength );
609 		}
610 	ENSURES( iterationCount < FAILSAFE_ITERATIONS_MED );
611 	if( cryptStatusOK( status ) )
612 		status = sendPacketSSH2( sessionInfoPtr, &stream, FALSE );
613 	sMemDisconnect( &stream );
614 
615 	return( status );
616 	}
617 
618 /* Handle PAM authentication */
619 
620 CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
processPamAuthentication(INOUT SESSION_INFO * sessionInfoPtr)621 static int processPamAuthentication( INOUT SESSION_INFO *sessionInfoPtr )
622 	{
623 	const ATTRIBUTE_LIST *userNamePtr = \
624 				findSessionInfo( sessionInfoPtr->attributeList,
625 								 CRYPT_SESSINFO_USERNAME );
626 	STREAM stream;
627 	int length, pamIteration, status;
628 
629 	assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
630 	assert( isReadPtr( userNamePtr, sizeof( ATTRIBUTE_LIST ) ) );
631 
632 	REQUIRES( userNamePtr != NULL && \
633 			  userNamePtr->valueLength > 0 && \
634 			  userNamePtr->valueLength <= CRYPT_MAX_TEXTSIZE );
635 
636 	/* Send a user-auth request asking for PAM authentication:
637 
638 		byte	type = SSH_MSG_USERAUTH_REQUEST
639 		string	user_name
640 		string	service_name = "ssh-connection"
641 		string	method_name = "keyboard-interactive"
642 		string	language = ""
643 		string	sub_methods = "password"
644 
645 	   The sub-methods are implementation-dependent and the spec suggests an
646 	   implementation strategy in which the server ignores them so
647 	   specifying anything here is mostly wishful thinking, but we ask for
648 	   password authentication anyway in case it helps */
649 	status = openPacketStreamSSH( &stream, sessionInfoPtr,
650 								  SSH_MSG_USERAUTH_REQUEST );
651 	if( cryptStatusError( status ) )
652 		return( status );
653 	writeString32( &stream, userNamePtr->value, userNamePtr->valueLength );
654 	writeString32( &stream, "ssh-connection", 14 );
655 	writeString32( &stream, "keyboard-interactive", 20 );
656 	writeUint32( &stream, 0 );		/* No language tag */
657 	if( sessionInfoPtr->protocolFlags & SSH_PFLAG_PAMPW )
658 		{
659 		/* Some servers choke if we supply a sub-method hint for the
660 		   authentication */
661 		status = writeUint32( &stream, 0 );
662 		}
663 	else
664 		status = writeString32( &stream, "password", 8 );
665 	if( cryptStatusOK( status ) )
666 		status = sendPacketSSH2( sessionInfoPtr, &stream, FALSE );
667 	sMemDisconnect( &stream );
668 	if( cryptStatusError( status ) )
669 		return( status );
670 
671 	/* Handle the PAM negotiation.  This can (in theory) go on indefinitely,
672 	   to avoid potential DoS problems we limit it to five iterations.  In
673 	   general we'll only need two iterations (or three for OpenSSH's empty-
674 	   message bug) so we shouldn't ever get to five */
675 	for( pamIteration = 0; pamIteration < 5; pamIteration++ )
676 		{
677 		int type;
678 
679 		/* Read back the response to our last message.  Although the spec
680 		   requires that the server not respond with a SSH_MSG_USERAUTH_-
681 		   FAILURE message if the request fails because of an invalid user
682 		   name (done for cargo-cult reasons to prevent an attacker from
683 		   being able to determine valid user names by checking for error
684 		   responses, although usability research on real-world users
685 		   indicates that this actually reduces security while having little
686 		   to no tangible benefit) some servers can return a failure
687 		   indication at this point so we have to allow for a failure
688 		   response as well as the expected SSH_MSG_USERAUTH_INFO_REQUEST */
689 		status = length = \
690 			readHSPacketSSH2( sessionInfoPtr, SSH_MSG_SPECIAL_USERAUTH_PAM,
691 							  ID_SIZE );
692 		if( cryptStatusError( status ) )
693 			return( status );
694 		type = sessionInfoPtr->sessionSSH->packetType;
695 
696 		/* If we got a success status, we're done */
697 		if( type == SSH_MSG_USERAUTH_SUCCESS )
698 			return( CRYPT_OK );
699 
700 		/* If the authentication failed provide more specific details to the
701 		   caller */
702 		if( type == SSH_MSG_USERAUTH_FAILURE )
703 			{
704 			/* If we failed on the first attempt (before we even tried to
705 			   send a password) it's probably because the user name is
706 			   invalid (or the server has the SSH_PFLAG_PAMPW bug).  Having
707 			   the server return a failure due to an invalid user name
708 			   shouldn't normally happen (see the comment above) but we
709 			   handle it just in case */
710 			if( pamIteration <= 0 )
711 				{
712 				char userNameBuffer[ CRYPT_MAX_TEXTSIZE + 8 ];
713 
714 				memcpy( userNameBuffer, userNamePtr->value,
715 						userNamePtr->valueLength );
716 				retExt( CRYPT_ERROR_WRONGKEY,
717 						( CRYPT_ERROR_WRONGKEY, SESSION_ERRINFO,
718 						  "Server reported: Invalid user name '%s'",
719 						  sanitiseString( userNameBuffer,
720 										  CRYPT_MAX_TEXTSIZE,
721 									      userNamePtr->valueLength ) ) );
722 				}
723 
724 			/* It's a failure after we've tried to authenticate ourselves,
725 			   report the details to the caller */
726 			return( reportAuthFailure( sessionInfoPtr, length, TRUE ) );
727 			}
728 		ENSURES( type == SSH_MSG_USERAUTH_INFO_REQUEST )
729 				 /* Guaranteed by the packet read with type =
730 				    SSH_MSG_SPECIAL_USERAUTH_PAM */
731 
732 		/* Perform the PAM authentication */
733 		status = pamAuthenticate( sessionInfoPtr,
734 								  sessionInfoPtr->receiveBuffer, length );
735 		if( cryptStatusError( status ) )
736 			return( status );
737 		}
738 
739 	retExt( CRYPT_ERROR_OVERFLOW,
740 			( CRYPT_ERROR_OVERFLOW, SESSION_ERRINFO,
741 			  "Too many iterations of negotiation during PAM "
742 			  "authentication" ) );
743 	}
744 
745 /****************************************************************************
746 *																			*
747 *						Perform Client-side Authentication					*
748 *																			*
749 ****************************************************************************/
750 
751 /* Authenticate the client to the server */
752 
753 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
processClientAuth(INOUT SESSION_INFO * sessionInfoPtr,INOUT SSH_HANDSHAKE_INFO * handshakeInfo)754 int processClientAuth( INOUT SESSION_INFO *sessionInfoPtr,
755 					   INOUT SSH_HANDSHAKE_INFO *handshakeInfo )
756 	{
757 	const ATTRIBUTE_LIST *userNamePtr = \
758 				findSessionInfo( sessionInfoPtr->attributeList,
759 								 CRYPT_SESSINFO_USERNAME );
760 	const ATTRIBUTE_LIST *passwordPtr = \
761 				findSessionInfo( sessionInfoPtr->attributeList,
762 								 CRYPT_SESSINFO_PASSWORD );
763 	STREAM stream;
764 	int length, status;
765 
766 	assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
767 	assert( isWritePtr( handshakeInfo, sizeof( SSH_HANDSHAKE_INFO ) ) );
768 
769 	REQUIRES( userNamePtr != NULL );
770 
771 	/* The buggy Tectia/ssh.com server requires a dummy request for
772 	   authentication methods otherwise it will reject any method other
773 	   than 'password' as invalid with the error "Client requested
774 	   non-existing method 'publickey'".  To work around this we submit a
775 	   dummy authentication request using the method 'none' */
776 	if( sessionInfoPtr->protocolFlags & SSH_PFLAG_DUMMYUSERAUTH )
777 		{
778 		status = sendDummyAuth( sessionInfoPtr, userNamePtr->value,
779 								userNamePtr->valueLength );
780 		if( cryptStatusError( status ) )
781 			{
782 			/* Under some special circumstances involving other server bugs
783 			   (see the comment in sendDummyAuth() for details) this dummy
784 			   authenticate can get us in, in which case we're done */
785 			if( status == OK_SPECIAL )
786 				return( CRYPT_OK );
787 
788 			return( status );
789 			}
790 		}
791 
792 	/* The way in which we handle authentication here isn't totally
793 	   appropriate since we assume that the user knows the appropriate form
794 	   of authentication to use.  If they're ambiguous and supply both a
795 	   password and a private key and the server only accepts PKC-based
796 	   authentication we'll always preferentially choose password-based
797 	   authentication.  The way around this is to send an authentication
798 	   request with a method-type of "none" to see what the server wants but
799 	   the only thing that cryptlib can do in this case (since it's non-
800 	   interactive during the handshake phase) is disconnect, tell the user
801 	   what went wrong, and try again.  The current mechanism does this
802 	   anyway so we don't gain much except extra RTT delays by adding this
803 	   question-and-answer facility */
804 	status = openPacketStreamSSH( &stream, sessionInfoPtr,
805 								  SSH_MSG_USERAUTH_REQUEST );
806 	if( cryptStatusError( status ) )
807 		return( status );
808 	if( passwordPtr != NULL )
809 		{
810 		/*	byte	type = SSH_MSG_USERAUTH_REQUEST
811 			string	user_name
812 			string	service_name = "ssh-connection"
813 			string	method-name = "password"
814 			boolean	FALSE
815 			string	password */
816 		writeString32( &stream, userNamePtr->value,
817 					   userNamePtr->valueLength );
818 		writeString32( &stream, "ssh-connection", 14 );
819 		writeString32( &stream, "password", 8 );
820 		sputc( &stream, 0 );
821 		status = writeString32( &stream, passwordPtr->value,
822 								passwordPtr->valueLength );
823 		}
824 	else
825 		{
826 		status = createPubkeyAuth( sessionInfoPtr, handshakeInfo, &stream,
827 								   userNamePtr );
828 		}
829 	if( cryptStatusError( status ) )
830 		{
831 		sMemDisconnect( &stream );
832 		return( status );
833 		}
834 
835 	/* Send the authentication information to the server */
836 	status = wrapPacketSSH2( sessionInfoPtr, &stream, 0, TRUE, TRUE );
837 	if( cryptStatusOK( status ) )
838 		status = sendPacketSSH2( sessionInfoPtr, &stream, TRUE );
839 	sMemDisconnect( &stream );
840 	if( cryptStatusError( status ) )
841 		return( status );
842 
843 	/* Wait for the server's ack of the authentication */
844 	status = length = \
845 		readHSPacketSSH2( sessionInfoPtr, SSH_MSG_SPECIAL_USERAUTH,
846 						  ID_SIZE );
847 	if( cryptStatusError( status ) )
848 		return( status );
849 	if( sessionInfoPtr->sessionSSH->packetType == SSH_MSG_USERAUTH_SUCCESS )
850 		{
851 		/* We've successfully authenticated ourselves and we're done */
852 		return( CRYPT_OK );
853 		}
854 
855 	/* The authentication failed, provide more specific details for the
856 	   caller, with an optional fallback to PAM authentication if the server
857 	   requested it.  Since this fallback can result in a successful
858 	   authentication (via the PAM fallback) we check for this as a return
859 	   status and convert the overall failure status that we'd be returning
860 	   at this point into a success status */
861 	status = reportAuthFailure( sessionInfoPtr, length, FALSE );
862 	if( cryptStatusOK( status ) )
863 		{
864 		/* The fallback to PAM authentication succeeded, we're done */
865 		return( CRYPT_OK );
866 		}
867 	if( status != OK_SPECIAL )
868 		return( status );
869 
870 	/* Some buggy implementations return an authentication failure as a
871 	   garbled attempt to tell us that no authentication is required, if we
872 	   encounter one of these (indicated by reportAuthFailure() returning an
873 	   OK_SPECIAL status) then we retry by sending a dummy authentication
874 	   request, which should get us in.
875 
876 	   The handling of return codes at this point is somewhat different from
877 	   normal because we're already in a failure state so even a successful
878 	   return (indicating that the dummy-auth was successfully sent) has to
879 	   be converted into an overall failure status */
880 	status = sendDummyAuth( sessionInfoPtr, userNamePtr->value,
881 							userNamePtr->valueLength );
882 	if( status == OK_SPECIAL )
883 		{
884 		/* If we got in with the dummy authenticate, we're done */
885 		return( CRYPT_OK );
886 		}
887 	return( cryptStatusOK( status ) ? CRYPT_ERROR_WRONGKEY : status );
888 	}
889 #endif /* USE_SSH */
890