blob: f2295078b29c313e3632bfa9fb73372fa715b2de [file] [log] [blame]
Damien Millerd4a8b7e1999-10-27 13:42:43 +10001/*
2
3auth-rsa.c
4
5Author: Tatu Ylonen <ylo@cs.hut.fi>
6
7Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
8 All rights reserved
9
10Created: Mon Mar 27 01:46:52 1995 ylo
11
12RSA-based authentication. This code determines whether to admit a login
13based on RSA authentication. This file also contains functions to check
14validity of the host key.
15
16*/
17
18#include "includes.h"
Damien Miller23b78391999-11-19 08:25:48 +110019RCSID("$Id: auth-rsa.c,v 1.8 1999/11/18 21:25:48 damien Exp $");
Damien Millerd4a8b7e1999-10-27 13:42:43 +100020
21#include "rsa.h"
22#include "packet.h"
23#include "xmalloc.h"
24#include "ssh.h"
25#include "mpaux.h"
26#include "uidswap.h"
Damien Miller6d7b2cd1999-11-12 15:19:27 +110027#include "servconf.h"
Damien Millerd4a8b7e1999-10-27 13:42:43 +100028
Damien Miller7f6ea021999-10-28 13:25:17 +100029#ifdef HAVE_OPENSSL
Damien Millerd4a8b7e1999-10-27 13:42:43 +100030#include <openssl/rsa.h>
31#include <openssl/md5.h>
Damien Miller7f6ea021999-10-28 13:25:17 +100032#endif
33#ifdef HAVE_SSL
34#include <ssl/rsa.h>
35#include <ssl/md5.h>
36#endif
Damien Millerd4a8b7e1999-10-27 13:42:43 +100037
38/* Flags that may be set in authorized_keys options. */
39extern int no_port_forwarding_flag;
40extern int no_agent_forwarding_flag;
41extern int no_x11_forwarding_flag;
42extern int no_pty_flag;
43extern char *forced_command;
44extern struct envstring *custom_environment;
45
46/* Session identifier that is used to bind key exchange and authentication
47 responses to a particular session. */
48extern unsigned char session_id[16];
49
50/* The .ssh/authorized_keys file contains public keys, one per line, in the
51 following format:
52 options bits e n comment
53 where bits, e and n are decimal numbers,
54 and comment is any string of characters up to newline. The maximum
55 length of a line is 8000 characters. See the documentation for a
56 description of the options.
57*/
58
59/* Performs the RSA authentication challenge-response dialog with the client,
60 and returns true (non-zero) if the client gave the correct answer to
61 our challenge; returns zero if the client gives a wrong answer. */
62
63int
Damien Miller7e8e8201999-11-16 13:37:16 +110064auth_rsa_challenge_dialog(BIGNUM *e, BIGNUM *n)
Damien Millerd4a8b7e1999-10-27 13:42:43 +100065{
66 BIGNUM *challenge, *encrypted_challenge, *aux;
67 RSA *pk;
68 BN_CTX *ctx = BN_CTX_new();
69 unsigned char buf[32], mdbuf[16], response[16];
70 MD5_CTX md;
71 unsigned int i;
72 int plen, len;
73
74 encrypted_challenge = BN_new();
75 challenge = BN_new();
76 aux = BN_new();
77
78 /* Generate a random challenge. */
79 BN_rand(challenge, 256, 0, 0);
80 BN_mod(challenge, challenge, n, ctx);
81
82 /* Create the public key data structure. */
83 pk = RSA_new();
84 pk->e = BN_new();
85 BN_copy(pk->e, e);
86 pk->n = BN_new();
87 BN_copy(pk->n, n);
88
89 /* Encrypt the challenge with the public key. */
90 rsa_public_encrypt(encrypted_challenge, challenge, pk);
91 RSA_free(pk);
92
93 /* Send the encrypted challenge to the client. */
94 packet_start(SSH_SMSG_AUTH_RSA_CHALLENGE);
95 packet_put_bignum(encrypted_challenge);
96 packet_send();
97 packet_write_wait();
98
99 /* The response is MD5 of decrypted challenge plus session id. */
100 len = BN_num_bytes(challenge);
Damien Millerfd7c9111999-11-08 16:15:55 +1100101 if (len <= 0 || len > 32)
102 fatal("auth_rsa_challenge_dialog: bad challenge length %d", len);
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000103 memset(buf, 0, 32);
104 BN_bn2bin(challenge, buf + 32 - len);
105 MD5_Init(&md);
106 MD5_Update(&md, buf, 32);
107 MD5_Update(&md, session_id, 16);
108 MD5_Final(mdbuf, &md);
109
110 /* We will no longer need these. */
111 BN_clear_free(encrypted_challenge);
112 BN_clear_free(challenge);
113 BN_clear_free(aux);
114 BN_CTX_free(ctx);
115
116 /* Wait for a response. */
117 packet_read_expect(&plen, SSH_CMSG_AUTH_RSA_RESPONSE);
118 packet_integrity_check(plen, 16, SSH_CMSG_AUTH_RSA_RESPONSE);
119 for (i = 0; i < 16; i++)
120 response[i] = packet_get_char();
121
122 /* Verify that the response is the original challenge. */
123 if (memcmp(response, mdbuf, 16) != 0)
124 {
125 /* Wrong answer. */
126 return 0;
127 }
128
129 /* Correct answer. */
130 return 1;
131}
132
133/* Performs the RSA authentication dialog with the client. This returns
134 0 if the client could not be authenticated, and 1 if authentication was
135 successful. This may exit if there is a serious protocol violation. */
136
137int
Damien Miller6d7b2cd1999-11-12 15:19:27 +1100138auth_rsa(struct passwd *pw, BIGNUM *client_n)
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000139{
Damien Miller6d7b2cd1999-11-12 15:19:27 +1100140 extern ServerOptions options;
Damien Miller7e8e8201999-11-16 13:37:16 +1100141 char line[8192], file[1024];
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000142 int authenticated;
143 unsigned int bits;
144 FILE *f;
145 unsigned long linenum = 0;
146 struct stat st;
147 BIGNUM *e, *n;
148
149 /* Temporarily use the user's uid. */
150 temporarily_use_uid(pw->pw_uid);
151
152 /* The authorized keys. */
Damien Miller7e8e8201999-11-16 13:37:16 +1100153 snprintf(file, sizeof file, "%.500s/%.100s", pw->pw_dir,
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000154 SSH_USER_PERMITTED_KEYS);
155
156 /* Fail quietly if file does not exist */
Damien Miller7e8e8201999-11-16 13:37:16 +1100157 if (stat(file, &st) < 0)
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000158 {
159 /* Restore the privileged uid. */
160 restore_uid();
161 return 0;
162 }
163
164 /* Open the file containing the authorized keys. */
Damien Miller7e8e8201999-11-16 13:37:16 +1100165 f = fopen(file, "r");
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000166 if (!f)
167 {
168 /* Restore the privileged uid. */
169 restore_uid();
Damien Miller7e8e8201999-11-16 13:37:16 +1100170 packet_send_debug("Could not open %.900s for reading.", file);
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000171 packet_send_debug("If your home is on an NFS volume, it may need to be world-readable.");
172 return 0;
173 }
174
Damien Miller6d7b2cd1999-11-12 15:19:27 +1100175 if (options.strict_modes) {
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000176 int fail=0;
177 char buf[1024];
178 /* Check open file in order to avoid open/stat races */
179 if (fstat(fileno(f), &st) < 0 ||
180 (st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
181 (st.st_mode & 022) != 0) {
182 snprintf(buf, sizeof buf, "RSA authentication refused for %.100s: "
Damien Miller7e8e8201999-11-16 13:37:16 +1100183 "bad ownership or modes for '%s'.", pw->pw_name, file);
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000184 fail=1;
185 }else{
186 /* Check path to SSH_USER_PERMITTED_KEYS */
187 int i;
188 static const char *check[] = {
189 "", SSH_USER_DIR, NULL
190 };
191 for (i=0; check[i]; i++) {
192 snprintf(line, sizeof line, "%.500s/%.100s", pw->pw_dir, check[i]);
193 if (stat(line, &st) < 0 ||
194 (st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
195 (st.st_mode & 022) != 0) {
196 snprintf(buf, sizeof buf, "RSA authentication refused for %.100s: "
197 "bad ownership or modes for '%s'.", pw->pw_name, line);
198 fail=1;
199 break;
200 }
201 }
202 }
203 if (fail) {
204 log(buf);
205 packet_send_debug(buf);
206 restore_uid();
207 return 0;
208 }
209 }
210
211 /* Flag indicating whether authentication has succeeded. */
212 authenticated = 0;
213
214 /* Initialize mp-int variables. */
215 e = BN_new();
216 n = BN_new();
217
218 /* Go though the accepted keys, looking for the current key. If found,
219 perform a challenge-response dialog to verify that the user really has
220 the corresponding private key. */
221 while (fgets(line, sizeof(line), f))
222 {
223 char *cp;
224 char *options;
225
226 linenum++;
227
228 /* Skip leading whitespace. */
229 for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
230 ;
231
232 /* Skip empty and comment lines. */
233 if (!*cp || *cp == '\n' || *cp == '#')
234 continue;
235
236 /* Check if there are options for this key, and if so, save their
237 starting address and skip the option part for now. If there are no
238 options, set the starting address to NULL. */
239 if (*cp < '0' || *cp > '9')
240 {
241 int quoted = 0;
242 options = cp;
243 for (; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++)
244 {
245 if (*cp == '\\' && cp[1] == '"')
246 cp++; /* Skip both */
247 else
248 if (*cp == '"')
249 quoted = !quoted;
250 }
251 }
252 else
253 options = NULL;
254
255 /* Parse the key from the line. */
256 if (!auth_rsa_read_key(&cp, &bits, e, n))
257 {
258 debug("%.100s, line %lu: bad key syntax",
259 SSH_USER_PERMITTED_KEYS, linenum);
260 packet_send_debug("%.100s, line %lu: bad key syntax",
261 SSH_USER_PERMITTED_KEYS, linenum);
262 continue;
263 }
264 /* cp now points to the comment part. */
265
Damien Miller7e8e8201999-11-16 13:37:16 +1100266 /* check the real bits */
267 if (bits != BN_num_bits(n))
Damien Miller23b78391999-11-19 08:25:48 +1100268 error("Warning: error in %s, line %ld: keysize mismatch: "
Damien Miller7e8e8201999-11-16 13:37:16 +1100269 "actual size %d vs. announced %d.",
270 file, linenum, BN_num_bits(n), bits);
271
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000272 /* Check if the we have found the desired key (identified by its
273 modulus). */
274 if (BN_cmp(n, client_n) != 0)
275 continue; /* Wrong key. */
276
277 /* We have found the desired key. */
278
279 /* Perform the challenge-response dialog for this key. */
Damien Miller7e8e8201999-11-16 13:37:16 +1100280 if (!auth_rsa_challenge_dialog(e, n))
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000281 {
282 /* Wrong response. */
283 log("Wrong response to RSA authentication challenge.");
284 packet_send_debug("Wrong response to RSA authentication challenge.");
285 continue;
286 }
287
288 /* Correct response. The client has been successfully authenticated.
289 Note that we have not yet processed the options; this will be reset
290 if the options cause the authentication to be rejected. */
291 authenticated = 1;
292
293 /* RSA part of authentication was accepted. Now process the options. */
294 if (options)
295 {
296 while (*options && *options != ' ' && *options != '\t')
297 {
298 cp = "no-port-forwarding";
299 if (strncmp(options, cp, strlen(cp)) == 0)
300 {
301 packet_send_debug("Port forwarding disabled.");
302 no_port_forwarding_flag = 1;
303 options += strlen(cp);
304 goto next_option;
305 }
306 cp = "no-agent-forwarding";
307 if (strncmp(options, cp, strlen(cp)) == 0)
308 {
309 packet_send_debug("Agent forwarding disabled.");
310 no_agent_forwarding_flag = 1;
311 options += strlen(cp);
312 goto next_option;
313 }
314 cp = "no-X11-forwarding";
315 if (strncmp(options, cp, strlen(cp)) == 0)
316 {
317 packet_send_debug("X11 forwarding disabled.");
318 no_x11_forwarding_flag = 1;
319 options += strlen(cp);
320 goto next_option;
321 }
322 cp = "no-pty";
323 if (strncmp(options, cp, strlen(cp)) == 0)
324 {
325 packet_send_debug("Pty allocation disabled.");
326 no_pty_flag = 1;
327 options += strlen(cp);
328 goto next_option;
329 }
330 cp = "command=\"";
331 if (strncmp(options, cp, strlen(cp)) == 0)
332 {
333 int i;
334 options += strlen(cp);
335 forced_command = xmalloc(strlen(options) + 1);
336 i = 0;
337 while (*options)
338 {
339 if (*options == '"')
340 break;
341 if (*options == '\\' && options[1] == '"')
342 {
343 options += 2;
344 forced_command[i++] = '"';
345 continue;
346 }
347 forced_command[i++] = *options++;
348 }
349 if (!*options)
350 {
351 debug("%.100s, line %lu: missing end quote",
352 SSH_USER_PERMITTED_KEYS, linenum);
353 packet_send_debug("%.100s, line %lu: missing end quote",
354 SSH_USER_PERMITTED_KEYS, linenum);
355 continue;
356 }
357 forced_command[i] = 0;
358 packet_send_debug("Forced command: %.900s", forced_command);
359 options++;
360 goto next_option;
361 }
362 cp = "environment=\"";
363 if (strncmp(options, cp, strlen(cp)) == 0)
364 {
365 int i;
366 char *s;
367 struct envstring *new_envstring;
368 options += strlen(cp);
369 s = xmalloc(strlen(options) + 1);
370 i = 0;
371 while (*options)
372 {
373 if (*options == '"')
374 break;
375 if (*options == '\\' && options[1] == '"')
376 {
377 options += 2;
378 s[i++] = '"';
379 continue;
380 }
381 s[i++] = *options++;
382 }
383 if (!*options)
384 {
385 debug("%.100s, line %lu: missing end quote",
386 SSH_USER_PERMITTED_KEYS, linenum);
387 packet_send_debug("%.100s, line %lu: missing end quote",
388 SSH_USER_PERMITTED_KEYS, linenum);
389 continue;
390 }
391 s[i] = 0;
392 packet_send_debug("Adding to environment: %.900s", s);
393 debug("Adding to environment: %.900s", s);
394 options++;
395 new_envstring = xmalloc(sizeof(struct envstring));
396 new_envstring->s = s;
397 new_envstring->next = custom_environment;
398 custom_environment = new_envstring;
399 goto next_option;
400 }
401 cp = "from=\"";
402 if (strncmp(options, cp, strlen(cp)) == 0)
403 {
404 char *patterns = xmalloc(strlen(options) + 1);
405 int i;
406 options += strlen(cp);
407 i = 0;
408 while (*options)
409 {
410 if (*options == '"')
411 break;
412 if (*options == '\\' && options[1] == '"')
413 {
414 options += 2;
415 patterns[i++] = '"';
416 continue;
417 }
418 patterns[i++] = *options++;
419 }
420 if (!*options)
421 {
422 debug("%.100s, line %lu: missing end quote",
423 SSH_USER_PERMITTED_KEYS, linenum);
424 packet_send_debug("%.100s, line %lu: missing end quote",
425 SSH_USER_PERMITTED_KEYS, linenum);
426 continue;
427 }
428 patterns[i] = 0;
429 options++;
430 if (!match_hostname(get_canonical_hostname(), patterns,
431 strlen(patterns)) &&
432 !match_hostname(get_remote_ipaddr(), patterns,
433 strlen(patterns)))
434 {
435 log("RSA authentication tried for %.100s with correct key but not from a permitted host (host=%.200s, ip=%.200s).",
436 pw->pw_name, get_canonical_hostname(),
437 get_remote_ipaddr());
438 packet_send_debug("Your host '%.200s' is not permitted to use this key for login.",
439 get_canonical_hostname());
440 xfree(patterns);
441 authenticated = 0;
442 break;
443 }
444 xfree(patterns);
445 /* Host name matches. */
446 goto next_option;
447 }
448 bad_option:
449 /* Unknown option. */
450 log("Bad options in %.100s file, line %lu: %.50s",
451 SSH_USER_PERMITTED_KEYS, linenum, options);
452 packet_send_debug("Bad options in %.100s file, line %lu: %.50s",
453 SSH_USER_PERMITTED_KEYS, linenum, options);
454 authenticated = 0;
455 break;
456
457 next_option:
458 /* Skip the comma, and move to the next option (or break out
459 if there are no more). */
460 if (!*options)
461 fatal("Bugs in auth-rsa.c option processing.");
462 if (*options == ' ' || *options == '\t')
463 break; /* End of options. */
464 if (*options != ',')
465 goto bad_option;
466 options++;
467 /* Process the next option. */
468 continue;
469 }
470 }
471
472 /* Break out of the loop if authentication was successful; otherwise
473 continue searching. */
474 if (authenticated)
475 break;
476 }
477
478 /* Restore the privileged uid. */
479 restore_uid();
480
481 /* Close the file. */
482 fclose(f);
483
484 /* Clear any mp-int variables. */
485 BN_clear_free(n);
486 BN_clear_free(e);
487
488 if (authenticated)
489 packet_send_debug("RSA authentication accepted.");
490
491 /* Return authentication result. */
492 return authenticated;
493}