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