Annotation of src/usr.bin/ssh/auth2-pubkeyfile.c, Revision 1.4
1.4 ! dtucker 1: /* $OpenBSD: auth2-pubkeyfile.c,v 1.3 2022/07/01 03:52:57 djm Exp $ */
1.1 djm 2: /*
3: * Copyright (c) 2000 Markus Friedl. All rights reserved.
4: * Copyright (c) 2010 Damien Miller. All rights reserved.
5: *
6: * Redistribution and use in source and binary forms, with or without
7: * modification, are permitted provided that the following conditions
8: * are met:
9: * 1. Redistributions of source code must retain the above copyright
10: * notice, this list of conditions and the following disclaimer.
11: * 2. Redistributions in binary form must reproduce the above copyright
12: * notice, this list of conditions and the following disclaimer in the
13: * documentation and/or other materials provided with the distribution.
14: *
15: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18: * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20: * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24: * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25: */
26:
27:
28: #include <sys/types.h>
29: #include <sys/stat.h>
30:
31: #include <stdlib.h>
32: #include <errno.h>
33: #include <fcntl.h>
34: #include <pwd.h>
35: #include <stdio.h>
36: #include <stdarg.h>
37: #include <string.h>
38: #include <time.h>
39: #include <unistd.h>
40:
41: #include "ssh.h"
42: #include "log.h"
43: #include "misc.h"
44: #include "sshkey.h"
45: #include "digest.h"
46: #include "hostfile.h"
47: #include "auth.h"
48: #include "auth-options.h"
49: #include "authfile.h"
50: #include "match.h"
51: #include "ssherr.h"
52:
53: int
54: auth_authorise_keyopts(struct passwd *pw, struct sshauthopt *opts,
55: int allow_cert_authority, const char *remote_ip, const char *remote_host,
56: const char *loc)
57: {
58: time_t now = time(NULL);
59: char buf[64];
60:
61: /*
62: * Check keys/principals file expiry time.
63: * NB. validity interval in certificate is handled elsewhere.
64: */
65: if (opts->valid_before && now > 0 &&
66: opts->valid_before < (uint64_t)now) {
67: format_absolute_time(opts->valid_before, buf, sizeof(buf));
68: debug("%s: entry expired at %s", loc, buf);
69: auth_debug_add("%s: entry expired at %s", loc, buf);
70: return -1;
71: }
72: /* Consistency checks */
73: if (opts->cert_principals != NULL && !opts->cert_authority) {
74: debug("%s: principals on non-CA key", loc);
75: auth_debug_add("%s: principals on non-CA key", loc);
76: /* deny access */
77: return -1;
78: }
79: /* cert-authority flag isn't valid in authorized_principals files */
80: if (!allow_cert_authority && opts->cert_authority) {
81: debug("%s: cert-authority flag invalid here", loc);
82: auth_debug_add("%s: cert-authority flag invalid here", loc);
83: /* deny access */
84: return -1;
85: }
86:
87: /* Perform from= checks */
88: if (opts->required_from_host_keys != NULL) {
89: switch (match_host_and_ip(remote_host, remote_ip,
90: opts->required_from_host_keys )) {
91: case 1:
92: /* Host name matches. */
93: break;
94: case -1:
95: default:
96: debug("%s: invalid from criteria", loc);
97: auth_debug_add("%s: invalid from criteria", loc);
98: /* FALLTHROUGH */
99: case 0:
100: logit("%s: Authentication tried for %.100s with "
101: "correct key but not from a permitted "
102: "host (host=%.200s, ip=%.200s, required=%.200s).",
103: loc, pw->pw_name, remote_host, remote_ip,
104: opts->required_from_host_keys);
105: auth_debug_add("%s: Your host '%.200s' is not "
106: "permitted to use this key for login.",
107: loc, remote_host);
108: /* deny access */
109: return -1;
110: }
111: }
112: /* Check source-address restriction from certificate */
113: if (opts->required_from_host_cert != NULL) {
114: switch (addr_match_cidr_list(remote_ip,
115: opts->required_from_host_cert)) {
116: case 1:
117: /* accepted */
118: break;
119: case -1:
120: default:
121: /* invalid */
122: error("%s: Certificate source-address invalid", loc);
123: /* FALLTHROUGH */
124: case 0:
125: logit("%s: Authentication tried for %.100s with valid "
126: "certificate but not from a permitted source "
127: "address (%.200s).", loc, pw->pw_name, remote_ip);
128: auth_debug_add("%s: Your address '%.200s' is not "
129: "permitted to use this certificate for login.",
130: loc, remote_ip);
131: return -1;
132: }
133: }
134: /*
135: *
136: * XXX this is spammy. We should report remotely only for keys
137: * that are successful in actual auth attempts, and not PK_OK
138: * tests.
139: */
140: auth_log_authopts(loc, opts, 1);
141:
142: return 0;
143: }
144:
145: static int
146: match_principals_option(const char *principal_list, struct sshkey_cert *cert)
147: {
148: char *result;
149: u_int i;
150:
151: /* XXX percent_expand() sequences for authorized_principals? */
152:
153: for (i = 0; i < cert->nprincipals; i++) {
154: if ((result = match_list(cert->principals[i],
155: principal_list, NULL)) != NULL) {
156: debug3("matched principal from key options \"%.100s\"",
157: result);
158: free(result);
159: return 1;
160: }
161: }
162: return 0;
163: }
164:
165: /*
166: * Process a single authorized_principals format line. Returns 0 and sets
167: * authoptsp is principal is authorised, -1 otherwise. "loc" is used as a
168: * log preamble for file/line information.
169: */
170: int
171: auth_check_principals_line(char *cp, const struct sshkey_cert *cert,
172: const char *loc, struct sshauthopt **authoptsp)
173: {
174: u_int i, found = 0;
175: char *ep, *line_opts;
176: const char *reason = NULL;
177: struct sshauthopt *opts = NULL;
178:
179: if (authoptsp != NULL)
180: *authoptsp = NULL;
181:
182: /* Trim trailing whitespace. */
183: ep = cp + strlen(cp) - 1;
184: while (ep > cp && (*ep == '\n' || *ep == ' ' || *ep == '\t'))
185: *ep-- = '\0';
186:
187: /*
188: * If the line has internal whitespace then assume it has
189: * key options.
190: */
191: line_opts = NULL;
192: if ((ep = strrchr(cp, ' ')) != NULL ||
193: (ep = strrchr(cp, '\t')) != NULL) {
194: for (; *ep == ' ' || *ep == '\t'; ep++)
195: ;
196: line_opts = cp;
197: cp = ep;
198: }
199: if ((opts = sshauthopt_parse(line_opts, &reason)) == NULL) {
200: debug("%s: bad principals options: %s", loc, reason);
201: auth_debug_add("%s: bad principals options: %s", loc, reason);
202: return -1;
203: }
204: /* Check principals in cert against those on line */
205: for (i = 0; i < cert->nprincipals; i++) {
206: if (strcmp(cp, cert->principals[i]) != 0)
207: continue;
208: debug3("%s: matched principal \"%.100s\"",
209: loc, cert->principals[i]);
210: found = 1;
211: }
212: if (found && authoptsp != NULL) {
213: *authoptsp = opts;
214: opts = NULL;
215: }
216: sshauthopt_free(opts);
217: return found ? 0 : -1;
218: }
219:
220: int
221: auth_process_principals(FILE *f, const char *file,
222: const struct sshkey_cert *cert, struct sshauthopt **authoptsp)
223: {
224: char loc[256], *line = NULL, *cp, *ep;
225: size_t linesize = 0;
226: u_long linenum = 0, nonblank = 0;
227: u_int found_principal = 0;
228:
229: if (authoptsp != NULL)
230: *authoptsp = NULL;
231:
232: while (getline(&line, &linesize, f) != -1) {
233: linenum++;
234: /* Always consume entire input */
235: if (found_principal)
236: continue;
237:
238: /* Skip leading whitespace. */
239: for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
240: ;
241: /* Skip blank and comment lines. */
242: if ((ep = strchr(cp, '#')) != NULL)
243: *ep = '\0';
244: if (!*cp || *cp == '\n')
245: continue;
246:
247: nonblank++;
248: snprintf(loc, sizeof(loc), "%.200s:%lu", file, linenum);
249: if (auth_check_principals_line(cp, cert, loc, authoptsp) == 0)
250: found_principal = 1;
251: }
252: debug2_f("%s: processed %lu/%lu lines", file, nonblank, linenum);
253: free(line);
254: return found_principal;
255: }
256:
257: /*
258: * Check a single line of an authorized_keys-format file. Returns 0 if key
259: * matches, -1 otherwise. Will return key/cert options via *authoptsp
260: * on success. "loc" is used as file/line location in log messages.
261: */
262: int
263: auth_check_authkey_line(struct passwd *pw, struct sshkey *key,
264: char *cp, const char *remote_ip, const char *remote_host, const char *loc,
265: struct sshauthopt **authoptsp)
266: {
267: int want_keytype = sshkey_is_cert(key) ? KEY_UNSPEC : key->type;
268: struct sshkey *found = NULL;
269: struct sshauthopt *keyopts = NULL, *certopts = NULL, *finalopts = NULL;
270: char *key_options = NULL, *fp = NULL;
271: const char *reason = NULL;
272: int ret = -1;
273:
274: if (authoptsp != NULL)
275: *authoptsp = NULL;
276:
277: if ((found = sshkey_new(want_keytype)) == NULL) {
278: debug3_f("keytype %d failed", want_keytype);
279: goto out;
280: }
281:
282: /* XXX djm: peek at key type in line and skip if unwanted */
283:
284: if (sshkey_read(found, &cp) != 0) {
285: /* no key? check for options */
286: debug2("%s: check options: '%s'", loc, cp);
287: key_options = cp;
288: if (sshkey_advance_past_options(&cp) != 0) {
289: reason = "invalid key option string";
290: goto fail_reason;
291: }
292: skip_space(&cp);
293: if (sshkey_read(found, &cp) != 0) {
294: /* still no key? advance to next line*/
295: debug2("%s: advance: '%s'", loc, cp);
296: goto out;
297: }
298: }
299: /* Parse key options now; we need to know if this is a CA key */
300: if ((keyopts = sshauthopt_parse(key_options, &reason)) == NULL) {
301: debug("%s: bad key options: %s", loc, reason);
302: auth_debug_add("%s: bad key options: %s", loc, reason);
303: goto out;
304: }
305: /* Ignore keys that don't match or incorrectly marked as CAs */
306: if (sshkey_is_cert(key)) {
307: /* Certificate; check signature key against CA */
308: if (!sshkey_equal(found, key->cert->signature_key) ||
309: !keyopts->cert_authority)
310: goto out;
311: } else {
312: /* Plain key: check it against key found in file */
313: if (!sshkey_equal(found, key) || keyopts->cert_authority)
314: goto out;
315: }
316:
317: /* We have a candidate key, perform authorisation checks */
318: if ((fp = sshkey_fingerprint(found,
319: SSH_FP_HASH_DEFAULT, SSH_FP_DEFAULT)) == NULL)
320: fatal_f("fingerprint failed");
321:
322: debug("%s: matching %s found: %s %s", loc,
323: sshkey_is_cert(key) ? "CA" : "key", sshkey_type(found), fp);
324:
325: if (auth_authorise_keyopts(pw, keyopts,
326: sshkey_is_cert(key), remote_ip, remote_host, loc) != 0) {
327: reason = "Refused by key options";
328: goto fail_reason;
329: }
330: /* That's all we need for plain keys. */
331: if (!sshkey_is_cert(key)) {
332: verbose("Accepted key %s %s found at %s",
333: sshkey_type(found), fp, loc);
334: finalopts = keyopts;
335: keyopts = NULL;
336: goto success;
337: }
338:
339: /*
340: * Additional authorisation for certificates.
341: */
342:
343: /* Parse and check options present in certificate */
344: if ((certopts = sshauthopt_from_cert(key)) == NULL) {
345: reason = "Invalid certificate options";
346: goto fail_reason;
347: }
348: if (auth_authorise_keyopts(pw, certopts, 0,
349: remote_ip, remote_host, loc) != 0) {
350: reason = "Refused by certificate options";
351: goto fail_reason;
352: }
353: if ((finalopts = sshauthopt_merge(keyopts, certopts, &reason)) == NULL)
354: goto fail_reason;
355:
356: /*
357: * If the user has specified a list of principals as
358: * a key option, then prefer that list to matching
359: * their username in the certificate principals list.
360: */
361: if (keyopts->cert_principals != NULL &&
362: !match_principals_option(keyopts->cert_principals, key->cert)) {
363: reason = "Certificate does not contain an authorized principal";
364: goto fail_reason;
365: }
366: if (sshkey_cert_check_authority_now(key, 0, 0, 0,
367: keyopts->cert_principals == NULL ? pw->pw_name : NULL,
368: &reason) != 0)
369: goto fail_reason;
370:
371: verbose("Accepted certificate ID \"%s\" (serial %llu) "
372: "signed by CA %s %s found at %s",
373: key->cert->key_id,
374: (unsigned long long)key->cert->serial,
375: sshkey_type(found), fp, loc);
376:
377: success:
378: if (finalopts == NULL)
379: fatal_f("internal error: missing options");
380: if (authoptsp != NULL) {
381: *authoptsp = finalopts;
382: finalopts = NULL;
383: }
384: /* success */
385: ret = 0;
386: goto out;
387:
388: fail_reason:
389: error("%s", reason);
390: auth_debug_add("%s", reason);
391: out:
392: free(fp);
393: sshauthopt_free(keyopts);
394: sshauthopt_free(certopts);
395: sshauthopt_free(finalopts);
396: sshkey_free(found);
397: return ret;
398: }
399:
400: /*
401: * Checks whether key is allowed in authorized_keys-format file,
402: * returns 1 if the key is allowed or 0 otherwise.
403: */
404: int
405: auth_check_authkeys_file(struct passwd *pw, FILE *f, char *file,
406: struct sshkey *key, const char *remote_ip,
407: const char *remote_host, struct sshauthopt **authoptsp)
408: {
409: char *cp, *line = NULL, loc[256];
410: size_t linesize = 0;
411: int found_key = 0;
412: u_long linenum = 0, nonblank = 0;
413:
414: if (authoptsp != NULL)
415: *authoptsp = NULL;
416:
417: while (getline(&line, &linesize, f) != -1) {
418: linenum++;
419: /* Always consume entire file */
420: if (found_key)
421: continue;
422:
423: /* Skip leading whitespace, empty and comment lines. */
424: cp = line;
425: skip_space(&cp);
426: if (!*cp || *cp == '\n' || *cp == '#')
427: continue;
428:
429: nonblank++;
430: snprintf(loc, sizeof(loc), "%.200s:%lu", file, linenum);
431: if (auth_check_authkey_line(pw, key, cp,
432: remote_ip, remote_host, loc, authoptsp) == 0)
433: found_key = 1;
434: }
435: free(line);
436: debug2_f("%s: processed %lu/%lu lines", file, nonblank, linenum);
437: return found_key;
438: }
439:
1.2 djm 440: static FILE *
441: auth_openfile(const char *file, struct passwd *pw, int strict_modes,
442: int log_missing, char *file_type)
443: {
444: char line[1024];
445: struct stat st;
446: int fd;
447: FILE *f;
448:
449: if ((fd = open(file, O_RDONLY|O_NONBLOCK)) == -1) {
1.3 djm 450: if (errno != ENOENT) {
451: logit("Could not open user '%s' %s '%s': %s",
452: pw->pw_name, file_type, file, strerror(errno));
453: } else if (log_missing) {
454: debug("Could not open user '%s' %s '%s': %s",
455: pw->pw_name, file_type, file, strerror(errno));
456: }
1.2 djm 457: return NULL;
458: }
459:
460: if (fstat(fd, &st) == -1) {
461: close(fd);
462: return NULL;
463: }
464: if (!S_ISREG(st.st_mode)) {
1.3 djm 465: logit("User '%s' %s '%s' is not a regular file",
1.2 djm 466: pw->pw_name, file_type, file);
467: close(fd);
468: return NULL;
469: }
470: unset_nonblock(fd);
471: if ((f = fdopen(fd, "r")) == NULL) {
472: close(fd);
473: return NULL;
474: }
475: if (strict_modes &&
476: safe_path_fd(fileno(f), file, pw, line, sizeof(line)) != 0) {
477: fclose(f);
478: logit("Authentication refused: %s", line);
479: auth_debug_add("Ignored %s: %s", file_type, line);
480: return NULL;
481: }
482:
483: return f;
484: }
485:
486:
487: FILE *
488: auth_openkeyfile(const char *file, struct passwd *pw, int strict_modes)
489: {
490: return auth_openfile(file, pw, strict_modes, 1, "authorized keys");
491: }
492:
493: FILE *
494: auth_openprincipals(const char *file, struct passwd *pw, int strict_modes)
495: {
496: return auth_openfile(file, pw, strict_modes, 0,
497: "authorized principals");
498: }
1.1 djm 499: