pps_fetch: introduce a helper to handle timeouts
[dragonfly.git] / crypto / openssh / auth2-pubkeyfile.c
blob0cfacac353c086cff767daff6fa145f7ccc60ad6
1 /* $OpenBSD: auth2-pubkeyfile.c,v 1.3 2022/07/01 03:52:57 djm Exp $ */
2 /*
3 * Copyright (c) 2000 Markus Friedl. All rights reserved.
4 * Copyright (c) 2010 Damien Miller. All rights reserved.
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.
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.
27 #include "includes.h"
29 #include <sys/types.h>
30 #include <sys/stat.h>
32 #include <stdlib.h>
33 #include <errno.h>
34 #include <fcntl.h>
35 #include <pwd.h>
36 #include <stdio.h>
37 #include <stdarg.h>
38 #include <string.h>
39 #include <time.h>
40 #include <unistd.h>
42 #include "ssh.h"
43 #include "log.h"
44 #include "misc.h"
45 #include "compat.h"
46 #include "sshkey.h"
47 #include "digest.h"
48 #include "hostfile.h"
49 #include "auth.h"
50 #include "auth-options.h"
51 #include "authfile.h"
52 #include "match.h"
53 #include "ssherr.h"
55 int
56 auth_authorise_keyopts(struct passwd *pw, struct sshauthopt *opts,
57 int allow_cert_authority, const char *remote_ip, const char *remote_host,
58 const char *loc)
60 time_t now = time(NULL);
61 char buf[64];
64 * Check keys/principals file expiry time.
65 * NB. validity interval in certificate is handled elsewhere.
67 if (opts->valid_before && now > 0 &&
68 opts->valid_before < (uint64_t)now) {
69 format_absolute_time(opts->valid_before, buf, sizeof(buf));
70 debug("%s: entry expired at %s", loc, buf);
71 auth_debug_add("%s: entry expired at %s", loc, buf);
72 return -1;
74 /* Consistency checks */
75 if (opts->cert_principals != NULL && !opts->cert_authority) {
76 debug("%s: principals on non-CA key", loc);
77 auth_debug_add("%s: principals on non-CA key", loc);
78 /* deny access */
79 return -1;
81 /* cert-authority flag isn't valid in authorized_principals files */
82 if (!allow_cert_authority && opts->cert_authority) {
83 debug("%s: cert-authority flag invalid here", loc);
84 auth_debug_add("%s: cert-authority flag invalid here", loc);
85 /* deny access */
86 return -1;
89 /* Perform from= checks */
90 if (opts->required_from_host_keys != NULL) {
91 switch (match_host_and_ip(remote_host, remote_ip,
92 opts->required_from_host_keys )) {
93 case 1:
94 /* Host name matches. */
95 break;
96 case -1:
97 default:
98 debug("%s: invalid from criteria", loc);
99 auth_debug_add("%s: invalid from criteria", loc);
100 /* FALLTHROUGH */
101 case 0:
102 logit("%s: Authentication tried for %.100s with "
103 "correct key but not from a permitted "
104 "host (host=%.200s, ip=%.200s, required=%.200s).",
105 loc, pw->pw_name, remote_host, remote_ip,
106 opts->required_from_host_keys);
107 auth_debug_add("%s: Your host '%.200s' is not "
108 "permitted to use this key for login.",
109 loc, remote_host);
110 /* deny access */
111 return -1;
114 /* Check source-address restriction from certificate */
115 if (opts->required_from_host_cert != NULL) {
116 switch (addr_match_cidr_list(remote_ip,
117 opts->required_from_host_cert)) {
118 case 1:
119 /* accepted */
120 break;
121 case -1:
122 default:
123 /* invalid */
124 error("%s: Certificate source-address invalid", loc);
125 /* FALLTHROUGH */
126 case 0:
127 logit("%s: Authentication tried for %.100s with valid "
128 "certificate but not from a permitted source "
129 "address (%.200s).", loc, pw->pw_name, remote_ip);
130 auth_debug_add("%s: Your address '%.200s' is not "
131 "permitted to use this certificate for login.",
132 loc, remote_ip);
133 return -1;
138 * XXX this is spammy. We should report remotely only for keys
139 * that are successful in actual auth attempts, and not PK_OK
140 * tests.
142 auth_log_authopts(loc, opts, 1);
144 return 0;
147 static int
148 match_principals_option(const char *principal_list, struct sshkey_cert *cert)
150 char *result;
151 u_int i;
153 /* XXX percent_expand() sequences for authorized_principals? */
155 for (i = 0; i < cert->nprincipals; i++) {
156 if ((result = match_list(cert->principals[i],
157 principal_list, NULL)) != NULL) {
158 debug3("matched principal from key options \"%.100s\"",
159 result);
160 free(result);
161 return 1;
164 return 0;
168 * Process a single authorized_principals format line. Returns 0 and sets
169 * authoptsp is principal is authorised, -1 otherwise. "loc" is used as a
170 * log preamble for file/line information.
173 auth_check_principals_line(char *cp, const struct sshkey_cert *cert,
174 const char *loc, struct sshauthopt **authoptsp)
176 u_int i, found = 0;
177 char *ep, *line_opts;
178 const char *reason = NULL;
179 struct sshauthopt *opts = NULL;
181 if (authoptsp != NULL)
182 *authoptsp = NULL;
184 /* Trim trailing whitespace. */
185 ep = cp + strlen(cp) - 1;
186 while (ep > cp && (*ep == '\n' || *ep == ' ' || *ep == '\t'))
187 *ep-- = '\0';
190 * If the line has internal whitespace then assume it has
191 * key options.
193 line_opts = NULL;
194 if ((ep = strrchr(cp, ' ')) != NULL ||
195 (ep = strrchr(cp, '\t')) != NULL) {
196 for (; *ep == ' ' || *ep == '\t'; ep++)
198 line_opts = cp;
199 cp = ep;
201 if ((opts = sshauthopt_parse(line_opts, &reason)) == NULL) {
202 debug("%s: bad principals options: %s", loc, reason);
203 auth_debug_add("%s: bad principals options: %s", loc, reason);
204 return -1;
206 /* Check principals in cert against those on line */
207 for (i = 0; i < cert->nprincipals; i++) {
208 if (strcmp(cp, cert->principals[i]) != 0)
209 continue;
210 debug3("%s: matched principal \"%.100s\"",
211 loc, cert->principals[i]);
212 found = 1;
214 if (found && authoptsp != NULL) {
215 *authoptsp = opts;
216 opts = NULL;
218 sshauthopt_free(opts);
219 return found ? 0 : -1;
223 auth_process_principals(FILE *f, const char *file,
224 const struct sshkey_cert *cert, struct sshauthopt **authoptsp)
226 char loc[256], *line = NULL, *cp, *ep;
227 size_t linesize = 0;
228 u_long linenum = 0, nonblank = 0;
229 u_int found_principal = 0;
231 if (authoptsp != NULL)
232 *authoptsp = NULL;
234 while (getline(&line, &linesize, f) != -1) {
235 linenum++;
236 /* Always consume entire input */
237 if (found_principal)
238 continue;
240 /* Skip leading whitespace. */
241 for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
243 /* Skip blank and comment lines. */
244 if ((ep = strchr(cp, '#')) != NULL)
245 *ep = '\0';
246 if (!*cp || *cp == '\n')
247 continue;
249 nonblank++;
250 snprintf(loc, sizeof(loc), "%.200s:%lu", file, linenum);
251 if (auth_check_principals_line(cp, cert, loc, authoptsp) == 0)
252 found_principal = 1;
254 debug2_f("%s: processed %lu/%lu lines", file, nonblank, linenum);
255 free(line);
256 return found_principal;
260 * Check a single line of an authorized_keys-format file. Returns 0 if key
261 * matches, -1 otherwise. Will return key/cert options via *authoptsp
262 * on success. "loc" is used as file/line location in log messages.
265 auth_check_authkey_line(struct passwd *pw, struct sshkey *key,
266 char *cp, const char *remote_ip, const char *remote_host, const char *loc,
267 struct sshauthopt **authoptsp)
269 int want_keytype = sshkey_is_cert(key) ? KEY_UNSPEC : key->type;
270 struct sshkey *found = NULL;
271 struct sshauthopt *keyopts = NULL, *certopts = NULL, *finalopts = NULL;
272 char *key_options = NULL, *fp = NULL;
273 const char *reason = NULL;
274 int ret = -1;
276 if (authoptsp != NULL)
277 *authoptsp = NULL;
279 if ((found = sshkey_new(want_keytype)) == NULL) {
280 debug3_f("keytype %d failed", want_keytype);
281 goto out;
284 /* XXX djm: peek at key type in line and skip if unwanted */
286 if (sshkey_read(found, &cp) != 0) {
287 /* no key? check for options */
288 debug2("%s: check options: '%s'", loc, cp);
289 key_options = cp;
290 if (sshkey_advance_past_options(&cp) != 0) {
291 reason = "invalid key option string";
292 goto fail_reason;
294 skip_space(&cp);
295 if (sshkey_read(found, &cp) != 0) {
296 /* still no key? advance to next line*/
297 debug2("%s: advance: '%s'", loc, cp);
298 goto out;
301 /* Parse key options now; we need to know if this is a CA key */
302 if ((keyopts = sshauthopt_parse(key_options, &reason)) == NULL) {
303 debug("%s: bad key options: %s", loc, reason);
304 auth_debug_add("%s: bad key options: %s", loc, reason);
305 goto out;
307 /* Ignore keys that don't match or incorrectly marked as CAs */
308 if (sshkey_is_cert(key)) {
309 /* Certificate; check signature key against CA */
310 if (!sshkey_equal(found, key->cert->signature_key) ||
311 !keyopts->cert_authority)
312 goto out;
313 } else {
314 /* Plain key: check it against key found in file */
315 if (!sshkey_equal(found, key) || keyopts->cert_authority)
316 goto out;
319 /* We have a candidate key, perform authorisation checks */
320 if ((fp = sshkey_fingerprint(found,
321 SSH_FP_HASH_DEFAULT, SSH_FP_DEFAULT)) == NULL)
322 fatal_f("fingerprint failed");
324 debug("%s: matching %s found: %s %s", loc,
325 sshkey_is_cert(key) ? "CA" : "key", sshkey_type(found), fp);
327 if (auth_authorise_keyopts(pw, keyopts,
328 sshkey_is_cert(key), remote_ip, remote_host, loc) != 0) {
329 reason = "Refused by key options";
330 goto fail_reason;
332 /* That's all we need for plain keys. */
333 if (!sshkey_is_cert(key)) {
334 verbose("Accepted key %s %s found at %s",
335 sshkey_type(found), fp, loc);
336 finalopts = keyopts;
337 keyopts = NULL;
338 goto success;
342 * Additional authorisation for certificates.
345 /* Parse and check options present in certificate */
346 if ((certopts = sshauthopt_from_cert(key)) == NULL) {
347 reason = "Invalid certificate options";
348 goto fail_reason;
350 if (auth_authorise_keyopts(pw, certopts, 0,
351 remote_ip, remote_host, loc) != 0) {
352 reason = "Refused by certificate options";
353 goto fail_reason;
355 if ((finalopts = sshauthopt_merge(keyopts, certopts, &reason)) == NULL)
356 goto fail_reason;
359 * If the user has specified a list of principals as
360 * a key option, then prefer that list to matching
361 * their username in the certificate principals list.
363 if (keyopts->cert_principals != NULL &&
364 !match_principals_option(keyopts->cert_principals, key->cert)) {
365 reason = "Certificate does not contain an authorized principal";
366 goto fail_reason;
368 if (sshkey_cert_check_authority_now(key, 0, 0, 0,
369 keyopts->cert_principals == NULL ? pw->pw_name : NULL,
370 &reason) != 0)
371 goto fail_reason;
373 verbose("Accepted certificate ID \"%s\" (serial %llu) "
374 "signed by CA %s %s found at %s",
375 key->cert->key_id,
376 (unsigned long long)key->cert->serial,
377 sshkey_type(found), fp, loc);
379 success:
380 if (finalopts == NULL)
381 fatal_f("internal error: missing options");
382 if (authoptsp != NULL) {
383 *authoptsp = finalopts;
384 finalopts = NULL;
386 /* success */
387 ret = 0;
388 goto out;
390 fail_reason:
391 error("%s", reason);
392 auth_debug_add("%s", reason);
393 out:
394 free(fp);
395 sshauthopt_free(keyopts);
396 sshauthopt_free(certopts);
397 sshauthopt_free(finalopts);
398 sshkey_free(found);
399 return ret;
403 * Checks whether key is allowed in authorized_keys-format file,
404 * returns 1 if the key is allowed or 0 otherwise.
407 auth_check_authkeys_file(struct passwd *pw, FILE *f, char *file,
408 struct sshkey *key, const char *remote_ip,
409 const char *remote_host, struct sshauthopt **authoptsp)
411 char *cp, *line = NULL, loc[256];
412 size_t linesize = 0;
413 int found_key = 0;
414 u_long linenum = 0, nonblank = 0;
416 if (authoptsp != NULL)
417 *authoptsp = NULL;
419 while (getline(&line, &linesize, f) != -1) {
420 linenum++;
421 /* Always consume entire file */
422 if (found_key)
423 continue;
425 /* Skip leading whitespace, empty and comment lines. */
426 cp = line;
427 skip_space(&cp);
428 if (!*cp || *cp == '\n' || *cp == '#')
429 continue;
431 nonblank++;
432 snprintf(loc, sizeof(loc), "%.200s:%lu", file, linenum);
433 if (auth_check_authkey_line(pw, key, cp,
434 remote_ip, remote_host, loc, authoptsp) == 0)
435 found_key = 1;
437 free(line);
438 debug2_f("%s: processed %lu/%lu lines", file, nonblank, linenum);
439 return found_key;
442 static FILE *
443 auth_openfile(const char *file, struct passwd *pw, int strict_modes,
444 int log_missing, char *file_type)
446 char line[1024];
447 struct stat st;
448 int fd;
449 FILE *f;
451 if ((fd = open(file, O_RDONLY|O_NONBLOCK)) == -1) {
452 if (errno != ENOENT) {
453 logit("Could not open user '%s' %s '%s': %s",
454 pw->pw_name, file_type, file, strerror(errno));
455 } else if (log_missing) {
456 debug("Could not open user '%s' %s '%s': %s",
457 pw->pw_name, file_type, file, strerror(errno));
459 return NULL;
462 if (fstat(fd, &st) == -1) {
463 close(fd);
464 return NULL;
466 if (!S_ISREG(st.st_mode)) {
467 logit("User '%s' %s '%s' is not a regular file",
468 pw->pw_name, file_type, file);
469 close(fd);
470 return NULL;
472 unset_nonblock(fd);
473 if ((f = fdopen(fd, "r")) == NULL) {
474 close(fd);
475 return NULL;
477 if (strict_modes &&
478 safe_path_fd(fileno(f), file, pw, line, sizeof(line)) != 0) {
479 fclose(f);
480 logit("Authentication refused: %s", line);
481 auth_debug_add("Ignored %s: %s", file_type, line);
482 return NULL;
485 return f;
489 FILE *
490 auth_openkeyfile(const char *file, struct passwd *pw, int strict_modes)
492 return auth_openfile(file, pw, strict_modes, 1, "authorized keys");
495 FILE *
496 auth_openprincipals(const char *file, struct passwd *pw, int strict_modes)
498 return auth_openfile(file, pw, strict_modes, 0,
499 "authorized principals");