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