[BACK]Return to auth2-pubkeyfile.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / ssh

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: