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