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