summaryrefslogtreecommitdiff
path: root/auth-rsa.c
blob: 82e399de703cffeb282053e321a2a3aec20ccf85 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
/*

auth-rsa.c

Author: Tatu Ylonen <ylo@cs.hut.fi>

Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
                   All rights reserved

Created: Mon Mar 27 01:46:52 1995 ylo

RSA-based authentication.  This code determines whether to admit a login
based on RSA authentication.  This file also contains functions to check
validity of the host key.

*/

#include "includes.h"
RCSID("$Id: auth-rsa.c,v 1.1.1.1 1999/10/27 03:42:43 damien Exp $");

#include "rsa.h"
#include "packet.h"
#include "xmalloc.h"
#include "ssh.h"
#include "mpaux.h"
#include "uidswap.h"

#include <openssl/rsa.h>
#include <openssl/md5.h>

/* Flags that may be set in authorized_keys options. */
extern int no_port_forwarding_flag;
extern int no_agent_forwarding_flag;
extern int no_x11_forwarding_flag;
extern int no_pty_flag;
extern char *forced_command;
extern struct envstring *custom_environment;

/* Session identifier that is used to bind key exchange and authentication
   responses to a particular session. */
extern unsigned char session_id[16];

/* The .ssh/authorized_keys file contains public keys, one per line, in the
   following format:
     options bits e n comment
   where bits, e and n are decimal numbers, 
   and comment is any string of characters up to newline.  The maximum
   length of a line is 8000 characters.  See the documentation for a
   description of the options.
*/

/* Performs the RSA authentication challenge-response dialog with the client,
   and returns true (non-zero) if the client gave the correct answer to
   our challenge; returns zero if the client gives a wrong answer. */

int
auth_rsa_challenge_dialog(unsigned int bits, BIGNUM *e, BIGNUM *n)
{
  BIGNUM *challenge, *encrypted_challenge, *aux;
  RSA *pk;
  BN_CTX *ctx = BN_CTX_new();
  unsigned char buf[32], mdbuf[16], response[16];
  MD5_CTX md;
  unsigned int i;
  int plen, len;

  encrypted_challenge = BN_new();
  challenge = BN_new();
  aux = BN_new();

  /* Generate a random challenge. */
  BN_rand(challenge, 256, 0, 0);
  BN_mod(challenge, challenge, n, ctx);
  
  /* Create the public key data structure. */
  pk = RSA_new();
  pk->e = BN_new();
  BN_copy(pk->e, e);
  pk->n = BN_new();
  BN_copy(pk->n, n);

  /* Encrypt the challenge with the public key. */
  rsa_public_encrypt(encrypted_challenge, challenge, pk);
  RSA_free(pk);

  /* Send the encrypted challenge to the client. */
  packet_start(SSH_SMSG_AUTH_RSA_CHALLENGE);
  packet_put_bignum(encrypted_challenge);
  packet_send();
  packet_write_wait();

  /* The response is MD5 of decrypted challenge plus session id. */
  len = BN_num_bytes(challenge);
  assert(len <= 32 && len);
  memset(buf, 0, 32);
  BN_bn2bin(challenge, buf + 32 - len);
  MD5_Init(&md);
  MD5_Update(&md, buf, 32);
  MD5_Update(&md, session_id, 16);
  MD5_Final(mdbuf, &md);

  /* We will no longer need these. */
  BN_clear_free(encrypted_challenge);
  BN_clear_free(challenge);
  BN_clear_free(aux);
  BN_CTX_free(ctx);
  
  /* Wait for a response. */
  packet_read_expect(&plen, SSH_CMSG_AUTH_RSA_RESPONSE);
  packet_integrity_check(plen, 16, SSH_CMSG_AUTH_RSA_RESPONSE);
  for (i = 0; i < 16; i++)
    response[i] = packet_get_char();

  /* Verify that the response is the original challenge. */
  if (memcmp(response, mdbuf, 16) != 0)
    {
      /* Wrong answer. */
      return 0;
    }

  /* Correct answer. */
  return 1;
}

/* Performs the RSA authentication dialog with the client.  This returns
   0 if the client could not be authenticated, and 1 if authentication was
   successful.  This may exit if there is a serious protocol violation. */

