1 /* $OpenBSD: auth2-pubkeyfile.c,v 1.4 2023/03/05 05:34:09 dtucker Exp $ */
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
auth_authorise_keyopts(struct passwd * pw,struct sshauthopt * opts,int allow_cert_authority,const char * remote_ip,const char * remote_host,const char * loc)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
match_principals_option(const char * principal_list,struct sshkey_cert * cert)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
auth_check_principals_line(char * cp,const struct sshkey_cert * cert,const char * loc,struct sshauthopt ** authoptsp)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
auth_process_principals(FILE * f,const char * file,const struct sshkey_cert * cert,struct sshauthopt ** authoptsp)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
auth_check_authkey_line(struct passwd * pw,struct sshkey * key,char * cp,const char * remote_ip,const char * remote_host,const char * loc,struct sshauthopt ** authoptsp)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
auth_check_authkeys_file(struct passwd * pw,FILE * f,char * file,struct sshkey * key,const char * remote_ip,const char * remote_host,struct sshauthopt ** authoptsp)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
440 static FILE *
auth_openfile(const char * file,struct passwd * pw,int strict_modes,int log_missing,char * file_type)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) {
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 }
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)) {
465 logit("User '%s' %s '%s' is not a regular file",
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 *
auth_openkeyfile(const char * file,struct passwd * pw,int strict_modes)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 *
auth_openprincipals(const char * file,struct passwd * pw,int strict_modes)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 }
499
500