blob: 88841482b32cc3ffe3895fe35a07e29d5885660e [file] [log] [blame]
Damien Millerd4a8b7e1999-10-27 13:42:43 +10001/*
Damien Miller95def091999-11-25 00:26:21 +11002 *
3 * auth-rsa.c
4 *
5 * Author: Tatu Ylonen <ylo@cs.hut.fi>
6 *
7 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
8 * All rights reserved
9 *
10 * Created: Mon Mar 27 01:46:52 1995 ylo
11 *
12 * RSA-based authentication. This code determines whether to admit a login
13 * based on RSA authentication. This file also contains functions to check
14 * validity of the host key.
15 *
16 */
Damien Millerd4a8b7e1999-10-27 13:42:43 +100017
18#include "includes.h"
Damien Miller95def091999-11-25 00:26:21 +110019RCSID("$Id: auth-rsa.c,v 1.9 1999/11/24 13:26:21 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
Damien Miller95def091999-11-25 00:26:21 +110053 where bits, e and n are decimal numbers,
Damien Millerd4a8b7e1999-10-27 13:42:43 +100054 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{
Damien Miller95def091999-11-25 00:26:21 +110066 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;
Damien Millerd4a8b7e1999-10-27 13:42:43 +100073
Damien Miller95def091999-11-25 00:26:21 +110074 encrypted_challenge = BN_new();
75 challenge = BN_new();
76 aux = BN_new();
Damien Millerd4a8b7e1999-10-27 13:42:43 +100077
Damien Miller95def091999-11-25 00:26:21 +110078 /* Generate a random challenge. */
79 BN_rand(challenge, 256, 0, 0);
80 BN_mod(challenge, challenge, n, ctx);
Damien Millerd4a8b7e1999-10-27 13:42:43 +100081
Damien Miller95def091999-11-25 00:26:21 +110082 /* 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);
Damien Millerd4a8b7e1999-10-27 13:42:43 +100088
Damien Miller95def091999-11-25 00:26:21 +110089 /* Encrypt the challenge with the public key. */
90 rsa_public_encrypt(encrypted_challenge, challenge, pk);
91 RSA_free(pk);
Damien Millerd4a8b7e1999-10-27 13:42:43 +100092
Damien Miller95def091999-11-25 00:26:21 +110093 /* 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();
Damien Millerd4a8b7e1999-10-27 13:42:43 +100098
Damien Miller95def091999-11-25 00:26:21 +110099 /* The response is MD5 of decrypted challenge plus session id. */
100 len = BN_num_bytes(challenge);
101 if (len <= 0 || len > 32)
102 fatal("auth_rsa_challenge_dialog: bad challenge length %d", len);
103 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);
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000109
Damien Miller95def091999-11-25 00:26:21 +1100110 /* 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);
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000115
Damien Miller95def091999-11-25 00:26:21 +1100116 /* 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 /* Wrong answer. */
125 return 0;
126 }
127 /* Correct answer. */
128 return 1;
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000129}
130
131/* Performs the RSA authentication dialog with the client. This returns
132 0 if the client could not be authenticated, and 1 if authentication was
133 successful. This may exit if there is a serious protocol violation. */
134
135int
Damien Miller6d7b2cd1999-11-12 15:19:27 +1100136auth_rsa(struct passwd *pw, BIGNUM *client_n)
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000137{
Damien Miller95def091999-11-25 00:26:21 +1100138 extern ServerOptions options;
139 char line[8192], file[1024];
140 int authenticated;
141 unsigned int bits;
142 FILE *f;
143 unsigned long linenum = 0;
144 struct stat st;
145 BIGNUM *e, *n;
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000146
Damien Miller95def091999-11-25 00:26:21 +1100147 /* Temporarily use the user's uid. */
148 temporarily_use_uid(pw->pw_uid);
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000149
Damien Miller95def091999-11-25 00:26:21 +1100150 /* The authorized keys. */
151 snprintf(file, sizeof file, "%.500s/%.100s", pw->pw_dir,
152 SSH_USER_PERMITTED_KEYS);
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000153
Damien Miller95def091999-11-25 00:26:21 +1100154 /* Fail quietly if file does not exist */
155 if (stat(file, &st) < 0) {
156 /* Restore the privileged uid. */
157 restore_uid();
158 return 0;
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000159 }
Damien Miller95def091999-11-25 00:26:21 +1100160 /* Open the file containing the authorized keys. */
161 f = fopen(file, "r");
162 if (!f) {
163 /* Restore the privileged uid. */
164 restore_uid();
165 packet_send_debug("Could not open %.900s for reading.", file);
166 packet_send_debug("If your home is on an NFS volume, it may need to be world-readable.");
167 return 0;
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000168 }
Damien Miller95def091999-11-25 00:26:21 +1100169 if (options.strict_modes) {
170 int fail = 0;
171 char buf[1024];
172 /* Check open file in order to avoid open/stat races */
173 if (fstat(fileno(f), &st) < 0 ||
174 (st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
175 (st.st_mode & 022) != 0) {
176 snprintf(buf, sizeof buf, "RSA authentication refused for %.100s: "
177 "bad ownership or modes for '%s'.", pw->pw_name, file);
178 fail = 1;
179 } else {
180 /* Check path to SSH_USER_PERMITTED_KEYS */
181 int i;
182 static const char *check[] = {
183 "", SSH_USER_DIR, NULL
184 };
185 for (i = 0; check[i]; i++) {
186 snprintf(line, sizeof line, "%.500s/%.100s", pw->pw_dir, check[i]);
187 if (stat(line, &st) < 0 ||
188 (st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
189 (st.st_mode & 022) != 0) {
190 snprintf(buf, sizeof buf, "RSA authentication refused for %.100s: "
191 "bad ownership or modes for '%s'.", pw->pw_name, line);
192 fail = 1;
193 break;
194 }
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000195 }
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000196 }
Damien Miller95def091999-11-25 00:26:21 +1100197 if (fail) {
198 log(buf);
199 packet_send_debug(buf);
200 restore_uid();
201 return 0;
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000202 }
Damien Miller95def091999-11-25 00:26:21 +1100203 }
204 /* Flag indicating whether authentication has succeeded. */
205 authenticated = 0;
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000206
Damien Miller95def091999-11-25 00:26:21 +1100207 /* Initialize mp-int variables. */
208 e = BN_new();
209 n = BN_new();
210
211 /* Go though the accepted keys, looking for the current key. If
212 found, perform a challenge-response dialog to verify that the
213 user really has the corresponding private key. */
214 while (fgets(line, sizeof(line), f)) {
215 char *cp;
216 char *options;
217
218 linenum++;
219
220 /* Skip leading whitespace. */
221 for (cp = line; *cp == ' ' || *cp == '\t'; cp++);
222
223 /* Skip empty and comment lines. */
224 if (!*cp || *cp == '\n' || *cp == '#')
225 continue;
226
227 /* Check if there are options for this key, and if so,
228 save their starting address and skip the option part
229 for now. If there are no options, set the starting
230 address to NULL. */
231 if (*cp < '0' || *cp > '9') {
232 int quoted = 0;
233 options = cp;
234 for (; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++) {
235 if (*cp == '\\' && cp[1] == '"')
236 cp++; /* Skip both */
237 else if (*cp == '"')
238 quoted = !quoted;
239 }
240 } else
241 options = NULL;
242
243 /* Parse the key from the line. */
244 if (!auth_rsa_read_key(&cp, &bits, e, n)) {
245 debug("%.100s, line %lu: bad key syntax",
246 SSH_USER_PERMITTED_KEYS, linenum);
247 packet_send_debug("%.100s, line %lu: bad key syntax",
248 SSH_USER_PERMITTED_KEYS, linenum);
249 continue;
250 }
251 /* cp now points to the comment part. */
252
253 /* check the real bits */
254 if (bits != BN_num_bits(n))
255 error("Warning: error in %s, line %ld: keysize mismatch: "
256 "actual size %d vs. announced %d.",
257 file, linenum, BN_num_bits(n), bits);
258
259 /* Check if the we have found the desired key (identified by its modulus). */
260 if (BN_cmp(n, client_n) != 0)
261 continue; /* Wrong key. */
262
263 /* We have found the desired key. */
264
265 /* Perform the challenge-response dialog for this key. */
266 if (!auth_rsa_challenge_dialog(e, n)) {
267 /* Wrong response. */
268 verbose("Wrong response to RSA authentication challenge.");
269 packet_send_debug("Wrong response to RSA authentication challenge.");
270 continue;
271 }
272 /* Correct response. The client has been successfully
273 authenticated. Note that we have not yet processed the
274 options; this will be reset if the options cause the
275 authentication to be rejected. */
276 authenticated = 1;
277
278 /* RSA part of authentication was accepted. Now process the options. */
279 if (options) {
280 while (*options && *options != ' ' && *options != '\t') {
281 cp = "no-port-forwarding";
282 if (strncmp(options, cp, strlen(cp)) == 0) {
283 packet_send_debug("Port forwarding disabled.");
284 no_port_forwarding_flag = 1;
285 options += strlen(cp);
286 goto next_option;
287 }
288 cp = "no-agent-forwarding";
289 if (strncmp(options, cp, strlen(cp)) == 0) {
290 packet_send_debug("Agent forwarding disabled.");
291 no_agent_forwarding_flag = 1;
292 options += strlen(cp);
293 goto next_option;
294 }
295 cp = "no-X11-forwarding";
296 if (strncmp(options, cp, strlen(cp)) == 0) {
297 packet_send_debug("X11 forwarding disabled.");
298 no_x11_forwarding_flag = 1;
299 options += strlen(cp);
300 goto next_option;
301 }
302 cp = "no-pty";
303 if (strncmp(options, cp, strlen(cp)) == 0) {
304 packet_send_debug("Pty allocation disabled.");
305 no_pty_flag = 1;
306 options += strlen(cp);
307 goto next_option;
308 }
309 cp = "command=\"";
310 if (strncmp(options, cp, strlen(cp)) == 0) {
311 int i;
312 options += strlen(cp);
313 forced_command = xmalloc(strlen(options) + 1);
314 i = 0;
315 while (*options) {
316 if (*options == '"')
317 break;
318 if (*options == '\\' && options[1] == '"') {
319 options += 2;
320 forced_command[i++] = '"';
321 continue;
322 }
323 forced_command[i++] = *options++;
324 }
325 if (!*options) {
326 debug("%.100s, line %lu: missing end quote",
327 SSH_USER_PERMITTED_KEYS, linenum);
328 packet_send_debug("%.100s, line %lu: missing end quote",
329 SSH_USER_PERMITTED_KEYS, linenum);
330 continue;
331 }
332 forced_command[i] = 0;
333 packet_send_debug("Forced command: %.900s", forced_command);
334 options++;
335 goto next_option;
336 }
337 cp = "environment=\"";
338 if (strncmp(options, cp, strlen(cp)) == 0) {
339 int i;
340 char *s;
341 struct envstring *new_envstring;
342 options += strlen(cp);
343 s = xmalloc(strlen(options) + 1);
344 i = 0;
345 while (*options) {
346 if (*options == '"')
347 break;
348 if (*options == '\\' && options[1] == '"') {
349 options += 2;
350 s[i++] = '"';
351 continue;
352 }
353 s[i++] = *options++;
354 }
355 if (!*options) {
356 debug("%.100s, line %lu: missing end quote",
357 SSH_USER_PERMITTED_KEYS, linenum);
358 packet_send_debug("%.100s, line %lu: missing end quote",
359 SSH_USER_PERMITTED_KEYS, linenum);
360 continue;
361 }
362 s[i] = 0;
363 packet_send_debug("Adding to environment: %.900s", s);
364 debug("Adding to environment: %.900s", s);
365 options++;
366 new_envstring = xmalloc(sizeof(struct envstring));
367 new_envstring->s = s;
368 new_envstring->next = custom_environment;
369 custom_environment = new_envstring;
370 goto next_option;
371 }
372 cp = "from=\"";
373 if (strncmp(options, cp, strlen(cp)) == 0) {
374 char *patterns = xmalloc(strlen(options) + 1);
375 int i;
376 options += strlen(cp);
377 i = 0;
378 while (*options) {
379 if (*options == '"')
380 break;
381 if (*options == '\\' && options[1] == '"') {
382 options += 2;
383 patterns[i++] = '"';
384 continue;
385 }
386 patterns[i++] = *options++;
387 }
388 if (!*options) {
389 debug("%.100s, line %lu: missing end quote",
390 SSH_USER_PERMITTED_KEYS, linenum);
391 packet_send_debug("%.100s, line %lu: missing end quote",
392 SSH_USER_PERMITTED_KEYS, linenum);
393 continue;
394 }
395 patterns[i] = 0;
396 options++;
397 if (!match_hostname(get_canonical_hostname(), patterns,
398 strlen(patterns)) &&
399 !match_hostname(get_remote_ipaddr(), patterns,
400 strlen(patterns))) {
401 log("RSA authentication tried for %.100s with correct key but not from a permitted host (host=%.200s, ip=%.200s).",
402 pw->pw_name, get_canonical_hostname(),
403 get_remote_ipaddr());
404 packet_send_debug("Your host '%.200s' is not permitted to use this key for login.",
405 get_canonical_hostname());
406 xfree(patterns);
407 authenticated = 0;
408 break;
409 }
410 xfree(patterns);
411 /* Host name matches. */
412 goto next_option;
413 }
414 bad_option:
415 /* Unknown option. */
416 log("Bad options in %.100s file, line %lu: %.50s",
417 SSH_USER_PERMITTED_KEYS, linenum, options);
418 packet_send_debug("Bad options in %.100s file, line %lu: %.50s",
419 SSH_USER_PERMITTED_KEYS, linenum, options);
420 authenticated = 0;
421 break;
422
423 next_option:
424 /* Skip the comma, and move to the next option
425 (or break out if there are no more). */
426 if (!*options)
427 fatal("Bugs in auth-rsa.c option processing.");
428 if (*options == ' ' || *options == '\t')
429 break; /* End of options. */
430 if (*options != ',')
431 goto bad_option;
432 options++;
433 /* Process the next option. */
434 continue;
435 }
436 }
437 /* Break out of the loop if authentication was successful;
438 otherwise continue searching. */
439 if (authenticated)
440 break;
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000441 }
442
Damien Miller95def091999-11-25 00:26:21 +1100443 /* Restore the privileged uid. */
444 restore_uid();
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000445
Damien Miller95def091999-11-25 00:26:21 +1100446 /* Close the file. */
447 fclose(f);
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000448
Damien Miller95def091999-11-25 00:26:21 +1100449 /* Clear any mp-int variables. */
450 BN_clear_free(n);
451 BN_clear_free(e);
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000452
Damien Miller95def091999-11-25 00:26:21 +1100453 if (authenticated)
454 packet_send_debug("RSA authentication accepted.");
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000455
Damien Miller95def091999-11-25 00:26:21 +1100456 /* Return authentication result. */
457 return authenticated;
Damien Millerd4a8b7e1999-10-27 13:42:43 +1000458}