cryptlib  3.4.1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros
ssh.c
Go to the documentation of this file.
1 /****************************************************************************
2 * *
3 * cryptlib SSH Session 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 #if defined( USE_SSH ) || defined( USE_SSH1 )
21 
22 /****************************************************************************
23 * *
24 * Utility Functions *
25 * *
26 ****************************************************************************/
27 
28 /* Initialise and destroy the handshake state information */
29 
31 static int initHandshakeInfo( OUT SSH_HANDSHAKE_INFO *handshakeInfo )
32  {
33  assert( isWritePtr( handshakeInfo, sizeof( SSH_HANDSHAKE_INFO ) ) );
34 
35  /* Initialise the handshake state information values */
36  memset( handshakeInfo, 0, sizeof( SSH_HANDSHAKE_INFO ) );
37  handshakeInfo->iExchangeHashContext = \
38  handshakeInfo->iExchangeHashAltContext = \
39  handshakeInfo->iServerCryptContext = CRYPT_ERROR;
40  handshakeInfo->exchangeHashAlgo = CRYPT_ALGO_SHA1;
41 
42  return( CRYPT_OK );
43  }
44 
45 STDC_NONNULL_ARG( ( 1 ) ) \
46 static void destroyHandshakeInfo( INOUT SSH_HANDSHAKE_INFO *handshakeInfo )
47  {
48  assert( isWritePtr( handshakeInfo, sizeof( SSH_HANDSHAKE_INFO ) ) );
49 
50  /* Destroy any active contexts. We need to do this here (even though
51  it's also done in the general session code) to provide a clean exit in
52  case the session activation fails, so that a second activation attempt
53  doesn't overwrite still-active contexts */
54  if( handshakeInfo->iExchangeHashContext != CRYPT_ERROR )
55  krnlSendNotifier( handshakeInfo->iExchangeHashContext,
57  if( handshakeInfo->iExchangeHashAltContext != CRYPT_ERROR )
58  krnlSendNotifier( handshakeInfo->iExchangeHashAltContext,
60  if( handshakeInfo->iServerCryptContext != CRYPT_ERROR )
61  krnlSendNotifier( handshakeInfo->iServerCryptContext,
63 
64  /* Clear the handshake state information, then reset it to explicit non-
65  initialised values */
66  zeroise( handshakeInfo, sizeof( SSH_HANDSHAKE_INFO ) );
67  ( void ) initHandshakeInfo( handshakeInfo );
68  }
69 
70 /* Read the SSH version information string */
71 
73 static int readCharFunction( INOUT TYPECAST( STREAM * ) void *streamPtr )
74  {
76  BYTE ch;
77  int status;
78 
79  assert( isWritePtr( stream, sizeof( STREAM ) ) );
80 
81  status = sread( stream, &ch, 1 );
82  return( cryptStatusError( status ) ? status : byteToInt( ch ) );
83  }
84 
86 static int readVersionString( INOUT SESSION_INFO *sessionInfoPtr )
87  {
88  const BYTE *versionStringPtr;
89  const char *peerType = isServer( sessionInfoPtr ) ? "Client" : "Server";
90  int versionStringLength, length = DUMMY_INIT, linesRead;
91  int status;
92 
93  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
94 
95  /* Read the server version information, with the format for the ID
96  string being "SSH-protocolversion-softwareversion comments", which
97  (in the original ssh.com interpretation) was "SSH-x.y-x.y vendorname"
98  (e.g. "SSH-2.0-3.0.0 SSH Secure Shell") but for almost everyone else
99  is "SSH-x.y-vendorname*version" (e.g "SSH-2.0-OpenSSH_3.0").
100 
101  This version information handling is rather ugly since it's a
102  variable-length string terminated with a newline, so we have to use
103  readTextLine() as if we were talking HTTP.
104 
105  Unfortunately the SSH RFC then further complicates this by allowing
106  implementations to send non-version-related text lines before the
107  version line. The theory is that this will allow applications like
108  TCP wrappers to display a (human-readable) error message before
109  disconnecting, however some installations use it to display general
110  banners before the ID string. Since the RFC doesn't provide any
111  means of distinguishing this banner information from arbitrary data
112  we can't quickly reject attempts to connect to something that isn't
113  an SSH server. In other words we have to sit here waiting for
114  further data in the hope that eventually an SSH ID turns up, until
115  such time as the connect timeout expires */
116  for( linesRead = 0; linesRead < 20; linesRead++ )
117  {
118  BOOLEAN isTextDataError;
119 
120  /* Get a line of input. Since this is the first communication that
121  we have with the remote system we're a bit more loquacious about
122  diagnostics in the event of an error */
123  status = readTextLine( readCharFunction, &sessionInfoPtr->stream,
124  sessionInfoPtr->receiveBuffer,
125  SSH_ID_MAX_SIZE, &length, &isTextDataError );
126  if( cryptStatusError( status ) )
127  {
128  const char *lcPeerType = isServer( sessionInfoPtr ) ? \
129  "client" : "server";
130  ERROR_INFO errorInfo; /* Lowercase version of peerType */
131 
132  sNetGetErrorInfo( &sessionInfoPtr->stream, &errorInfo );
133  retExtErr( status,
134  ( status, SESSION_ERRINFO, &errorInfo,
135  "Error reading %s's SSH identifier string: ",
136  lcPeerType ) );
137  }
138 
139  /* If it's the SSH ID/version string, we're done */
140  if( length >= SSH_ID_SIZE && \
141  !memcmp( sessionInfoPtr->receiveBuffer, SSH_ID, SSH_ID_SIZE ) )
142  break;
143  }
144 
145  /* The peer shouldn't be throwing infinite amounts of junk at us, if we
146  don't get an SSH ID after reading 20 lines of input then there's a
147  problem */
148  if( linesRead >= 20 )
149  {
152  "%s sent excessive amounts of text without sending an "
153  "SSH identifier string", peerType ) );
154  }
155 
156  /* Make sure that we got enough data to work with. We need at least
157  "SSH-" (ID, size SSH_ID_SIZE) + "x.y-" (protocol version) + "a.b"
158  (software version) + "str" (software ID) */
159  if( length < SSH_ID_SIZE + 10 || length > SSH_ID_MAX_SIZE )
160  {
163  "%s sent truncated identifier string '%s'", peerType,
164  sanitiseString( sessionInfoPtr->receiveBuffer,
165  CRYPT_MAX_TEXTSIZE, length ) ) );
166  }
167 
168  /* Null-terminate the string so that we can hash it to create the SSHv2
169  exchange hash. We actually set a block of memory following the
170  string to zeroes (rather than just one null character) in case of
171  any slight range errors in the free-format text-string checks that
172  are required further on to identify bugs in SSH implementations */
173  memset( sessionInfoPtr->receiveBuffer + length, 0, 16 );
174 
175  /* Determine which version we're talking to */
176  switch( sessionInfoPtr->receiveBuffer[ SSH_ID_SIZE ] )
177  {
178  case '1':
179  if( !memcmp( sessionInfoPtr->receiveBuffer + SSH_ID_SIZE,
180  "1.99", 4 ) )
181  {
182  /* SSHv2 server in backwards-compatibility mode */
183  sessionInfoPtr->version = 2;
184  break;
185  }
186 
187 #ifdef USE_SSH1
188  /* If the caller has specifically asked for SSHv2 but all that
189  the server offers is SSHv1, we can't continue */
190  if( sessionInfoPtr->version >= 2 )
191  {
194  "Server can only do SSHv1 when SSHv2 was "
195  "requested" ) );
196  }
197  sessionInfoPtr->version = 1;
198  break;
199 #else
202  "Server can only do SSHv1" ) );
203 #endif /* USE_SSH1 */
204 
205  case '2':
206  sessionInfoPtr->version = 2;
207  break;
208 
209  default:
212  "Invalid SSH version '%s'",
213  sanitiseString( &sessionInfoPtr->receiveBuffer[ SSH_ID_SIZE ],
214  CRYPT_MAX_TEXTSIZE, 1 ) ) );
215  }
216 
217  /* Find the end of the protocol version substring, i.e. locate whatever
218  follows the "SSH-x.y" portion of the ID string by searching for the
219  second '-' delimiter */
220  for( versionStringLength = length - SSH_ID_SIZE, \
221  versionStringPtr = sessionInfoPtr->receiveBuffer + SSH_ID_SIZE;
222  versionStringLength > 0 && *versionStringPtr != '-';
223  versionStringLength--, versionStringPtr++ );
224  if( versionStringLength < 4 )
225  {
226  /* We need at least "-x.y" after the initial ID string, we can't
227  require any more than this because of CuteFTP (see note below) */
230  "%s sent malformed identifier string '%s'", peerType,
231  sanitiseString( sessionInfoPtr->receiveBuffer,
232  CRYPT_MAX_TEXTSIZE, length ) ) );
233  }
234  versionStringPtr++, versionStringLength--; /* Skip '-' */
235  ENSURES( versionStringLength >= 3 && \
236  versionStringLength < SSH_ID_MAX_SIZE ); /* From earlier checks */
237 
238  /* Check whether the peer is using cryptlib */
239  if( versionStringLength >= 8 && \
240  !memcmp( versionStringPtr, "cryptlib", 8 ) )
241  sessionInfoPtr->flags |= SESSION_ISCRYPTLIB;
242 
243  /* Check for various servers that require special-case bug workarounds.
244  The versions that we check for are:
245 
246  BitVise WinSSHD:
247  This one is a bit hard to identify because it's built on top of
248  their SSH library which changes names from time to time, so for
249  WinSSHD 4.x it was identified via the vendor ID string
250  "sshlib: WinSSHD 4.yy" while for WinSSHD 5.x it was identified
251  via the vendor ID string "FlowSsh: WinSSHD 5.xx". In theory we
252  could handle this by skipping the library name and looking
253  further inside the string for the "WinSSHD" identifier, but then
254  there's another version that uses "SrSshServer" instead of
255  "WinSSHD", and there's also a "GlobalScape" ID used by CuteFTP
256  (which means that CuteFTP might have finally fixed their buggy
257  implementation of SSH by using someone else's). As a result we
258  can see any of "sshlib: <vendor>" or "FlowSsh: <vendor>", which
259  we use as the identifier.
260 
261  Sends mismatched compression algorithm IDs, no compression
262  client -> server, zlib server -> client, but works fine if no
263  compression is selected, for versions 4.x and up.
264 
265  CuteFTP:
266  Drops the connection after seeing the server hello with no
267  (usable) error indication. This implementation is somewhat
268  tricky to detect since it identifies itself using the dubious
269  vendor ID string "1.0" (see the ssh.com note below), this
270  problem still hasn't been fixed several years after the vendor
271  was notified of it, indicating that it's unlikely to ever be
272  fixed. This runs into problems with other implementations like
273  BitVise WinSSHD 5.x, which has an ID string beginning with "1.0"
274  (see the comment for WinSSHD above) so when trying to identify
275  CuteFTP we check for an exact match for "1.0" as the ID string.
276 
277  CuteFTP also uses the SSHv1 backwards-compatible version string
278  "1.99" even though it can't actually do SSHv1, which means that
279  it'll fail if it ever tries to connect to an SSHv1 peer.
280 
281  OpenSSH:
282  Omits hashing the exchange hash length when creating the hash
283  to be signed for client auth for version 2.0 (all subversions).
284 
285  Requires RSA signatures to be padded out with zeroes to the RSA
286  modulus size for all versions from 2.5 to 3.2.
287 
288  Can't handle "password" as a PAM sub-method (meaning an
289  authentication method hint), it responds with an authentication-
290  failed response as soon as we send the PAM authentication
291  request, for versions 3.8 onwards (this doesn't look like it'll
292  get fixed any time soon so we enable it for all newer versions
293  until further notice).
294 
295  Putty:
296  Sends zero-length SSH_MSG_IGNORE messages for version 0.59.
297 
298  ssh.com:
299  This implementation puts the version number first so if we find
300  something without a vendor name at the start we treat it as an
301  ssh.com version. However, Van Dyke's SSH server VShell also
302  uses the ssh.com-style identification (fronti nulla fides) so
303  when we check for the ssh.com implementation we habe to make
304  sure that it isn't really VShell. In addition CuteFTP
305  advertises its implementation as "1.0" (without any vendor
306  name), which is going to cause problems in the future when they
307  move to 2.x.
308 
309  Omits the DH-derived shared secret when hashing the keying
310  material for versions identified as "2.0.0" (all
311  sub-versions) and "2.0.10".
312 
313  Uses an SSH2_FIXED_KEY_SIZE-sized key for HMAC instead of the de
314  facto 160 bits for versions identified as "2.0.", "2.1 ", "2.1.",
315  and "2.2." (i.e. all sub-versions of 2.0, 2.1, and 2.2), and
316  specifically version "2.3.0". This was fixed in 2.3.1.
317 
318  Omits the signature algorithm name for versions identified as
319  "2.0" and "2.1" (all sub-versions), requiring a complex rewrite
320  of the signature data in order to process it.
321 
322  Mishandles large window sizes in a variety of ways. Typically
323  for any size over about 8M the server gets slower and slower,
324  eventually more or less grinding to halt at about 64MB
325  (presumably some O(n^2) algorithm, although how you manage to
326  do this for a window-size notification is a mystery). Some
327  versions also reportedly require a window adjust for every 32K
328  or so sent no matter what the actual window size is, which seems
329  to occur for versions identified as "2.0" and "2.1" (all
330  sub-versions). This may be just a variant of the general mis-
331  handling of large window sizes so we treat it as the same thing
332  and advertise a smaller-than-optimal window which, as a side-
333  effect, results in a constant flow of window adjusts.
334 
335  Omits hashing the exchange hash length when creating the hash
336  to be signed for client auth for versions 2.1 and 2.2 (all
337  subversions).
338 
339  Sends an empty SSH_SERVICE_ACCEPT response for version 2.0 (all
340  subversions).
341 
342  Sends an empty userauth-failure response if no authentication is
343  required instead of allowing the auth, for uncertain versions
344  probably in the 2.x range.
345 
346  Dumps text diagnostics (that is, raw text strings rather than
347  SSH error packets) onto the connection if something unexpected
348  occurs, for uncertain versions probably in the 2.x range.
349 
350  Van Dyke:
351  Omits hashing the exchange hash length when creating the hash to
352  be signed for client auth for version 3.0 (SecureCRT = SSH) and
353  1.7 (SecureFX = SFTP).
354 
355  WeOnlyDo:
356  Has the same mismatched compression algorithm ID bug as BitVise
357  WinSSHD (see comment above) for unknown versions above about 2.x.
358 
359  Further quirks and peculiarities abound, some are handled automatically
360  by workarounds in the code and for the rest they're fortunately rare
361  enough (mostly for long-obsolete SSHv1 versions) that we don't have to
362  go out of our way to handle them */
363  if( versionStringLength >= 8 + 3 && \
364  !memcmp( versionStringPtr, "OpenSSH_", 8 ) )
365  {
366  const BYTE *subVersionStringPtr = versionStringPtr + 8;
367 
368  if( !memcmp( subVersionStringPtr, "2.0", 3 ) )
369  sessionInfoPtr->protocolFlags |= SSH_PFLAG_NOHASHLENGTH;
370  if( !memcmp( subVersionStringPtr, "3.8", 3 ) || \
371  !memcmp( subVersionStringPtr, "3.9", 3 ) || \
372  ( versionStringLength >= 8 + 4 && \
373  !memcmp( subVersionStringPtr, "3.10", 4 ) ) || \
374  *subVersionStringPtr >= '4' )
375  sessionInfoPtr->protocolFlags |= SSH_PFLAG_PAMPW;
376  if( ( !memcmp( subVersionStringPtr, "2.", 2 ) && \
377  subVersionStringPtr[ 2 ] >= '5' ) || \
378  ( !memcmp( subVersionStringPtr, "3.", 2 ) && \
379  subVersionStringPtr[ 2 ] <= '2' ) )
380  sessionInfoPtr->protocolFlags |= SSH_PFLAG_RSASIGPAD;
381 
382  return( CRYPT_OK );
383  }
384  if( versionStringLength >= 14 + 4 && \
385  !memcmp( versionStringPtr, "PuTTY_Release_", 14 ) )
386  {
387  const BYTE *subVersionStringPtr = versionStringPtr + 14;
388 
389  if( !memcmp( subVersionStringPtr, "0.59", 4 ) )
390  sessionInfoPtr->protocolFlags |= SSH_PFLAG_ZEROLENIGNORE;
391 
392  return( CRYPT_OK );
393  }
394  if( versionStringLength >= 9 && \
395  !memcmp( versionStringPtr, "WeOnlyDo ", 9 ) )
396  {
397  const BYTE *subVersionStringPtr = versionStringPtr + 9;
398 
399  if( subVersionStringPtr[ 0 ] >= '2' )
400  sessionInfoPtr->protocolFlags |= SSH_PFLAG_ASYMMCOPR;
401  return( CRYPT_OK );
402  }
403  if( isDigit( *versionStringPtr ) )
404  {
405  const BYTE *vendorIDString;
406  const int versionDigit = byteToInt( *versionStringPtr );
407  int vendorIDStringLength;
408 
409  /* Look for a vendor ID after the version information. This breaks
410  down the string "[SSH-x.y-]x.yy vendor-text" to
411  'versionStringPtr = "x.yy"' and 'vendorIDString = "vendor-text"' */
412  for( vendorIDStringLength = versionStringLength, \
413  vendorIDString = versionStringPtr;
414  vendorIDStringLength > 0 && *vendorIDString != ' ';
415  vendorIDStringLength--, vendorIDString++ );
416  if( vendorIDStringLength > 1 )
417  {
418  /* There's a vendor ID present, skip the ' ' separator */
419  vendorIDString++, vendorIDStringLength--;
420  }
421  ENSURES( vendorIDStringLength >= 0 && \
422  vendorIDStringLength < SSH_ID_MAX_SIZE );
423  switch( versionDigit )
424  {
425  case '1':
426  if( versionStringLength >= 12 && \
427  !memcmp( versionStringPtr, "1.7 SecureFX", 12 ) )
428  sessionInfoPtr->protocolFlags |= SSH_PFLAG_NOHASHLENGTH;
429  if( versionStringLength == 3 && \
430  !memcmp( versionStringPtr, "1.0", 3 ) )
431  sessionInfoPtr->protocolFlags |= SSH_PFLAG_CUTEFTP;
432  if( ( vendorIDStringLength > 8 && \
433  !memcmp( vendorIDString, "sshlib: ", 8 ) > 0 ) || \
434  ( vendorIDStringLength > 9 && \
435  !memcmp( vendorIDString, "FlowSsh: ", 9 ) > 0 ) )
436  sessionInfoPtr->protocolFlags |= SSH_PFLAG_ASYMMCOPR;
437  break;
438 
439  case '2':
440  if( vendorIDStringLength >= 6 && \
441  !memcmp( vendorIDString, "VShell", 6 ) )
442  break; /* Make sure that it isn't VShell */
443 
444  /* ssh.com 2.x versions have quite a number of bugs so we
445  check for them as a group */
446  if( ( versionStringLength >= 5 && \
447  !memcmp( versionStringPtr, "2.0.0", 5 ) ) || \
448  ( versionStringLength >= 6 && \
449  !memcmp( versionStringPtr, "2.0.10", 6 ) ) )
450  sessionInfoPtr->protocolFlags |= SSH_PFLAG_NOHASHSECRET;
451  if( !memcmp( versionStringPtr, "2.0", 3 ) || \
452  !memcmp( versionStringPtr, "2.1", 3 ) )
453  sessionInfoPtr->protocolFlags |= SSH_PFLAG_SIGFORMAT;
454  if( !memcmp( versionStringPtr, "2.0", 3 ) || \
455  !memcmp( versionStringPtr, "2.1", 3 ) )
456  sessionInfoPtr->protocolFlags |= SSH_PFLAG_WINDOWSIZE;
457  if( !memcmp( versionStringPtr, "2.1", 3 ) || \
458  !memcmp( versionStringPtr, "2.2", 3 ) )
459  sessionInfoPtr->protocolFlags |= SSH_PFLAG_NOHASHLENGTH;
460  if( !memcmp( versionStringPtr, "2.0", 3 ) || \
461  !memcmp( versionStringPtr, "2.1", 3 ) || \
462  !memcmp( versionStringPtr, "2.2", 3 ) || \
463  ( versionStringLength >= 5 && \
464  !memcmp( versionStringPtr, "2.3.0", 5 ) ) )
465  sessionInfoPtr->protocolFlags |= SSH_PFLAG_HMACKEYSIZE;
466  if( !memcmp( versionStringPtr, "2.0", 3 ) )
467  sessionInfoPtr->protocolFlags |= SSH_PFLAG_EMPTYSVCACCEPT;
468  if( !memcmp( versionStringPtr, "2.", 2 ) )
469  {
470  /* Not sure of the exact versions where this occurs */
471  sessionInfoPtr->protocolFlags |= SSH_PFLAG_EMPTYUSERAUTH;
472  sessionInfoPtr->protocolFlags |= SSH_PFLAG_TEXTDIAGS;
473  }
474  break;
475 
476  case '3':
477  if( versionStringLength >= 13 && \
478  !memcmp( versionStringPtr, "3.0 SecureCRT", 13 ) )
479  sessionInfoPtr->protocolFlags |= SSH_PFLAG_NOHASHLENGTH;
480  break;
481 
482  case '5':
483  if( versionStringLength >= 10 && \
484  !memcmp( vendorIDString, "SSH Tectia", 10 ) )
485  sessionInfoPtr->protocolFlags |= SSH_PFLAG_DUMMYUSERAUTH;
486  }
487  }
488 
489  return( CRYPT_OK );
490  }
491 
492 /****************************************************************************
493 * *
494 * Init/Shutdown Functions *
495 * *
496 ****************************************************************************/
497 
498 /* Connect to an SSH server */
499 
500 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
501 static int initVersion( INOUT SESSION_INFO *sessionInfoPtr,
502  INOUT SSH_HANDSHAKE_INFO *handshakeInfo )
503  {
504  MESSAGE_CREATEOBJECT_INFO createInfo;
505  int status;
506 
507  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
508  assert( isWritePtr( handshakeInfo, sizeof( SSH_HANDSHAKE_INFO ) ) );
509 
510  /* Set up handshake function pointers based on the protocol version */
511  status = readVersionString( sessionInfoPtr );
512  if( cryptStatusError( status ) )
513  return( status );
514 #ifdef USE_SSH1
515  if( sessionInfoPtr->version == 1 )
516  {
517  initSSH1processing( sessionInfoPtr, handshakeInfo,
518  isServer( sessionInfoPtr ) ? TRUE : FALSE );
519  sessionInfoPtr->sendBufStartOfs = \
520  sessionInfoPtr->receiveBufStartOfs = \
521  sessionInfoPtr->protocolInfo->sendBufStartOfs;
522  return( CRYPT_OK );
523  }
524 #endif /* USE_SSH1 */
525  initSSH2processing( sessionInfoPtr, handshakeInfo,
526  isServer( sessionInfoPtr ) ? TRUE : FALSE );
527 
528  /* SSHv2 hashes parts of the handshake messages for integrity-protection
529  purposes so we create a context for the hash. In addition since the
530  handshake can retroactively switch to a different hash algorithm mid-
531  exchange we have to speculatively hash the messages with SHA2 as well
532  as SHA1 in case the other side decides to switch */
535  &createInfo, OBJECT_TYPE_CONTEXT );
536  if( cryptStatusError( status ) )
537  return( status );
538  handshakeInfo->iExchangeHashContext = createInfo.cryptHandle;
540  {
543  &createInfo, OBJECT_TYPE_CONTEXT );
544  if( cryptStatusError( status ) )
545  {
546  krnlSendNotifier( handshakeInfo->iExchangeHashContext,
548  handshakeInfo->iExchangeHashContext = CRYPT_ERROR;
549  return( status );
550  }
551  handshakeInfo->iExchangeHashAltContext = createInfo.cryptHandle;
552  }
553 
554  return( CRYPT_OK );
555  }
556 
557 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
558 static int completeHandshake( INOUT SESSION_INFO *sessionInfoPtr,
559  INOUT SSH_HANDSHAKE_INFO *handshakeInfo )
560  {
561  int status;
562 
563  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
564  assert( isWritePtr( handshakeInfo, sizeof( SSH_HANDSHAKE_INFO ) ) );
565 
566  status = handshakeInfo->completeHandshake( sessionInfoPtr,
567  handshakeInfo );
568  destroyHandshakeInfo( handshakeInfo );
569  if( cryptStatusError( status ) )
570  {
571  /* If we need confirmation from the user before continuing, let
572  them know */
573  if( status == CRYPT_ENVELOPE_RESOURCE )
574  return( status );
575 
576  /* At this point we could be in the secure state so we have to
577  keep the security information around until after we've called
578  the shutdown function, which could require sending secured
579  data */
580  disableErrorReporting( sessionInfoPtr );
581  sessionInfoPtr->shutdownFunction( sessionInfoPtr );
582  destroySecurityContextsSSH( sessionInfoPtr );
583  return( status );
584  }
585 
586  return( CRYPT_OK );
587  }
588 
590 static int completeStartup( INOUT SESSION_INFO *sessionInfoPtr )
591  {
593  int status;
594 
595  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
596 
597  /* Initialise the handshake information and begin the handshake. Since
598  we don't know what type of peer we're talking to and since the
599  protocols aren't compatible in anything but name we have to peek at
600  the peer's initial communication and, in initVersion(), redirect
601  function pointers based on that */
602  status = initHandshakeInfo( &handshakeInfo );
603  if( cryptStatusOK( status ) )
604  status = initVersion( sessionInfoPtr, &handshakeInfo );
605  if( cryptStatusOK( status ) )
606  status = handshakeInfo.beginHandshake( sessionInfoPtr,
607  &handshakeInfo );
608  if( cryptStatusError( status ) )
609  {
610  /* If we run into an error at this point we need to disable error-
611  reporting during the shutdown phase since we've already got
612  status information present from the already-encountered error */
613  destroyHandshakeInfo( &handshakeInfo );
614  disableErrorReporting( sessionInfoPtr );
615  sessionInfoPtr->shutdownFunction( sessionInfoPtr );
616  return( status );
617  }
618 
619  /* Exchange a key with the server */
620  status = handshakeInfo.exchangeKeys( sessionInfoPtr, &handshakeInfo );
621  if( cryptStatusError( status ) )
622  {
623  destroySecurityContextsSSH( sessionInfoPtr );
624  destroyHandshakeInfo( &handshakeInfo );
625  disableErrorReporting( sessionInfoPtr );
626  sessionInfoPtr->shutdownFunction( sessionInfoPtr );
627  return( status );
628  }
629 
630  /* Complete the handshake */
631  return( completeHandshake( sessionInfoPtr, &handshakeInfo ) );
632  }
633 
634 /* Start an SSH server */
635 
637 static int serverStartup( INOUT SESSION_INFO *sessionInfoPtr )
638  {
639  const char *idString = ( sessionInfoPtr->version == 1 ) ? \
640  SSH1_ID_STRING "\n" : SSH2_ID_STRING "\r\n";
641  const int idStringLen = SSH_ID_STRING_SIZE + \
642  ( ( sessionInfoPtr->version == 1 ) ? 1 : 2 );
643  int status;
644 
645  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
646 
647  /* If we're completing a handshake that was interrupted while we got
648  confirmation of the client auth, skip the initial handshake stages
649  and go straight to the handshake completion stage */
650  if( sessionInfoPtr->flags & SESSION_PARTIALOPEN )
651  {
653 
654  status = initHandshakeInfo( &handshakeInfo );
655  if( cryptStatusError( status ) )
656  return( status );
657  initSSH2processing( sessionInfoPtr, &handshakeInfo, TRUE );
658  return( completeHandshake( sessionInfoPtr, &handshakeInfo ) );
659  }
660 
661  /* Send the ID string to the client before we continue with the
662  handshake. We don't have to wait for any input from the client since
663  we know that if we got here there's a client listening. Note that
664  standard cryptlib practice for sessions is to wait for input from the
665  client, make sure that it looks reasonable, and only then send back a
666  reply of any kind. If anything that doesn't look right arrives, we
667  close the connection immediately without any response. Unfortunately
668  this isn't possible with SSH, which requires that the server send data
669  before the client does */
670  status = swrite( &sessionInfoPtr->stream, idString, idStringLen );
671  if( cryptStatusError( status ) )
672  return( status );
673 
674  /* Complete the handshake in the shared code */
675  return( completeStartup( sessionInfoPtr ) );
676  }
677 
678 /****************************************************************************
679 * *
680 * Control Information Management Functions *
681 * *
682 ****************************************************************************/
683 
684 #ifdef USE_SSH_EXTENDED
685 
686 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
687 static int getAttributeFunction( INOUT SESSION_INFO *sessionInfoPtr,
688  OUT void *data,
690  {
691  int status;
692 
693  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
694 
700 
701  if( type == CRYPT_SESSINFO_SSH_CHANNEL || \
703  {
704  status = getChannelAttribute( sessionInfoPtr, type, data );
705  }
706  else
707  {
709 
710  status = getChannelAttributeS( sessionInfoPtr, type, msgData->data,
711  msgData->length, &msgData->length );
712  }
713  return( ( status == CRYPT_ERROR ) ? CRYPT_ARGERROR_NUM1 : status );
714  }
715 
716 CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
717 static int setAttributeFunction( INOUT SESSION_INFO *sessionInfoPtr,
718  IN const void *data,
720  {
721  int value = DUMMY_INIT, status;
722 
723  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
724  assert( isReadPtr( data, sizeof( int ) ) );
725 
731 
732  /* Get the data value if it's an integer parameter */
733  if( type == CRYPT_SESSINFO_SSH_CHANNEL || \
735  value = *( ( int * ) data );
736 
737  /* If we're selecting a channel and there's unwritten data from a
738  previous write still in the buffer, we can't change the write
739  channel */
740  if( type == CRYPT_SESSINFO_SSH_CHANNEL && sessionInfoPtr->partialWrite )
741  return( CRYPT_ERROR_INCOMPLETE );
742 
743  /* If we're creating a new channel by setting the value to CRYPT_UNUSED,
744  create the new channel */
745  if( type == CRYPT_SESSINFO_SSH_CHANNEL && value == CRYPT_UNUSED )
746  {
747  /* If the session hasn't been activated yet, we can only create a
748  single channel during session activation, any subsequent ones
749  have to be handled later */
750  if( !( sessionInfoPtr->flags & SESSION_ISOPEN ) && \
751  getCurrentChannelNo( sessionInfoPtr, \
753  return( CRYPT_ERROR_INITED );
754 
755  return( createChannel( sessionInfoPtr ) );
756  }
757 
758  /* If we 're setting the channel-active attribute, this implicitly
759  activates or deactivates the channel rather than setting any
760  attribute value */
762  {
763  if( value )
764  return( sendChannelOpen( sessionInfoPtr ) );
765  return( closeChannel( sessionInfoPtr, FALSE ) );
766  }
767 
768  if( type == CRYPT_SESSINFO_SSH_CHANNEL )
769  status = setChannelAttribute( sessionInfoPtr, type, value );
770  else
771  {
772  const MESSAGE_DATA *msgData = data;
773 
774  status = setChannelAttributeS( sessionInfoPtr, type, msgData->data,
775  msgData->length );
776  }
777  return( ( status == CRYPT_ERROR ) ? CRYPT_ARGERROR_NUM1 : status );
778  }
779 #endif /* USE_SSH_EXTENDED */
780 
782 static int checkAttributeFunction( SESSION_INFO *sessionInfoPtr,
783  IN const void *data,
785  {
786  const CRYPT_CONTEXT cryptContext = *( ( CRYPT_CONTEXT * ) data );
788  HASHFUNCTION_ATOMIC hashFunctionAtomic;
789  STREAM stream;
790  BYTE buffer[ 128 + ( CRYPT_MAX_PKCSIZE * 4 ) + 8 ];
791  BYTE fingerPrint[ CRYPT_MAX_HASHSIZE + 8 ];
792  void *blobData = DUMMY_INIT_PTR;
793  int blobDataLength = DUMMY_INIT, hashSize, pkcAlgo, status;
794 
795  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
796 
797  REQUIRES( isAttribute( type ) );
798 
799  if( type != CRYPT_SESSINFO_PRIVATEKEY )
800  return( CRYPT_OK );
801 
802  /* If it's an ECC key then it has to be one of NIST { P256, P384, P521 }.
803  Unfortunately there's no easy way to determine whether the curve
804  being used is an SSH-compatible one or not since the user could load
805  their own custom 256-bit curve, or conversely load a known NIST curve
806  as a series of discrete key parameters, for now we just assume that a
807  curve of the given size is the correct one */
808  status = krnlSendMessage( cryptContext, IMESSAGE_GETATTRIBUTE,
809  &pkcAlgo, CRYPT_CTXINFO_ALGO );
810  if( cryptStatusError( status ) )
811  return( status );
812  if( isEccAlgo( pkcAlgo ) )
813  {
814  int keySize;
815 
816  status = krnlSendMessage( cryptContext, IMESSAGE_GETATTRIBUTE,
817  &keySize, CRYPT_CTXINFO_KEYSIZE );
818  if( cryptStatusError( status ) )
819  return( status );
820  if( keySize != bitsToBytes( 256 ) && \
821  keySize != bitsToBytes( 384 ) && \
822  keySize != bitsToBytes( 521 ) )
823  return( CRYPT_ARGERROR_NUM1 );
824  }
825 
826  /* Only the server key has a fingerprint */
827  if( !isServer( sessionInfoPtr ) )
828  return( CRYPT_OK );
829 
830  getHashAtomicParameters( CRYPT_ALGO_MD5, 0, &hashFunctionAtomic,
831  &hashSize );
832 
833  /* The fingerprint is computed from the "key blob", which is different
834  from the server key. The server key is the full key while the "key
835  blob" is only the raw key components (e, n for RSA, p, q, g, y for
836  DSA) so we have to skip the key header before we hash the key data:
837 
838  uint32 length
839  string algorithm
840  byte[] key_blob
841 
842  Note that, as with the old PGP 2.x key hash mechanism, this allows
843  key spoofing (although it isn't quite as bad as the PGP 2.x key
844  fingerprint mechanism) since it doesn't hash an indication of the key
845  type or format */
846  setMessageData( &msgData, buffer, 128 + ( CRYPT_MAX_PKCSIZE * 4 ) );
847  status = krnlSendMessage( cryptContext, IMESSAGE_GETATTRIBUTE_S,
848  &msgData, CRYPT_IATTRIBUTE_KEY_SSH );
849  if( cryptStatusError( status ) )
850  return( status );
851  sMemConnect( &stream, buffer, msgData.length );
852  readUint32( &stream ); /* Length */
853  status = readUniversal32( &stream ); /* Algorithm ID */
854  if( cryptStatusOK( status ) )
855  status = sMemGetDataBlockRemaining( &stream, &blobData,
856  &blobDataLength );
857  sMemDisconnect( &stream );
858  if( cryptStatusError( status ) )
859  return( status );
860  hashFunctionAtomic( fingerPrint, CRYPT_MAX_HASHSIZE, blobData,
861  blobDataLength );
862 
863  /* Add the fingerprint */
864  return( addSessionInfoS( &sessionInfoPtr->attributeList,
866  fingerPrint, hashSize ) );
867  }
868 
869 /****************************************************************************
870 * *
871 * Session Access Routines *
872 * *
873 ****************************************************************************/
874 
876 int setAccessMethodSSH( INOUT SESSION_INFO *sessionInfoPtr )
877  {
878  assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) );
879 
880  /* Set the access method pointers */
881 #ifdef USE_SSH_EXTENDED
882  sessionInfoPtr->getAttributeFunction = getAttributeFunction;
883  sessionInfoPtr->setAttributeFunction = setAttributeFunction;
884 #endif /* USE_SSH_EXTENDED */
885  sessionInfoPtr->checkAttributeFunction = checkAttributeFunction;
886  if( isServer( sessionInfoPtr ) )
887  {
888  sessionInfoPtr->transactFunction = serverStartup;
889  initSSH2processing( sessionInfoPtr, NULL, TRUE );
890  }
891  else
892  {
893  sessionInfoPtr->transactFunction = completeStartup;
894  initSSH2processing( sessionInfoPtr, NULL, FALSE );
895  }
896 
897  return( CRYPT_OK );
898  }
899 #endif /* USE_SSH || USE_SSH1 */