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