int
auth_rsa(struct passwd *pw, BIGNUM *client_n, int strict_modes)
{
  char line[8192];
  int authenticated;
  unsigned int bits;
  FILE *f;
  unsigned long linenum = 0;
  struct stat st;
  BIGNUM *e, *n;

  /* Temporarily use the user's uid. */
  temporarily_use_uid(pw->pw_uid);

  /* The authorized keys. */
  snprintf(line, sizeof line, "%.500s/%.100s", pw->pw_dir,
    SSH_USER_PERMITTED_KEYS);
  
  /* Fail quietly if file does not exist */
  if (stat(line, &st) < 0)
    {
      /* Restore the privileged uid. */
      restore_uid();
      return 0;
    }

  /* Open the file containing the authorized keys. */
  f = fopen(line, "r");
  if (!f)
    {
      /* Restore the privileged uid. */
      restore_uid();
      packet_send_debug("Could not open %.900s for reading.", line);
      packet_send_debug("If your home is on an NFS volume, it may need to be world-readable.");
      return 0;
    }

  if (strict_modes) {
    int fail=0;
    char buf[1024];
    /* Check open file in order to avoid open/stat races */
    if (fstat(fileno(f), &st) < 0 ||
        (st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
        (st.st_mode & 022) != 0) {
      snprintf(buf, sizeof buf, "RSA authentication refused for %.100s: "
               "bad ownership or modes for '%s'.", pw->pw_name, line);
      fail=1;
    }else{
      /* Check path to SSH_USER_PERMITTED_KEYS */
      int i;
      static const char *check[] = {
            "", SSH_USER_DIR, NULL
      };
      for (i=0; check[i]; i++) {
        snprintf(line, sizeof line, "%.500s/%.100s", pw->pw_dir, check[i]);
        if (stat(line, &st) < 0 ||
            (st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
            (st.st_mode & 022) != 0) {
          snprintf(buf, sizeof buf, "RSA authentication refused for %.100s: "
                   "bad ownership or modes for '%s'.", pw->pw_name, line);
          fail=1;
          break;
        }
      }
    }
    if (fail) {
      log(buf);
      packet_send_debug(buf);
      restore_uid();
      return 0;
    }
  } 

  /* Flag indicating whether authentication has succeeded. */
  authenticated = 0;
  
  /* Initialize mp-int variables. */
  e = BN_new();
  n = BN_new();

  /* Go though the accepted keys, looking for the current key.  If found,
     perform a challenge-response dialog to verify that the user really has
     the corresponding private key. */
  while (fgets(line, sizeof(line), f))
    {
      char *cp;
      char *options;

      linenum++;

      /* Skip leading whitespace. */
      for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
	;

      /* Skip empty and comment lines. */
      if (!*cp || *cp == '\n' || *cp == '#')
	continue;

      /* Check if there are options for this key, and if so, save their 
	 starting address and skip the option part for now.  If there are no 
	 options, set the starting address to NULL. */
      if (*cp < '0' || *cp > '9')
	{
	  int quoted = 0;
	  options = cp;
	  for (; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++)
	    {
	      if (*cp == '\\' && cp[1] == '"')
		cp++; /* Skip both */
	      else
		if (*cp == '"')
		  quoted = !quoted;
	    }
	}
      else
	options = NULL;
      
      /* Parse the key from the line. */
      if (!auth_rsa_read_key(&cp, &bits, e, n))
	{
	  debug("%.100s, line %lu: bad key syntax", 
		SSH_USER_PERMITTED_KEYS, linenum);
	  packet_send_debug("%.100s, line %lu: bad key syntax", 
			    SSH_USER_PERMITTED_KEYS, linenum);
	  continue;
	}
      /* cp now points to the comment part. */

      /* Check if the we have found the desired key (identified by its
	 modulus). */
      if (BN_cmp(n, client_n) != 0)
	continue; /* Wrong key. */

      /* We have found the desired key. */

      /* Perform the challenge-response dialog for this key. */
      if (!auth_rsa_challenge_dialog(bits, e, n))
	{
	  /* Wrong response. */
	  log("Wrong response to RSA authentication challenge.");
	  packet_send_debug("Wrong response to RSA authentication challenge.");
	  continue;
	}

      /* Correct response.  The client has been successfully authenticated.
	 Note that we have not yet processed the options; this will be reset
	 if the options cause the authentication to be rejected. */
      authenticated = 1;

      /* RSA part of authentication was accepted.  Now process the options. */
      if (options)
	{
	  while (*options && *options != ' ' && *options != '\t')
	    {
	      cp = "no-port-forwarding";
	      if (strncmp(options, cp, strlen(cp)) == 0)
		{
		  packet_send_debug("Port forwarding disabled.");
		  no_port_forwarding_flag = 1;
		  options += strlen(cp);
		  goto next_option;
		}
	      cp = "no-agent-forwarding";
	      if (strncmp(options, cp, strlen(cp)) == 0)
		{
		  packet_send_debug("Agent forwarding disabled.");
		  no_agent_forwarding_flag = 1;
		  options += strlen(cp);
		  goto next_option;
		}
	      cp = "no-X11-forwarding";
	      if (strncmp(options, cp, strlen(cp)) == 0)
		{
		  packet_send_debug("X11 forwarding disabled.");
		  no_x11_forwarding_flag = 1;
		  options += strlen(cp);
		  goto next_option;
		}
	      cp = "no-pty";
	      if (strncmp(options, cp, strlen(cp)) == 0)
		{
		  packet_send_debug("Pty allocation disabled.");
		  no_pty_flag = 1;
		  options += strlen(cp);
		  goto next_option;
		}
	      cp = "command=\"";
	      if (strncmp(options, cp, strlen(cp)) == 0)
		{
		  int i;
		  options += strlen(cp);
		  forced_command = xmalloc(strlen(options) + 1);
		  i = 0;
		  while (*options)
		    {
		      if (*options == '"')
			break;
		      if (*options == '\\' && options[1] == '"')
			{
			  options += 2;
			  forced_command[i++] = '"';
			  continue;
			}
		      forced_command[i++] = *options++;
		    }
		  if (!*options)
		    {
		      debug("%.100s, line %lu: missing end quote",
			    SSH_USER_PERMITTED_KEYS, linenum);
		      packet_send_debug("%.100s, line %lu: missing end quote",
					SSH_USER_PERMITTED_KEYS, linenum);
		      continue;
		    }
		  forced_command[i] = 0;
		  packet_send_debug("Forced command: %.900s", forced_command);
		  options++;
		  goto next_option;
		}
	      cp = "environment=\"";
	      if (strncmp(options, cp, strlen(cp)) == 0)
		{
		  int i;
		  char *s;
		  struct envstring *new_envstring;
		  options += strlen(cp);
		  s = xmalloc(strlen(options) + 1);
		  i = 0;
		  while (*options)
		    {
		      if (*options == '"')
			break;
		      if (*options == '\\' && options[1] == '"')
			{
			  options += 2;
			  s[i++] = '"';
			  continue;
			}
		      s[i++] = *options++;
		    }
		  if (!*options)
		    {
		      debug("%.100s, line %lu: missing end quote",
			    SSH_USER_PERMITTED_KEYS, linenum);
		      packet_send_debug("%.100s, line %lu: missing end quote",
					SSH_USER_PERMITTED_KEYS, linenum);
		      continue;
		    }
		  s[i] = 0;
		  packet_send_debug("Adding to environment: %.900s", s);
		  debug("Adding to environment: %.900s", s);
		  options++;
		  new_envstring = xmalloc(sizeof(struct envstring));
		  new_envstring->s = s;
		  new_envstring->next = custom_environment;
		  custom_environment = new_envstring;
		  goto next_option;
		}
	      cp = "from=\"";
	      if (strncmp(options, cp, strlen(cp)) == 0)
		{
		  char *patterns = xmalloc(strlen(options) + 1);
		  int i;
		  options += strlen(cp);
		  i = 0;
		  while (*options)
		    {
		      if (*options == '"')
			break;
		      if (*options == '\\' && options[1] == '"')
			{
			  options += 2;
			  patterns[i++] = '"';
			  continue;
			}
		      patterns[i++] = *options++;
		    }
		  if (!*options)
		    {
		      debug("%.100s, line %lu: missing end quote",
			    SSH_USER_PERMITTED_KEYS, linenum);
		      packet_send_debug("%.100s, line %lu: missing end quote",
					SSH_USER_PERMITTED_KEYS, linenum);
		      continue;
		    }
		  patterns[i] = 0;
		  options++;
		  if (!match_hostname(get_canonical_hostname(), patterns,
				     strlen(patterns)) &&
		      !match_hostname(get_remote_ipaddr(), patterns,
				      strlen(patterns)))
		    {
		      log("RSA authentication tried for %.100s with correct key but not from a permitted host (host=%.200s, ip=%.200s).",
			  pw->pw_name, get_canonical_hostname(),
			  get_remote_ipaddr());
		      packet_send_debug("Your host '%.200s' is not permitted to use this key for login.",
					get_canonical_hostname());
		      xfree(patterns);
		      authenticated = 0;
		      break;
		    }
		  xfree(patterns);
		  /* Host name matches. */
		  goto next_option;
		}
	    bad_option:
	      /* Unknown option. */
	      log("Bad options in %.100s file, line %lu: %.50s",
		  SSH_USER_PERMITTED_KEYS, linenum, options);
	      packet_send_debug("Bad options in %.100s file, line %lu: %.50s",
				SSH_USER_PERMITTED_KEYS, linenum, options);
	      authenticated = 0;
	      break;

	    next_option:
	      /* Skip the comma, and move to the next option (or break out
		 if there are no more). */
	      if (!*options)
		fatal("Bugs in auth-rsa.c option processing.");
	      if (*options == ' ' || *options == '\t')
		break; /* End of options. */
	      if (*options != ',')
		goto bad_option;
	      options++;
	      /* Process the next option. */
	      continue;
	    }
	}

      /* Break out of the loop if authentication was successful; otherwise
	 continue searching. */
      if (authenticated)
	break;
    }

  /* Restore the privileged uid. */
  restore_uid();

  /* Close the file. */
  fclose(f);
  
  /* Clear any mp-int variables. */
  BN_clear_free(n);
  BN_clear_free(e);

  if (authenticated)
    packet_send_debug("RSA authentication accepted.");

  /* Return authentication result. */
  return authenticated;
}