cryptlib  3.4.1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros
ssh2_authc.c
Go to the documentation of this file.
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, CRYPT_PSEUDOALGO_PASSWORD },
57  { "keyboard-interactive", 20, CRYPT_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, CRYPT_PSEUDOALGO_PASSWORD },
64  { "keyboard-interactive", 20, CRYPT_PSEUDOALGO_PAM },
65  { NULL, 0, CRYPT_ALGO_NONE }, { NULL, CRYPT_ALGO_NONE }
66  };
67 
68 /* Forward declaration for authentication function */
69 
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 ) ) \
83 static int sendDummyAuth( INOUT SESSION_INFO *sessionInfoPtr,
84  IN_BUFFER( userNameLength ) const char *userName,
85  IN_LENGTH_SHORT 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,
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 
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, \
180  &authentAlgo, FALSE, SESSION_ERRINFO );
181  }
182  else
183  {
184  status = readAlgoString( &stream, algoStringUserauthentPKCTbl,
185  FAILSAFE_ARRAYSIZE( algoStringUserauthentPKCTbl, \
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  {
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,
258  "Server requested password authentication but only a "
259  "public/private key was available" ) );
260  }
261 
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,
275  "Server requested public-key authentication but only a "
276  "password was available" ) );
277  }
278 
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 ) ) \
287 static int createPubkeyAuth( const SESSION_INFO *sessionInfoPtr,
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,
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 ] */
352  IMESSAGE_DEV_CREATEOBJECT, &createInfo,
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  {
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 );
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,
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 ) ) \
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,
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 the server is supposed to send back a standard user-auth
518  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  status = readString32( &stream, promptBuffer,
542  CRYPT_MAX_TEXTSIZE, &promptLength );
543  sMemDisconnect( &stream );
544  if( cryptStatusError( status ) )
545  {
546  retExt( status,
547  ( status, SESSION_ERRINFO,
548  "Invalid PAM authentication request packet" ) );
549  }
550 
551  /* Make sure that we're being asked for some form of password
552  authentication. This assumes that the prompt string begins with the
553  word "password" (which always seems to be the case), if it isn't then
554  it may be necessary to do a substring search */
555  if( promptLength < 8 || strCompare( promptBuffer, "Password", 8 ) )
556  {
557  /* The following may produce somewhat inconsistent results in terms
558  of what it reports because it's unclear what 'name' actually is,
559  on the off chance that something fills this in it could produce
560  a less appropriate error message than the prompt, but we
561  opportunistically try it in case it contains something useful */
564  "Server requested unknown PAM authentication type '%s'",
565  ( nameLength > 0 ) ? \
566  sanitiseString( nameBuffer, CRYPT_MAX_TEXTSIZE, \
567  nameLength ) : \
568  sanitiseString( promptBuffer, CRYPT_MAX_TEXTSIZE, \
569  promptLength ) ) );
570  }
571 
572  REQUIRES( passwordPtr != NULL && \
573  passwordPtr->valueLength > 0 && \
574  passwordPtr->valueLength <= CRYPT_MAX_TEXTSIZE );
575 
576  /* Send back the PAM user-auth response:
577 
578  byte type = SSH_MSG_USERAUTH_INFO_RESPONSE
579  int num_responses = num_prompts
580  string response
581 
582  What to do if there's more than one prompt is a bit tricky, usually
583  PAM is used as a form of (awkward) password authentication and
584  there's only a single prompt, if we ever encounter a situation where
585  there's more than one prompt then it's probably a request to confirm
586  the password so we just send it again for successive prompts */
587  status = openPacketStreamSSH( &stream, sessionInfoPtr,
589  if( cryptStatusError( status ) )
590  return( status );
591  status = writeUint32( &stream, noPrompts );
592  for( i = 0, iterationCount = 0;
593  cryptStatusOK( status ) && i < noPrompts && \
594  iterationCount < FAILSAFE_ITERATIONS_MED;
595  i++, iterationCount++ )
596  {
597  status = writeString32( &stream, passwordPtr->value,
598  passwordPtr->valueLength );
599  }
600  ENSURES( iterationCount < FAILSAFE_ITERATIONS_MED );
601  if( cryptStatusOK( status ) )
602  status = sendPacketSSH2( sessionInfoPtr, &stream, FALSE );
603  sMemDisconnect( &stream );
604 
605  return( status );
606  }
607 
608 /* Handle PAM authentication */
609 
611 static int processPamAuthentication( INOUT SESSION_INFO *sessionInfoPtr )
612  {
613  const ATTRIBUTE_LIST *userNamePtr = \
614  findSessionInfo( sessionInfoPtr->attributeList,
616  STREAM stream;
617  int length, pamIteration, status;
618 
619  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
620  assert( isReadPtr( userNamePtr, sizeof( ATTRIBUTE_LIST ) ) );
621 
622  REQUIRES( userNamePtr != NULL && \
623  userNamePtr->valueLength > 0 && \
624  userNamePtr->valueLength <= CRYPT_MAX_TEXTSIZE );
625 
626  /* Send a user-auth request asking for PAM authentication:
627 
628  byte type = SSH_MSG_USERAUTH_REQUEST
629  string user_name
630  string service_name = "ssh-connection"
631  string method_name = "keyboard-interactive"
632  string language = ""
633  string sub_methods = "password"
634 
635  The sub-methods are implementation-dependent and the spec suggests an
636  implementation strategy in which the server ignores them so
637  specifying anything here is mostly wishful thinking, but we ask for
638  password authentication anyway in case it helps */
639  status = openPacketStreamSSH( &stream, sessionInfoPtr,
641  if( cryptStatusError( status ) )
642  return( status );
643  writeString32( &stream, userNamePtr->value, userNamePtr->valueLength );
644  writeString32( &stream, "ssh-connection", 14 );
645  writeString32( &stream, "keyboard-interactive", 20 );
646  writeUint32( &stream, 0 ); /* No language tag */
647  if( sessionInfoPtr->protocolFlags & SSH_PFLAG_PAMPW )
648  {
649  /* Some servers choke if we supply a sub-method hint for the
650  authentication */
651  status = writeUint32( &stream, 0 );
652  }
653  else
654  status = writeString32( &stream, "password", 8 );
655  if( cryptStatusOK( status ) )
656  status = sendPacketSSH2( sessionInfoPtr, &stream, FALSE );
657  sMemDisconnect( &stream );
658  if( cryptStatusError( status ) )
659  return( status );
660 
661  /* Handle the PAM negotiation. This can (in theory) go on indefinitely,
662  to avoid potential DoS problems we limit it to five iterations. In
663  general we'll only need two iterations (or three for OpenSSH's empty-
664  message bug) so we shouldn't ever get to five */
665  for( pamIteration = 0; pamIteration < 5; pamIteration++ )
666  {
667  int type;
668 
669  /* Read back the response to our last message. Although the spec
670  requires that the server not respond with a SSH_MSG_USERAUTH_-
671  FAILURE message if the request fails because of an invalid user
672  name (done for cargo-cult reasons to prevent an attacker from
673  being able to determine valid user names by checking for error
674  responses, although usability research on real-world users
675  indicates that this actually reduces security while having little
676  to no tangible benefit) some servers can return a failure
677  indication at this point so we have to allow for a failure
678  response as well as the expected SSH_MSG_USERAUTH_INFO_REQUEST */
679  status = length = \
680  readHSPacketSSH2( sessionInfoPtr, SSH_MSG_SPECIAL_USERAUTH_PAM,
681  ID_SIZE );
682  if( cryptStatusError( status ) )
683  return( status );
684  type = sessionInfoPtr->sessionSSH->packetType;
685 
686  /* If we got a success status, we're done */
687  if( type == SSH_MSG_USERAUTH_SUCCESS )
688  return( CRYPT_OK );
689 
690  /* If the authentication failed provide more specific details to the
691  caller */
692  if( type == SSH_MSG_USERAUTH_FAILURE )
693  {
694  /* If we failed on the first attempt (before we even tried to
695  send a password) it's probably because the user name is
696  invalid (or the server has the SSH_PFLAG_PAMPW bug). Having
697  the server return a failure due to an invalid user name
698  shouldn't normally happen (see the comment above) but we
699  handle it just in case */
700  if( pamIteration <= 0 )
701  {
702  char userNameBuffer[ CRYPT_MAX_TEXTSIZE + 8 ];
703 
704  memcpy( userNameBuffer, userNamePtr->value,
705  userNamePtr->valueLength );
708  "Server reported: Invalid user name '%s'",
709  sanitiseString( userNameBuffer,
711  userNamePtr->valueLength ) ) );
712  }
713 
714  /* It's a failure after we've tried to authenticate ourselves,
715  report the details to the caller */
716  return( reportAuthFailure( sessionInfoPtr, length, TRUE ) );
717  }
719  /* Guaranteed by the packet read with type =
720  SSH_MSG_SPECIAL_USERAUTH_PAM */
721 
722  /* Perform the PAM authentication */
723  status = pamAuthenticate( sessionInfoPtr,
724  sessionInfoPtr->receiveBuffer, length );
725  if( cryptStatusError( status ) )
726  return( status );
727  }
728 
730  ( CRYPT_ERROR_OVERFLOW, SESSION_ERRINFO,
731  "Too many iterations of negotiation during PAM "
732  "authentication" ) );
733  }
734 
735 /****************************************************************************
736 * *
737 * Perform Client-side Authentication *
738 * *
739 ****************************************************************************/
740 
741 /* Authenticate the client to the server */
742 
743 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
744 int processClientAuth( INOUT SESSION_INFO *sessionInfoPtr,
745  INOUT SSH_HANDSHAKE_INFO *handshakeInfo )
746  {
747  const ATTRIBUTE_LIST *userNamePtr = \
748  findSessionInfo( sessionInfoPtr->attributeList,
750  const ATTRIBUTE_LIST *passwordPtr = \
751  findSessionInfo( sessionInfoPtr->attributeList,
753  STREAM stream;
754  int length, status;
755 
756  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
757  assert( isWritePtr( handshakeInfo, sizeof( SSH_HANDSHAKE_INFO ) ) );
758 
759  REQUIRES( userNamePtr != NULL );
760 
761  /* The buggy Tectia/ssh.com server requires a dummy request for
762  authentication methods otherwise it will reject any method other
763  than 'password' as invalid with the error "Client requested
764  non-existing method 'publickey'". To work around this we submit a
765  dummy authentication request using the method 'none' */
766  if( sessionInfoPtr->protocolFlags & SSH_PFLAG_DUMMYUSERAUTH )
767  {
768  status = sendDummyAuth( sessionInfoPtr, userNamePtr->value,
769  userNamePtr->valueLength );
770  if( cryptStatusError( status ) )
771  {
772  /* Under some special circumstances involving other server bugs
773  (see the comment in sendDummyAuth() for details) this dummy
774  authenticate can get us in, in which case we're done */
775  if( status == OK_SPECIAL )
776  return( CRYPT_OK );
777 
778  return( status );
779  }
780  }
781 
782  /* The way in which we handle authentication here isn't totally
783  appropriate since we assume that the user knows the appropriate form
784  of authentication to use. If they're ambiguous and supply both a
785  password and a private key and the server only accepts PKC-based
786  authentication we'll always preferentially choose password-based
787  authentication. The way around this is to send an authentication
788  request with a method-type of "none" to see what the server wants but
789  the only thing that cryptlib can do in this case (since it's non-
790  interactive during the handshake phase) is disconnect, tell the user
791  what went wrong, and try again. The current mechanism does this
792  anyway so we don't gain much except extra RTT delays by adding this
793  question-and-answer facility */
794  status = openPacketStreamSSH( &stream, sessionInfoPtr,
796  if( cryptStatusError( status ) )
797  return( status );
798  if( passwordPtr != NULL )
799  {
800  /* byte type = SSH_MSG_USERAUTH_REQUEST
801  string user_name
802  string service_name = "ssh-connection"
803  string method-name = "password"
804  boolean FALSE
805  string password */
806  writeString32( &stream, userNamePtr->value,
807  userNamePtr->valueLength );
808  writeString32( &stream, "ssh-connection", 14 );
809  writeString32( &stream, "password", 8 );
810  sputc( &stream, 0 );
811  status = writeString32( &stream, passwordPtr->value,
812  passwordPtr->valueLength );
813  }
814  else
815  {
816  status = createPubkeyAuth( sessionInfoPtr, handshakeInfo, &stream,
817  userNamePtr );
818  }
819  if( cryptStatusError( status ) )
820  {
821  sMemDisconnect( &stream );
822  return( status );
823  }
824 
825  /* Send the authentication information to the server */
826  status = wrapPacketSSH2( sessionInfoPtr, &stream, 0, TRUE, TRUE );
827  if( cryptStatusOK( status ) )
828  status = sendPacketSSH2( sessionInfoPtr, &stream, TRUE );
829  sMemDisconnect( &stream );
830  if( cryptStatusError( status ) )
831  return( status );
832 
833  /* Wait for the server's ack of the authentication */
834  status = length = \
835  readHSPacketSSH2( sessionInfoPtr, SSH_MSG_SPECIAL_USERAUTH,
836  ID_SIZE );
837  if( cryptStatusError( status ) )
838  return( status );
839  if( sessionInfoPtr->sessionSSH->packetType == SSH_MSG_USERAUTH_SUCCESS )
840  {
841  /* We've successfully authenticated ourselves and we're done */
842  return( CRYPT_OK );
843  }
844 
845  /* The authentication failed, provide more specific details for the
846  caller, with an optional fallback to PAM authentication if the server
847  requested it. Since this fallback can result in a successful
848  authentication (via the PAM fallback) we check for this as a return
849  status and convert the overall failure status that we'd be returning
850  at this point into a success status */
851  status = reportAuthFailure( sessionInfoPtr, length, FALSE );
852  if( cryptStatusOK( status ) )
853  {
854  /* The fallback to PAM authentication succeeded, we're done */
855  return( CRYPT_OK );
856  }
857  if( status != OK_SPECIAL )
858  return( status );
859 
860  /* Some buggy implementations return an authentication failure as a
861  garbled attempt to tell us that no authentication is required, if we
862  encounter one of these (indicated by reportAuthFailure() returning an
863  OK_SPECIAL status) then we retry by sending a dummy authentication
864  request, which should get us in.
865 
866  The handling of return codes at this point is somewhat different from
867  normal because we're already in a failure state so even a successful
868  return (indicating that the dummy-auth was successfully sent) has to
869  be converted into an overall failure status */
870  status = sendDummyAuth( sessionInfoPtr, userNamePtr->value,
871  userNamePtr->valueLength );
872  if( status == OK_SPECIAL )
873  {
874  /* If we got in with the dummy authenticate, we're done */
875  return( CRYPT_OK );
876  }
877  return( cryptStatusOK( status ) ? CRYPT_ERROR_WRONGKEY : status );
878  }
879 #endif /* USE_SSH */