cryptlib  3.4.1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros
ssh2_auths.c
Go to the documentation of this file.
1 /****************************************************************************
2 * *
3 * cryptlib SSHv2 Server-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 #ifdef USE_SSH
21 
22 /* SSH user authentication gets quite complicated because of the way that
23  the multi-pass process defined in the spec affects our handling of user
24  name and password information. The SSH spec allows the client to perform
25  authentication in bits and pieces and change the details of what it sends
26  at each step (and use authentication methods that it specifically knows
27  the server can't handle and all manner of other craziness, see the
28  SimpleSSH draft for a long discussion of these problems). This is
29  particularly nasty because of the large amount of leeway that it provides
30  for malicious clients to subvert the authentication process, for example
31  the client can supply a privileged user name the first time around and
32  then authenticate the second time round as an unprivileged user. If the
33  calling application just checks the first user name that it finds it'll
34  then treat the client as being an authenticated privileged user (which
35  indeed some server applications have done in the past).
36 
37  To defend against this we record the user name the first time that it's
38  entered and from then on require that the client supply the same name on
39  subsequent authentication attempts. This is the standard client
40  behaviour anyway, if the user name + password are rejected then the
41  assumption is that the password is wrong and the user gets to retry the
42  password. In order to accommodate public-key authentication (currently
43  not handled, see the comment further on) we also verify that the
44  authentication method remains constant over successive iterations, i.e.
45  that the client doesn't try part of an authentication with a public key
46  and then another part with a password.
47 
48  Handling the state machine required to process this gets a bit
49  complicated, the protocol flow that we enforce is:
50 
51  Step 0 (optional):
52 
53  Client sends SSH_MSG_USERAUTH_REQUEST with method "none" to query
54  available authentication method types.
55 
56  Server responsd with SSH_MSG_USERAUTH_FAILURE listing available
57  methods.
58 
59  Step 1:
60 
61  Client sends SSH_MSG_USERAUTH_REQUEST with method "password" or
62  "publickey" and password data or a digital signature as appropriate.
63 
64  Step 2, one of:
65 
66  a. Server responds with SSH_MSG_USERAUTH_SUCCESS and the
67  authentication exchange terminates.
68 
69  b. Server responds to method "password" with
70  SSH_MSG_USERAUTH_FAILURE, the client may retry step 1 if permitted
71  by the server as described in the SSH specification.
72 
73  c. Server responds to method "publickey" with
74  SSH_MSG_USERAUTH_FAILURE and the authentication exchange terminates.
75 
76  This is a simplified form of what's given in the SSH spec, which allows
77  almost any jumble of messages including ones that don't make any sense
78  (again, see the SimpleSSH draft for more details). The credential-type
79  matching that we perform in processUserAuth() is indicated by the caller
80  supplying one of the following values:
81 
82  NONE: No existing user name or password to match against, store the
83  client's user name and password for the caller to check.
84 
85  USERNAME: Existing user name present from a previous iteration of
86  authentication, match the client's user name to the existing one and
87  store the client's password for the caller to check.
88 
89  USERNAME_PASSWORD: Caller-supplied credentials present, match the
90  client's credentials against them */
91 
92 typedef enum {
93  CREDENTIAL_NONE, /* No credential information type */
94  CREDENTIAL_USERNAME,/* User name is present and must match */
95  CREDENTIAL_USERNAME_PASSWORD,/* User/password present and must match */
96  CREDENTIAL_LAST /* Last possible credential information type */
97  } CREDENTIAL_TYPE;
98 
99 /* The processUserAuth() function has multiple possible return values,
100  broken down into CRYPT_OK for a password match, OK_SPECIAL for something
101  that the caller has to handle, and the standard error status, with
102  subvalues given in the userAuthInfo variable. The different types and
103  subtypes are:
104 
105  CRYPT_OK + uAI = USERAUTH_SUCCESS: User credentials present and matched.
106  Note that the caller has to check both of these values, one a return
107  status and the other a by-reference parameter, to avoid a single
108  point of failure for the authentication.
109 
110  OK_SPECIAL + uAI = USERAUTH_CALLERCHECK: User credentials not present,
111  added to the session attributes for the caller to check.
112 
113  OK_SPECIAL + uAI = USERAUTH_NOOP: No-op read for a client query of
114  available authentication methods via the pseudo-method "none".
115 
116  Error + uAI = USERAUTH_ERROR: Standard error status, which includes non-
117  matched credentials */
118 
119 typedef enum {
120  USERAUTH_NONE, /* No authentication type */
121  USERAUTH_SUCCESS, /* User authenticated successfully */
122  USERAUTH_CALLERCHECK,/* Caller must check whether auth.was successful */
123  USERAUTH_NOOP, /* No-op read */
124  USERAUTH_ERROR, /* User failed authentication */
125  USERAUTH_LAST /* Last possible authentication type */
126  } USERAUTH_TYPE;
127 
128 /* The match options with a caller-supplied list of credentials to match
129  against are:
130 
131  Client sends | Cred.type | Action | Result
132  -------------+--------------+---------------------------+--------------
133  Name, pw | USERNAME_PW | Match name, password | SUCCESS/ERROR
134  -------------+--------------+---------------------------+--------------
135  Name, none | USERNAME_PW | Match name | NOOP
136  Name, none | USERNAME_PW | Match name | ERROR (fatal)
137  -------------+--------------+---------------------------+--------------
138  Name, none | USERNAME_PW | Match name | NOOP
139  Name, pw | USERNAME_PW | Match name, password | SUCCESS/ERROR
140  -------------+--------------+---------------------------+--------------
141  Name, none | USERNAME_PW | Match name | NOOP
142  Name2, pw | USERNAME_PW | Match name2 -> Fail | ERROR (fatal)
143  -------------+--------------+---------------------------+--------------
144  Name, wrongPW| USERNAME_PW | Match name, wrongPW |
145  | | -> Fail | ERROR
146  Name, pw | USERNAME_PW | Match name, password | SUCCESS/ERROR
147 
148  If an error status is returned then any value other than
149  CRYPT_ERROR_WRONGKEY is treated as a fatal error. CRYPT_ERROR_WRONGKEY
150  is nonfatal as determined by the caller, for example until some predefined
151  retry count has been exceeded.
152 
153  Handling of the third case (two different user names supplied in different
154  messages) gets a bit tricky because if both names are present in the list
155  of credentials then we'll get a successful match both times even though a
156  different user name was used. To detect this we record the initial user
157  name in the SSH session information and check it against subsequently
158  supplied values.
159 
160  The match options with no caller-supplied list of credentials to match
161  against are:
162 
163  Client sends | Cred.type | Action | Result
164  -------------+--------------+---------------------------+--------------
165  Name, pw | NONE | Add name, password | CALLERCHECK
166  -------------+--------------+---------------------------+--------------
167  Name, none | USERNAME_PW | Match name | NOOP
168  Name, none | USERNAME_PW | Match name | ERROR (fatal)
169  -------------+--------------+---------------------------+--------------
170  Name, none | NONE | Add name | NOOP
171  Name, pw | USERNAME | Match name, add password | CALLERCHECK
172  -------------+--------------+---------------------------+--------------
173  Name, none | NONE | Add name | NOOP
174  Name2, pw | USERNAME | Match name2 -> Fail | ERROR (fatal)
175  -------------+--------------+---------------------------+--------------
176  Name, wrongPW| NONE | Add name, wrongPW | CALLERCHECK
177  Name, pw | USERNAME | Match name, add password | CALLERCHECK
178 
179  Unlike SSHv1, SSHv2 properly identifies public keys, however because of
180  its complexity (several more states added to the state machine because of
181  SSH's propensity for carrying out any negotiation that it performs in lots
182  of little bits and pieces) we don't support this form of authentication
183  until someone specifically requests it */
184 
185 /* A lookup table for the authentication methods submitted by the client */
186 
187 typedef enum {
188  METHOD_NONE, /* No authentication method type */
189  METHOD_QUERY, /* Query available authentication methods */
190  METHOD_PASSWORD, /* Password authentication */
191  METHOD_LAST /* Last possible authentication method type */
192  } METHOD_TYPE;
193 
194 typedef struct {
195  BUFFER_FIXED( nameLength ) \
196  const char *name; /* Method name */
197  const int nameLength;
198  const METHOD_TYPE type; /* Method type */
199  } METHOD_INFO;
200 
201 static const METHOD_INFO methodInfoTbl[] = {
202  { "none", 4, METHOD_QUERY },
203  { "password", 8, METHOD_PASSWORD },
204  { NULL, 0, METHOD_NONE }, { NULL, 0, METHOD_NONE }
205  };
206 
207 /****************************************************************************
208 * *
209 * Utility Functions *
210 * *
211 ****************************************************************************/
212 
213 /* Send a succeeded/failed-authentication response to the client:
214 
215  byte type = SSH_MSG_USERAUTH_SUCCESS
216 
217  or
218 
219  byte type = SSH_MSG_USERAUTH_FAILURE
220  string allowed_authent = "password" | empty
221  boolean partial_success = FALSE */
222 
224 static int sendResponseSuccess( INOUT SESSION_INFO *sessionInfoPtr )
225  {
226  STREAM stream;
227  int status;
228 
229  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
230 
231  status = openPacketStreamSSH( &stream, sessionInfoPtr,
233  if( cryptStatusError( status ) )
234  return( status );
235  status = sendPacketSSH2( sessionInfoPtr, &stream, FALSE );
236  sMemDisconnect( &stream );
237 
238  return( status );
239  }
240 
242 static int sendResponseFailure( INOUT SESSION_INFO *sessionInfoPtr,
243  const BOOLEAN allowFurtherAuth )
244  {
245  STREAM stream;
246  int status;
247 
248  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
249 
250  status = openPacketStreamSSH( &stream, sessionInfoPtr,
252  if( cryptStatusError( status ) )
253  return( status );
254  if( allowFurtherAuth )
255  writeString32( &stream, "password", 8 );
256  else
257  writeUint32( &stream, 0 );
258  status = sputc( &stream, 0 );
259  if( cryptStatusOK( status ) )
260  status = sendPacketSSH2( sessionInfoPtr, &stream, FALSE );
261  sMemDisconnect( &stream );
262 
263  return( status );
264  }
265 
266 /* Check that the credentials submitted by the client are consistent with
267  any information submitted during earlier rounds of authentication */
268 
269 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
270 static int checkCredentialsConsistent( INOUT SSH_INFO *sshInfo,
271  IN_BUFFER( userNameLength ) \
272  const void *userName,
274  const int userNameLength,
275  IN_ENUM( METHOD ) \
276  const METHOD_TYPE authMethod,
277  const BOOLEAN isInitialAuth )
278  {
279  HASHFUNCTION_ATOMIC hashFunctionAtomic;
280  BYTE userNameHash[ KEYID_SIZE + 8 ];
281 
282  assert( isWritePtr( sshInfo, sizeof( SSH_INFO ) ) );
283  assert( isReadPtr( userName, userNameLength ) );
284 
285  REQUIRES( userNameLength >= 1 && userNameLength <= CRYPT_MAX_TEXTSIZE );
286  REQUIRES( authMethod > METHOD_NONE && authMethod < METHOD_LAST );
287 
288  /* Hash the user name so that we only need to store the fixed-length
289  hash rather than the variable-length user name */
290  getHashAtomicParameters( CRYPT_ALGO_SHA1, 0, &hashFunctionAtomic, NULL );
291  hashFunctionAtomic( userNameHash, KEYID_SIZE, userName, userNameLength );
292 
293  /* If this is the first message remember the user name and
294  authentication method in case we need to check it on a subsequent
295  round of authentication */
296  if( isInitialAuth )
297  {
298  memcpy( sshInfo->authUserNameHash, userNameHash, KEYID_SIZE );
299  sshInfo->authType = authMethod;
300  return( CRYPT_OK );
301  }
302 
303  /* If the client is switching credentials across authentication messages
304  then there's something fishy going on */
305  if( !compareDataConstTime( sshInfo->authUserNameHash, userNameHash,
306  KEYID_SIZE ) )
307  return( CRYPT_ERROR_INVALID );
308 
309  /* Make sure that the authentication messages follow the state
310  transitions:
311 
312  <empty> -> query -> auth_method
313  <empty> ----------> auth_method
314 
315  There are two error cases that can occur here, either the client
316  sends multiple authentication requests with the pseudo-method "none"
317  (SSH's way of performing a method query) or they send a request with
318  a different method than a previous one, for example "password" in the
319  initial request and then "publickey" in a following one */
320  if( sshInfo->authType == METHOD_QUERY )
321  {
322  /* We've already processed a query message, any subsequent message
323  has to be an actual authentication message */
324  if( authMethod == METHOD_QUERY )
325  return( CRYPT_ERROR_DUPLICATE );
326 
327  /* We've had a query followed by a standard authentication method,
328  remember the authentication method for later */
329  sshInfo->authType = authMethod;
330  return( CRYPT_OK );
331  }
332 
333  /* We've already seen a standard authentication method, the new method
334  must be the same */
335  if( sshInfo->authType != authMethod )
336  return( CRYPT_ERROR_INVALID );
337 
338  return( CRYPT_OK );
339  }
340 
341 /****************************************************************************
342 * *
343 * Verify the Client's Authentication *
344 * *
345 ****************************************************************************/
346 
347 /* Handle client authentication */
348 
349 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
350 static int processUserAuth( INOUT SESSION_INFO *sessionInfoPtr,
351  OUT_ENUM( USERAUTH ) USERAUTH_TYPE *userAuthInfo,
352  IN_ENUM_OPT( CREDENTIAL ) \
353  const CREDENTIAL_TYPE credentialType,
354  const BOOLEAN initialAuth )
355  {
356  STREAM stream;
358  const METHOD_INFO *methodInfoPtr = NULL;
359  BYTE userNameBuffer[ CRYPT_MAX_TEXTSIZE + 8 ];
360  BYTE stringBuffer[ CRYPT_MAX_TEXTSIZE + 8 ];
361  int length, userNameLength, stringLength, i, status;
362 
363  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
364 
365  REQUIRES( credentialType >= CREDENTIAL_NONE && \
366  credentialType < CREDENTIAL_LAST );
367  /* CREDENTIAL_NONE is a valid type since it indicates that we
368  should record the user name and password for the caller to
369  check */
370 
371  /* Clear the return value, or at least set it to the default failed-
372  authentication state */
373  *userAuthInfo = USERAUTH_ERROR;
374 
375  /* Get the userAuth packet from the client:
376 
377  byte type = SSH_MSG_USERAUTH_REQUEST
378  string user_name
379  string service_name = "ssh-connection"
380  string method_name = "none" | "password"
381  [ boolean FALSE -- For method = "password"
382  string password ]
383 
384  The client can optionally send a method-type of "none" as its first
385  message to indicate that it'd like the server to return a list of
386  allowed authentication types, if we get a packet of this kind and
387  the initialAuth flag is set then we return our allowed types list */
388  status = length = \
389  readHSPacketSSH2( sessionInfoPtr, SSH_MSG_USERAUTH_REQUEST,
390  ID_SIZE + sizeofString32( "", 1 ) + \
391  sizeofString32( "", 8 ) + \
392  sizeofString32( "", 4 ) );
393  if( cryptStatusError( status ) )
394  return( status );
395  sMemConnect( &stream, sessionInfoPtr->receiveBuffer, length );
396 
397  /* Read the user name, service name, and authentication method type */
398  status = readString32( &stream, userNameBuffer, CRYPT_MAX_TEXTSIZE,
399  &userNameLength );
400  if( cryptStatusError( status ) || \
401  userNameLength <= 0 || userNameLength > CRYPT_MAX_TEXTSIZE )
402  {
403  sMemDisconnect( &stream );
406  "Invalid user-authentication user name" ) );
407  }
408  status = readString32( &stream, stringBuffer, CRYPT_MAX_TEXTSIZE,
409  &stringLength );
410  if( cryptStatusOK( status ) )
411  {
412  if( stringLength != 14 || \
413  memcmp( stringBuffer, "ssh-connection", 14 ) )
414  status = CRYPT_ERROR_BADDATA;
415  }
416  if( cryptStatusOK( status ) )
417  status = readString32( &stream, stringBuffer, CRYPT_MAX_TEXTSIZE,
418  &stringLength );
419  if( cryptStatusOK( status ) )
420  {
421  if( stringLength <= 0 || stringLength > CRYPT_MAX_TEXTSIZE )
422  status = CRYPT_ERROR_BADDATA;
423  }
424  if( cryptStatusError( status ) )
425  {
426  sMemDisconnect( &stream );
429  "Invalid user-authentication service or method name" ) );
430  }
431 
432  /* Check which authentication method the client has requested. Note that
433  we perform this and the following checks before any checking of the
434  user name to make it harder for an attacker to scan for valid accounts
435  (but see also the comment further on this issue further on where the
436  user-name check is performed) */
437  for( i = 0; methodInfoTbl[ i ].type != METHOD_NONE && \
438  i < FAILSAFE_ARRAYSIZE( methodInfoTbl, METHOD_INFO ); i++ )
439  {
440  const METHOD_INFO *methodInfo = &methodInfoTbl[ i ];
441 
442  if( methodInfo->nameLength == stringLength && \
443  !memcmp( methodInfo->name, stringBuffer, stringLength ) )
444  {
445  methodInfoPtr = methodInfo;
446  break;
447  }
448  }
449  ENSURES( i < FAILSAFE_ARRAYSIZE( methodInfoTbl, METHOD_INFO ) );
450  if( methodInfoPtr == NULL )
451  {
452  sMemDisconnect( &stream );
455  "Unknown user-authentication method type '%s'",
456  sanitiseString( stringBuffer, CRYPT_MAX_TEXTSIZE,
457  stringLength ) ) );
458  }
459  ( void ) sgetc( &stream ); /* Skip boolean flag */
460 
461  /* Check that the credentials submitted by the client are consistent
462  with any information submitted during earlier rounds of
463  authentication */
464  status = checkCredentialsConsistent( sessionInfoPtr->sessionSSH,
465  userNameBuffer, userNameLength,
466  methodInfoPtr->type,
467  initialAuth );
468  if( cryptStatusError( status ) )
469  {
470  sMemDisconnect( &stream );
471 
472  /* There are two slightly different error conditions that we can
473  encounter here, we provide distinct error messages to give the
474  caller more information on what went wrong */
475  if( status == CRYPT_ERROR_DUPLICATE )
476  {
479  "Client sent duplicate authentication requests with "
480  "method 'none'" ) );
481  }
484  "Client supplied different credentials than the ones "
485  "supplied during a previous authentication attempt" ) );
486  }
487 
488  /* If it's a proper user authentication rather than a dummy read to get
489  a list of supported authentication types, read the user password */
490  if( methodInfoPtr->type != METHOD_QUERY )
491  {
492  status = readString32( &stream, stringBuffer, CRYPT_MAX_TEXTSIZE,
493  &stringLength );
494  sMemDisconnect( &stream );
495  if( cryptStatusError( status ) || \
496  stringLength <= 0 || stringLength > CRYPT_MAX_TEXTSIZE )
497  {
500  "Invalid user-authentication payload" ) );
501  }
502  }
503 
504  /* If the user credentials are pre-set make sure that the newly-
505  submitted user name matches an existing one. Note that we've left
506  this check as late as possible (so that it's right next to the
507  password check) to avoid timing attacks that might help an attacker
508  guess a valid user name. On the other hand given the typical pattern
509  of SSH password-guessing attacks which just run through a fixed set
510  of user names this probably isn't worth the trouble since it'll have
511  little to no effect on attackers, so what it's avoiding is purely a
512  certificational weakness.
513 
514  There's also a slight anomaly in error reporting here, if the client
515  is performing a dummy read and there are pre-set user credentials
516  present then instead of the expected list of available authentication
517  methods they'll get an error response. This is consistent with the
518  specification (which is ambiguous on the topic), it says that the
519  server must return a failure response and may also include a list of
520  allowed methods, but it may not be what the client is expecting.
521  This problem occurs because of the overloading of the authentication
522  mechanism as a method-query mechanism, it's not clear whether the
523  query response or the username-check response is supposed to take
524  precedence */
525  if( credentialType != CREDENTIAL_NONE )
526  {
527  attributeListPtr = \
528  findSessionInfoEx( sessionInfoPtr->attributeList,
530  userNameBuffer, userNameLength );
531  if( attributeListPtr == NULL )
532  {
533  ( void ) sendResponseFailure( sessionInfoPtr, FALSE );
536  "Unknown/invalid user name '%s'",
537  sanitiseString( userNameBuffer, CRYPT_MAX_TEXTSIZE,
538  userNameLength ) ) );
539  }
540 
541  /* We've matched an existing user name, select the attribute that
542  contains it */
543  sessionInfoPtr->attributeListCurrent = \
544  ( ATTRIBUTE_LIST * ) attributeListPtr;
545  }
546  else
547  {
548  /* It's a new user name, add it */
549  status = addSessionInfoS( &sessionInfoPtr->attributeList,
551  userNameBuffer, userNameLength );
552  if( cryptStatusError( status ) )
553  {
554  retExt( status,
555  ( status, SESSION_ERRINFO,
556  "Error recording user name '%s'",
557  sanitiseString( userNameBuffer, CRYPT_MAX_TEXTSIZE,
558  userNameLength ) ) );
559  }
560  }
561 
562  /* If the client just wants a list of supported authentication
563  mechanisms tell them what we allow (handled by sending a failed-
564  authentication response, which contains a list of mechanisms that can
565  be used to continue) and await further input */
566  if( methodInfoPtr->type == METHOD_QUERY )
567  {
568  sMemDisconnect( &stream );
569 
570  /* Tell the client which authentication methods can continue */
571  status = sendResponseFailure( sessionInfoPtr, TRUE );
572  if( cryptStatusError( status ) )
573  return( status );
574 
575  /* Inform the caller that this was a no-op pass and the client can
576  try again */
577  *userAuthInfo = USERAUTH_NOOP;
578  return( OK_SPECIAL );
579  }
580 
581  /* If full user credentials are present then the user name has been
582  matched to a caller-supplied list of allowed { user name, password }
583  pairs and we move on to the corresponding password and verify it */
584  if( credentialType == CREDENTIAL_USERNAME_PASSWORD )
585  {
586  /* Move on to the password associated with the user name */
587  attributeListPtr = attributeListPtr->next;
588  ENSURES( attributeListPtr != NULL && \
589  attributeListPtr->attributeID == CRYPT_SESSINFO_PASSWORD );
590  /* Ensured by checkMissingInfo() in sess_iattr.c */
591 
592  /* Make sure that the password matches. Note that in the case of an
593  error we don't report the incorrect password that was entered
594  since we don't want it appearing in logfiles */
595  if( stringLength != attributeListPtr->valueLength || \
596  !compareDataConstTime( stringBuffer, attributeListPtr->value,
597  stringLength ) )
598  {
601  "Invalid password for user '%s'",
602  sanitiseString( userNameBuffer, CRYPT_MAX_TEXTSIZE,
603  userNameLength ) ) );
604  }
605 
606  /* The user has successfully authenticated, indicate this through a
607  failsafe two-value return status (see the comment for
608  processFixedAuth()/processServerAuth() for details) */
609  *userAuthInfo = USERAUTH_SUCCESS;
610  return( CRYPT_OK );
611  }
612 
613  /* There are no pre-set credentials present to match against, record the
614  password for the caller to check, making it an ephemeral attribute
615  since the client could try and re-enter it on a subsequent iteration
616  if we tell them that it's incorrect. This adds the password after
617  the first user name that it finds but since there's only one user
618  name present, namely the one that was recorded or matched above,
619  there's no problems with potentially ambiguous password entries in
620  the attribute list */
621  status = updateSessionInfo( &sessionInfoPtr->attributeList,
623  stringBuffer, stringLength,
624  CRYPT_MAX_TEXTSIZE, ATTR_FLAG_EPHEMERAL );
625  if( cryptStatusError( status ) )
626  {
627  retExt( status,
628  ( status, SESSION_ERRINFO,
629  "Error recording password for user '%s'",
630  sanitiseString( userNameBuffer, CRYPT_MAX_TEXTSIZE,
631  userNameLength ) ) );
632  }
633 
634  *userAuthInfo = USERAUTH_CALLERCHECK;
635  return( OK_SPECIAL );
636  }
637 
638 /****************************************************************************
639 * *
640 * Perform Server-side Authentication *
641 * *
642 ****************************************************************************/
643 
644 /* Server-side authentication is a critical authorisation step so we don't
645  want to make it vulnerable to a simple boolean control-variable overwrite
646  that an attacker can use to bypass the authentication check. To do this
647  we require confirmation both via the function return status and the by-
648  reference value, we require the two values to be different values (one a
649  zero value, the other a small nonzero integer value), and we store them
650  separated by a canary that's also checked when the status values are
651  checked. In theory it's still possible to overwrite this if an exact
652  pattern of 96 bits (on a 32-bit system) can be placed in memory, but this
653  is now vastly harder than simply entering an over-long user name or
654  password that converts the following boolean-flag zero value to a nonzero
655  value */
656 
657 typedef struct {
658  USERAUTH_TYPE userAuthInfo;
659  int canary;
660  int status;
661  } FAILSAFE_AUTH_INFO;
662 
663 static const FAILSAFE_AUTH_INFO failsafeAuthSuccessTemplate = \
664  { USERAUTH_SUCCESS, OK_SPECIAL, CRYPT_OK };
665 
666 #define initFailsafeAuthInfo( authInfo ) \
667  { \
668  memset( ( authInfo ), 0, sizeof( FAILSAFE_AUTH_INFO ) ); \
669  ( authInfo )->canary = OK_SPECIAL; \
670  ( authInfo )->status = CRYPT_ERROR_FAILED; \
671  }
672 
673 /* Process the client's authentication */
674 
676 int processFixedAuth( INOUT SESSION_INFO *sessionInfoPtr )
677  {
678  FAILSAFE_AUTH_INFO authInfo = DUMMY_INIT_STRUCT;
679  BOOLEAN isFatalError;
680  int authAttempts;
681 
682  /* The caller has specified user credentials to match against so we can
683  perform a basic pass/fail check against the client-supplied
684  information. Since this is an all-or-nothing process at the end of
685  which the client is either authenticated or not authenticated we
686  allow the traditional three retries (or until the first fatal error)
687  to get it right */
688  for( isFatalError = FALSE, authAttempts = 0;
689  !isFatalError && authAttempts < 3; authAttempts++ )
690  {
691  /* Process the user authentication and, if it's a dummy read, try a
692  second time. This can only happen on the first iteration since
693  the initialAuth flag for processUserAuth() is set to FALSE for
694  subsequent attempts, which means that further dummy reads are
695  disallowed */
696  initFailsafeAuthInfo( &authInfo );
697  authInfo.status = processUserAuth( sessionInfoPtr,
698  &authInfo.userAuthInfo,
699  CREDENTIAL_USERNAME_PASSWORD,
700  ( authAttempts <= 0 ) ? TRUE : FALSE );
701  if( authInfo.status == OK_SPECIAL && \
702  authInfo.userAuthInfo == USERAUTH_NOOP )
703  {
704  /* It was a dummy read, try again */
705  authInfo.status = processUserAuth( sessionInfoPtr,
706  &authInfo.userAuthInfo,
707  CREDENTIAL_USERNAME_PASSWORD,
708  FALSE );
709  }
710  if( cryptStatusOK( authInfo.status ) && \
711  !memcmp( &authInfo, &failsafeAuthSuccessTemplate, \
712  sizeof( FAILSAFE_AUTH_INFO ) ) )
713  {
714  /* The user has authenticated successfully and this fact has
715  been verified in a (reasonably) failsafe manner, we're
716  done */
717  return( sendResponseSuccess( sessionInfoPtr ) );
718  }
719  ENSURES( cryptStatusError( authInfo.status ) );
720 
721  /* If the authentication processing returns anything other than a
722  wrong-key error due to a failed authentication attempt then the
723  error is fatal */
724  if( authInfo.status != CRYPT_ERROR_WRONGKEY )
725  isFatalError = TRUE;
726 
727  /* Tell the client that the authentication failed. If it's the
728  final attempt (either because it's a fatal error or because the
729  user has used up all of their retries) then we ignore any return
730  code since it's secondary to the authentication-failed status
731  that we're returning, and in any case since the caller is going
732  to close the session in response to this it'll be signalled to
733  the client one way or another. In addition we set the
734  allowFurtherAuth flag to FALSE to indicate to the client that
735  they can't continue past this point */
736  if( isFatalError || authAttempts >= 2 )
737  ( void ) sendResponseFailure( sessionInfoPtr, FALSE );
738  else
739  {
740  int status;
741 
742  status = sendResponseFailure( sessionInfoPtr, TRUE );
743  if( cryptStatusError( status ) )
744  return( status );
745  }
746  }
747  ENSURES( authInfo.status != OK_SPECIAL );
748 
749  /* The user still hasn't successfully authenticated after multiple
750  attempts, we're done */
751  return( authInfo.status );
752  }
753 
755 int processServerAuth( INOUT SESSION_INFO *sessionInfoPtr,
756  const BOOLEAN userInfoPresent )
757  {
758  SSH_INFO *sshInfo = sessionInfoPtr->sessionSSH;
759  USERAUTH_TYPE userAuthInfo;
760  int status = DUMMY_INIT;
761 
762  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
763 
764  /* If the caller has specified user credentials to match against we
765  perform a basic pass/fail check against the client-supplied
766  information */
767  if( userInfoPresent )
768  return( processFixedAuth( sessionInfoPtr ) );
769 
770  /* The caller hasn't supplied any user credentials to match against,
771  indicating that they're going to perform an on-demand match. If this
772  is a second pass through then the caller will have supplied an
773  authentication response to be sent to the client, in which case we
774  send it before continuing */
775  if( sshInfo->authRead )
776  {
777  /* If the caller accepted the authentication, we're done */
778  if( sessionInfoPtr->authResponse == AUTHRESPONSE_SUCCESS )
779  return( sendResponseSuccess( sessionInfoPtr ) );
780 
781  /* The caller denied the authentication, inform the client and go
782  back to asking what to do at the next authentication attempt */
783  status = sendResponseFailure( sessionInfoPtr, TRUE );
784  if( cryptStatusError( status ) )
785  return( status );
786  sessionInfoPtr->authResponse = AUTHRESPONSE_NONE;
787  }
788 
789  /* Process the user authentication and, if it's a dummy read, try a
790  second time. The first time that we're called (authRead == FALSE) we
791  accept and record the user name supplied by the client, on any
792  subsequent calls (authRead == TRUE) we require a match for the
793  previously-supplied user name.
794 
795  Note that we don't use the complex failsafe-check method used by
796  processFixedAuth() because we're not performing the authorisation
797  check here but simply passing the data back to the caller */
798  if( !sshInfo->authRead )
799  {
800  status = processUserAuth( sessionInfoPtr, &userAuthInfo,
801  CREDENTIAL_NONE, TRUE );
802  if( status == OK_SPECIAL && userAuthInfo == USERAUTH_NOOP )
803  {
804  /* It was a dummy read, try again */
805  status = processUserAuth( sessionInfoPtr, &userAuthInfo,
806  CREDENTIAL_USERNAME, FALSE );
807  }
808  }
809  else
810  {
811  status = processUserAuth( sessionInfoPtr, &userAuthInfo,
812  CREDENTIAL_USERNAME, FALSE );
813  ENSURES( !( status == OK_SPECIAL && \
814  userAuthInfo == USERAUTH_NOOP ) );
815  }
816  sshInfo->authRead = TRUE;
817  ENSURES( cryptStatusError( status ) || status == OK_SPECIAL );
818 
819  return( ( status == OK_SPECIAL ) ? CRYPT_ENVELOPE_RESOURCE : status );
820  }
821 #endif /* USE_SSH */