freebsd-dev/crypto/openssh/sshsig.c
Ed Maste 87c1498d1a ssh: update to OpenSSH v9.0p1
Release notes are available at https://www.openssh.com/txt/release-9.0

Some highlights:

 * ssh(1), sshd(8): use the hybrid Streamlined NTRU Prime + x25519 key
   exchange method by default ("sntrup761x25519-sha512@openssh.com").
   The NTRU algorithm is believed to resist attacks enabled by future
   quantum computers and is paired with the X25519 ECDH key exchange
   (the previous default) as a backstop against any weaknesses in
   NTRU Prime that may be discovered in the future. The combination
   ensures that the hybrid exchange offers at least as good security
   as the status quo.

 * sftp-server(8): support the "copy-data" extension to allow server-
   side copying of files/data, following the design in
   draft-ietf-secsh-filexfer-extensions-00. bz2948

 * sftp(1): add a "cp" command to allow the sftp client to perform
   server-side file copies.

This commit excludes the scp(1) change to use the SFTP protocol by
default; that change will immediately follow.

MFC after:	1 month
Relnotes:	Yes
Sponsored by:	The FreeBSD Foundation
2022-04-15 10:41:08 -04:00

1148 lines
29 KiB
C

/* $OpenBSD: sshsig.c,v 1.29 2022/03/30 04:27:51 djm Exp $ */
/*
* Copyright (c) 2019 Google LLC
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "includes.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include "authfd.h"
#include "authfile.h"
#include "log.h"
#include "misc.h"
#include "sshbuf.h"
#include "sshsig.h"
#include "ssherr.h"
#include "sshkey.h"
#include "match.h"
#include "digest.h"
#define SIG_VERSION 0x01
#define MAGIC_PREAMBLE "SSHSIG"
#define MAGIC_PREAMBLE_LEN (sizeof(MAGIC_PREAMBLE) - 1)
#define BEGIN_SIGNATURE "-----BEGIN SSH SIGNATURE-----\n"
#define END_SIGNATURE "-----END SSH SIGNATURE-----"
#define RSA_SIGN_ALG "rsa-sha2-512" /* XXX maybe make configurable */
#define RSA_SIGN_ALLOWED "rsa-sha2-512,rsa-sha2-256"
#define HASHALG_DEFAULT "sha512" /* XXX maybe make configurable */
#define HASHALG_ALLOWED "sha256,sha512"
int
sshsig_armor(const struct sshbuf *blob, struct sshbuf **out)
{
struct sshbuf *buf = NULL;
int r = SSH_ERR_INTERNAL_ERROR;
*out = NULL;
if ((buf = sshbuf_new()) == NULL) {
error_f("sshbuf_new failed");
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if ((r = sshbuf_put(buf, BEGIN_SIGNATURE,
sizeof(BEGIN_SIGNATURE)-1)) != 0) {
error_fr(r, "sshbuf_putf");
goto out;
}
if ((r = sshbuf_dtob64(blob, buf, 1)) != 0) {
error_fr(r, "base64 encode signature");
goto out;
}
if ((r = sshbuf_put(buf, END_SIGNATURE,
sizeof(END_SIGNATURE)-1)) != 0 ||
(r = sshbuf_put_u8(buf, '\n')) != 0) {
error_fr(r, "sshbuf_put");
goto out;
}
/* success */
*out = buf;
buf = NULL; /* transferred */
r = 0;
out:
sshbuf_free(buf);
return r;
}
int
sshsig_dearmor(struct sshbuf *sig, struct sshbuf **out)
{
int r;
size_t eoffset = 0;
struct sshbuf *buf = NULL;
struct sshbuf *sbuf = NULL;
char *b64 = NULL;
if ((sbuf = sshbuf_fromb(sig)) == NULL) {
error_f("sshbuf_fromb failed");
return SSH_ERR_ALLOC_FAIL;
}
if ((r = sshbuf_cmp(sbuf, 0,
BEGIN_SIGNATURE, sizeof(BEGIN_SIGNATURE)-1)) != 0) {
error("Couldn't parse signature: missing header");
goto done;
}
if ((r = sshbuf_consume(sbuf, sizeof(BEGIN_SIGNATURE)-1)) != 0) {
error_fr(r, "consume");
goto done;
}
if ((r = sshbuf_find(sbuf, 0, "\n" END_SIGNATURE,
sizeof("\n" END_SIGNATURE)-1, &eoffset)) != 0) {
error("Couldn't parse signature: missing footer");
goto done;
}
if ((r = sshbuf_consume_end(sbuf, sshbuf_len(sbuf)-eoffset)) != 0) {
error_fr(r, "consume");
goto done;
}
if ((b64 = sshbuf_dup_string(sbuf)) == NULL) {
error_f("sshbuf_dup_string failed");
r = SSH_ERR_ALLOC_FAIL;
goto done;
}
if ((buf = sshbuf_new()) == NULL) {
error_f("sshbuf_new() failed");
r = SSH_ERR_ALLOC_FAIL;
goto done;
}
if ((r = sshbuf_b64tod(buf, b64)) != 0) {
error_fr(r, "decode base64");
goto done;
}
/* success */
*out = buf;
r = 0;
buf = NULL; /* transferred */
done:
sshbuf_free(buf);
sshbuf_free(sbuf);
free(b64);
return r;
}
static int
sshsig_wrap_sign(struct sshkey *key, const char *hashalg,
const char *sk_provider, const char *sk_pin, const struct sshbuf *h_message,
const char *sig_namespace, struct sshbuf **out,
sshsig_signer *signer, void *signer_ctx)
{
int r;
size_t slen = 0;
u_char *sig = NULL;
struct sshbuf *blob = NULL;
struct sshbuf *tosign = NULL;
const char *sign_alg = NULL;
if ((tosign = sshbuf_new()) == NULL ||
(blob = sshbuf_new()) == NULL) {
error_f("sshbuf_new failed");
r = SSH_ERR_ALLOC_FAIL;
goto done;
}
if ((r = sshbuf_put(tosign, MAGIC_PREAMBLE, MAGIC_PREAMBLE_LEN)) != 0 ||
(r = sshbuf_put_cstring(tosign, sig_namespace)) != 0 ||
(r = sshbuf_put_string(tosign, NULL, 0)) != 0 || /* reserved */
(r = sshbuf_put_cstring(tosign, hashalg)) != 0 ||
(r = sshbuf_put_stringb(tosign, h_message)) != 0) {
error_fr(r, "assemble message to sign");
goto done;
}
/* If using RSA keys then default to a good signature algorithm */
if (sshkey_type_plain(key->type) == KEY_RSA)
sign_alg = RSA_SIGN_ALG;
if (signer != NULL) {
if ((r = signer(key, &sig, &slen,
sshbuf_ptr(tosign), sshbuf_len(tosign),
sign_alg, sk_provider, sk_pin, 0, signer_ctx)) != 0) {
error_r(r, "Couldn't sign message (signer)");
goto done;
}
} else {
if ((r = sshkey_sign(key, &sig, &slen,
sshbuf_ptr(tosign), sshbuf_len(tosign),
sign_alg, sk_provider, sk_pin, 0)) != 0) {
error_r(r, "Couldn't sign message");
goto done;
}
}
if ((r = sshbuf_put(blob, MAGIC_PREAMBLE, MAGIC_PREAMBLE_LEN)) != 0 ||
(r = sshbuf_put_u32(blob, SIG_VERSION)) != 0 ||
(r = sshkey_puts(key, blob)) != 0 ||
(r = sshbuf_put_cstring(blob, sig_namespace)) != 0 ||
(r = sshbuf_put_string(blob, NULL, 0)) != 0 || /* reserved */
(r = sshbuf_put_cstring(blob, hashalg)) != 0 ||
(r = sshbuf_put_string(blob, sig, slen)) != 0) {
error_fr(r, "assemble signature object");
goto done;
}
if (out != NULL) {
*out = blob;
blob = NULL;
}
r = 0;
done:
free(sig);
sshbuf_free(blob);
sshbuf_free(tosign);
return r;
}
/* Check preamble and version. */
static int
sshsig_parse_preamble(struct sshbuf *buf)
{
int r = SSH_ERR_INTERNAL_ERROR;
uint32_t sversion;
if ((r = sshbuf_cmp(buf, 0, MAGIC_PREAMBLE, MAGIC_PREAMBLE_LEN)) != 0 ||
(r = sshbuf_consume(buf, (sizeof(MAGIC_PREAMBLE)-1))) != 0 ||
(r = sshbuf_get_u32(buf, &sversion)) != 0) {
error("Couldn't verify signature: invalid format");
return r;
}
if (sversion > SIG_VERSION) {
error("Signature version %lu is larger than supported "
"version %u", (unsigned long)sversion, SIG_VERSION);
return SSH_ERR_INVALID_FORMAT;
}
return 0;
}
static int
sshsig_check_hashalg(const char *hashalg)
{
if (hashalg == NULL ||
match_pattern_list(hashalg, HASHALG_ALLOWED, 0) == 1)
return 0;
error_f("unsupported hash algorithm \"%.100s\"", hashalg);
return SSH_ERR_SIGN_ALG_UNSUPPORTED;
}
static int
sshsig_peek_hashalg(struct sshbuf *signature, char **hashalgp)
{
struct sshbuf *buf = NULL;
char *hashalg = NULL;
int r = SSH_ERR_INTERNAL_ERROR;
if (hashalgp != NULL)
*hashalgp = NULL;
if ((buf = sshbuf_fromb(signature)) == NULL)
return SSH_ERR_ALLOC_FAIL;
if ((r = sshsig_parse_preamble(buf)) != 0)
goto done;
if ((r = sshbuf_get_string_direct(buf, NULL, NULL)) != 0 ||
(r = sshbuf_get_string_direct(buf, NULL, NULL)) != 0 ||
(r = sshbuf_get_string(buf, NULL, NULL)) != 0 ||
(r = sshbuf_get_cstring(buf, &hashalg, NULL)) != 0 ||
(r = sshbuf_get_string_direct(buf, NULL, NULL)) != 0) {
error_fr(r, "parse signature object");
goto done;
}
/* success */
r = 0;
*hashalgp = hashalg;
hashalg = NULL;
done:
free(hashalg);
sshbuf_free(buf);
return r;
}
static int
sshsig_wrap_verify(struct sshbuf *signature, const char *hashalg,
const struct sshbuf *h_message, const char *expect_namespace,
struct sshkey **sign_keyp, struct sshkey_sig_details **sig_details)
{
int r = SSH_ERR_INTERNAL_ERROR;
struct sshbuf *buf = NULL, *toverify = NULL;
struct sshkey *key = NULL;
const u_char *sig;
char *got_namespace = NULL, *sigtype = NULL, *sig_hashalg = NULL;
size_t siglen;
debug_f("verify message length %zu", sshbuf_len(h_message));
if (sig_details != NULL)
*sig_details = NULL;
if (sign_keyp != NULL)
*sign_keyp = NULL;
if ((toverify = sshbuf_new()) == NULL) {
error_f("sshbuf_new failed");
r = SSH_ERR_ALLOC_FAIL;
goto done;
}
if ((r = sshbuf_put(toverify, MAGIC_PREAMBLE,
MAGIC_PREAMBLE_LEN)) != 0 ||
(r = sshbuf_put_cstring(toverify, expect_namespace)) != 0 ||
(r = sshbuf_put_string(toverify, NULL, 0)) != 0 || /* reserved */
(r = sshbuf_put_cstring(toverify, hashalg)) != 0 ||
(r = sshbuf_put_stringb(toverify, h_message)) != 0) {
error_fr(r, "assemble message to verify");
goto done;
}
if ((r = sshsig_parse_preamble(signature)) != 0)
goto done;
if ((r = sshkey_froms(signature, &key)) != 0 ||
(r = sshbuf_get_cstring(signature, &got_namespace, NULL)) != 0 ||
(r = sshbuf_get_string(signature, NULL, NULL)) != 0 ||
(r = sshbuf_get_cstring(signature, &sig_hashalg, NULL)) != 0 ||
(r = sshbuf_get_string_direct(signature, &sig, &siglen)) != 0) {
error_fr(r, "parse signature object");
goto done;
}
if (sshbuf_len(signature) != 0) {
error("Signature contains trailing data");
r = SSH_ERR_INVALID_FORMAT;
goto done;
}
if (strcmp(expect_namespace, got_namespace) != 0) {
error("Couldn't verify signature: namespace does not match");
debug_f("expected namespace \"%s\" received \"%s\"",
expect_namespace, got_namespace);
r = SSH_ERR_SIGNATURE_INVALID;
goto done;
}
if (strcmp(hashalg, sig_hashalg) != 0) {
error("Couldn't verify signature: hash algorithm mismatch");
debug_f("expected algorithm \"%s\" received \"%s\"",
hashalg, sig_hashalg);
r = SSH_ERR_SIGNATURE_INVALID;
goto done;
}
/* Ensure that RSA keys use an acceptable signature algorithm */
if (sshkey_type_plain(key->type) == KEY_RSA) {
if ((r = sshkey_get_sigtype(sig, siglen, &sigtype)) != 0) {
error_r(r, "Couldn't verify signature: unable to get "
"signature type");
goto done;
}
if (match_pattern_list(sigtype, RSA_SIGN_ALLOWED, 0) != 1) {
error("Couldn't verify signature: unsupported RSA "
"signature algorithm %s", sigtype);
r = SSH_ERR_SIGN_ALG_UNSUPPORTED;
goto done;
}
}
if ((r = sshkey_verify(key, sig, siglen, sshbuf_ptr(toverify),
sshbuf_len(toverify), NULL, 0, sig_details)) != 0) {
error_r(r, "Signature verification failed");
goto done;
}
/* success */
r = 0;
if (sign_keyp != NULL) {
*sign_keyp = key;
key = NULL; /* transferred */
}
done:
free(got_namespace);
free(sigtype);
free(sig_hashalg);
sshbuf_free(buf);
sshbuf_free(toverify);
sshkey_free(key);
return r;
}
static int
hash_buffer(const struct sshbuf *m, const char *hashalg, struct sshbuf **bp)
{
char *hex, hash[SSH_DIGEST_MAX_LENGTH];
int alg, r = SSH_ERR_INTERNAL_ERROR;
struct sshbuf *b = NULL;
*bp = NULL;
memset(hash, 0, sizeof(hash));
if ((r = sshsig_check_hashalg(hashalg)) != 0)
return r;
if ((alg = ssh_digest_alg_by_name(hashalg)) == -1) {
error_f("can't look up hash algorithm %s", hashalg);
return SSH_ERR_INTERNAL_ERROR;
}
if ((r = ssh_digest_buffer(alg, m, hash, sizeof(hash))) != 0) {
error_fr(r, "ssh_digest_buffer");
return r;
}
if ((hex = tohex(hash, ssh_digest_bytes(alg))) != NULL) {
debug3_f("final hash: %s", hex);
freezero(hex, strlen(hex));
}
if ((b = sshbuf_new()) == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if ((r = sshbuf_put(b, hash, ssh_digest_bytes(alg))) != 0) {
error_fr(r, "sshbuf_put");
goto out;
}
*bp = b;
b = NULL; /* transferred */
/* success */
r = 0;
out:
sshbuf_free(b);
explicit_bzero(hash, sizeof(hash));
return r;
}
int
sshsig_signb(struct sshkey *key, const char *hashalg,
const char *sk_provider, const char *sk_pin,
const struct sshbuf *message, const char *sig_namespace,
struct sshbuf **out, sshsig_signer *signer, void *signer_ctx)
{
struct sshbuf *b = NULL;
int r = SSH_ERR_INTERNAL_ERROR;
if (hashalg == NULL)
hashalg = HASHALG_DEFAULT;
if (out != NULL)
*out = NULL;
if ((r = hash_buffer(message, hashalg, &b)) != 0) {
error_fr(r, "hash buffer");
goto out;
}
if ((r = sshsig_wrap_sign(key, hashalg, sk_provider, sk_pin, b,
sig_namespace, out, signer, signer_ctx)) != 0)
goto out;
/* success */
r = 0;
out:
sshbuf_free(b);
return r;
}
int
sshsig_verifyb(struct sshbuf *signature, const struct sshbuf *message,
const char *expect_namespace, struct sshkey **sign_keyp,
struct sshkey_sig_details **sig_details)
{
struct sshbuf *b = NULL;
int r = SSH_ERR_INTERNAL_ERROR;
char *hashalg = NULL;
if (sig_details != NULL)
*sig_details = NULL;
if (sign_keyp != NULL)
*sign_keyp = NULL;
if ((r = sshsig_peek_hashalg(signature, &hashalg)) != 0)
return r;
debug_f("signature made with hash \"%s\"", hashalg);
if ((r = hash_buffer(message, hashalg, &b)) != 0) {
error_fr(r, "hash buffer");
goto out;
}
if ((r = sshsig_wrap_verify(signature, hashalg, b, expect_namespace,
sign_keyp, sig_details)) != 0)
goto out;
/* success */
r = 0;
out:
sshbuf_free(b);
free(hashalg);
return r;
}
static int
hash_file(int fd, const char *hashalg, struct sshbuf **bp)
{
char *hex, rbuf[8192], hash[SSH_DIGEST_MAX_LENGTH];
ssize_t n, total = 0;
struct ssh_digest_ctx *ctx;
int alg, oerrno, r = SSH_ERR_INTERNAL_ERROR;
struct sshbuf *b = NULL;
*bp = NULL;
memset(hash, 0, sizeof(hash));
if ((r = sshsig_check_hashalg(hashalg)) != 0)
return r;
if ((alg = ssh_digest_alg_by_name(hashalg)) == -1) {
error_f("can't look up hash algorithm %s", hashalg);
return SSH_ERR_INTERNAL_ERROR;
}
if ((ctx = ssh_digest_start(alg)) == NULL) {
error_f("ssh_digest_start failed");
return SSH_ERR_INTERNAL_ERROR;
}
for (;;) {
if ((n = read(fd, rbuf, sizeof(rbuf))) == -1) {
if (errno == EINTR || errno == EAGAIN)
continue;
oerrno = errno;
error_f("read: %s", strerror(errno));
ssh_digest_free(ctx);
errno = oerrno;
r = SSH_ERR_SYSTEM_ERROR;
goto out;
} else if (n == 0) {
debug2_f("hashed %zu bytes", total);
break; /* EOF */
}
total += (size_t)n;
if ((r = ssh_digest_update(ctx, rbuf, (size_t)n)) != 0) {
error_fr(r, "ssh_digest_update");
goto out;
}
}
if ((r = ssh_digest_final(ctx, hash, sizeof(hash))) != 0) {
error_fr(r, "ssh_digest_final");
goto out;
}
if ((hex = tohex(hash, ssh_digest_bytes(alg))) != NULL) {
debug3_f("final hash: %s", hex);
freezero(hex, strlen(hex));
}
if ((b = sshbuf_new()) == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if ((r = sshbuf_put(b, hash, ssh_digest_bytes(alg))) != 0) {
error_fr(r, "sshbuf_put");
goto out;
}
*bp = b;
b = NULL; /* transferred */
/* success */
r = 0;
out:
sshbuf_free(b);
ssh_digest_free(ctx);
explicit_bzero(hash, sizeof(hash));
return r;
}
int
sshsig_sign_fd(struct sshkey *key, const char *hashalg,
const char *sk_provider, const char *sk_pin,
int fd, const char *sig_namespace, struct sshbuf **out,
sshsig_signer *signer, void *signer_ctx)
{
struct sshbuf *b = NULL;
int r = SSH_ERR_INTERNAL_ERROR;
if (hashalg == NULL)
hashalg = HASHALG_DEFAULT;
if (out != NULL)
*out = NULL;
if ((r = hash_file(fd, hashalg, &b)) != 0) {
error_fr(r, "hash_file");
return r;
}
if ((r = sshsig_wrap_sign(key, hashalg, sk_provider, sk_pin, b,
sig_namespace, out, signer, signer_ctx)) != 0)
goto out;
/* success */
r = 0;
out:
sshbuf_free(b);
return r;
}
int
sshsig_verify_fd(struct sshbuf *signature, int fd,
const char *expect_namespace, struct sshkey **sign_keyp,
struct sshkey_sig_details **sig_details)
{
struct sshbuf *b = NULL;
int r = SSH_ERR_INTERNAL_ERROR;
char *hashalg = NULL;
if (sig_details != NULL)
*sig_details = NULL;
if (sign_keyp != NULL)
*sign_keyp = NULL;
if ((r = sshsig_peek_hashalg(signature, &hashalg)) != 0)
return r;
debug_f("signature made with hash \"%s\"", hashalg);
if ((r = hash_file(fd, hashalg, &b)) != 0) {
error_fr(r, "hash_file");
goto out;
}
if ((r = sshsig_wrap_verify(signature, hashalg, b, expect_namespace,
sign_keyp, sig_details)) != 0)
goto out;
/* success */
r = 0;
out:
sshbuf_free(b);
free(hashalg);
return r;
}
struct sshsigopt {
int ca;
char *namespaces;
uint64_t valid_after, valid_before;
};
struct sshsigopt *
sshsigopt_parse(const char *opts, const char *path, u_long linenum,
const char **errstrp)
{
struct sshsigopt *ret;
int r;
char *opt;
const char *errstr = NULL;
if ((ret = calloc(1, sizeof(*ret))) == NULL)
return NULL;
if (opts == NULL || *opts == '\0')
return ret; /* Empty options yields empty options :) */
while (*opts && *opts != ' ' && *opts != '\t') {
/* flag options */
if ((r = opt_flag("cert-authority", 0, &opts)) != -1) {
ret->ca = 1;
} else if (opt_match(&opts, "namespaces")) {
if (ret->namespaces != NULL) {
errstr = "multiple \"namespaces\" clauses";
goto fail;
}
ret->namespaces = opt_dequote(&opts, &errstr);
if (ret->namespaces == NULL)
goto fail;
} else if (opt_match(&opts, "valid-after")) {
if (ret->valid_after != 0) {
errstr = "multiple \"valid-after\" clauses";
goto fail;
}
if ((opt = opt_dequote(&opts, &errstr)) == NULL)
goto fail;
if (parse_absolute_time(opt, &ret->valid_after) != 0 ||
ret->valid_after == 0) {
free(opt);
errstr = "invalid \"valid-after\" time";
goto fail;
}
free(opt);
} else if (opt_match(&opts, "valid-before")) {
if (ret->valid_before != 0) {
errstr = "multiple \"valid-before\" clauses";
goto fail;
}
if ((opt = opt_dequote(&opts, &errstr)) == NULL)
goto fail;
if (parse_absolute_time(opt, &ret->valid_before) != 0 ||
ret->valid_before == 0) {
free(opt);
errstr = "invalid \"valid-before\" time";
goto fail;
}
free(opt);
}
/*
* Skip the comma, and move to the next option
* (or break out if there are no more).
*/
if (*opts == '\0' || *opts == ' ' || *opts == '\t')
break; /* End of options. */
/* Anything other than a comma is an unknown option */
if (*opts != ',') {
errstr = "unknown key option";
goto fail;
}
opts++;
if (*opts == '\0') {
errstr = "unexpected end-of-options";
goto fail;
}
}
/* final consistency check */
if (ret->valid_after != 0 && ret->valid_before != 0 &&
ret->valid_before <= ret->valid_after) {
errstr = "\"valid-before\" time is before \"valid-after\"";
goto fail;
}
/* success */
return ret;
fail:
if (errstrp != NULL)
*errstrp = errstr;
sshsigopt_free(ret);
return NULL;
}
void
sshsigopt_free(struct sshsigopt *opts)
{
if (opts == NULL)
return;
free(opts->namespaces);
free(opts);
}
static int
parse_principals_key_and_options(const char *path, u_long linenum, char *line,
const char *required_principal, char **principalsp, struct sshkey **keyp,
struct sshsigopt **sigoptsp)
{
char *opts = NULL, *tmp, *cp, *principals = NULL;
const char *reason = NULL;
struct sshsigopt *sigopts = NULL;
struct sshkey *key = NULL;
int r = SSH_ERR_INTERNAL_ERROR;
if (principalsp != NULL)
*principalsp = NULL;
if (sigoptsp != NULL)
*sigoptsp = NULL;
if (keyp != NULL)
*keyp = NULL;
cp = line;
cp = cp + strspn(cp, " \t"); /* skip leading whitespace */
if (*cp == '#' || *cp == '\0')
return SSH_ERR_KEY_NOT_FOUND; /* blank or all-comment line */
/* format: identity[,identity...] [option[,option...]] key */
if ((tmp = strdelimw(&cp)) == NULL || cp == NULL) {
error("%s:%lu: invalid line", path, linenum);
r = SSH_ERR_INVALID_FORMAT;
goto out;
}
if ((principals = strdup(tmp)) == NULL) {
error_f("strdup failed");
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
/*
* Bail out early if we're looking for a particular principal and this
* line does not list it.
*/
if (required_principal != NULL) {
if (match_pattern_list(required_principal,
principals, 0) != 1) {
/* principal didn't match */
r = SSH_ERR_KEY_NOT_FOUND;
goto out;
}
debug_f("%s:%lu: matched principal \"%s\"",
path, linenum, required_principal);
}
if ((key = sshkey_new(KEY_UNSPEC)) == NULL) {
error_f("sshkey_new failed");
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if (sshkey_read(key, &cp) != 0) {
/* no key? Check for options */
opts = cp;
if (sshkey_advance_past_options(&cp) != 0) {
error("%s:%lu: invalid options", path, linenum);
r = SSH_ERR_INVALID_FORMAT;
goto out;
}
if (cp == NULL || *cp == '\0') {
error("%s:%lu: missing key", path, linenum);
r = SSH_ERR_INVALID_FORMAT;
goto out;
}
*cp++ = '\0';
skip_space(&cp);
if (sshkey_read(key, &cp) != 0) {
error("%s:%lu: invalid key", path, linenum);
r = SSH_ERR_INVALID_FORMAT;
goto out;
}
}
debug3("%s:%lu: options %s", path, linenum, opts == NULL ? "" : opts);
if ((sigopts = sshsigopt_parse(opts, path, linenum, &reason)) == NULL) {
error("%s:%lu: bad options: %s", path, linenum, reason);
r = SSH_ERR_INVALID_FORMAT;
goto out;
}
/* success */
if (principalsp != NULL) {
*principalsp = principals;
principals = NULL; /* transferred */
}
if (sigoptsp != NULL) {
*sigoptsp = sigopts;
sigopts = NULL; /* transferred */
}
if (keyp != NULL) {
*keyp = key;
key = NULL; /* transferred */
}
r = 0;
out:
free(principals);
sshsigopt_free(sigopts);
sshkey_free(key);
return r;
}
static int
cert_filter_principals(const char *path, u_long linenum,
char **principalsp, const struct sshkey *cert, uint64_t verify_time)
{
char *cp, *oprincipals, *principals;
const char *reason;
struct sshbuf *nprincipals;
int r = SSH_ERR_INTERNAL_ERROR, success = 0;
u_int i;
oprincipals = principals = *principalsp;
*principalsp = NULL;
if ((nprincipals = sshbuf_new()) == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
while ((cp = strsep(&principals, ",")) != NULL && *cp != '\0') {
/* Check certificate validity */
if ((r = sshkey_cert_check_authority(cert, 0, 1, 0,
verify_time, NULL, &reason)) != 0) {
debug("%s:%lu: principal \"%s\" not authorized: %s",
path, linenum, cp, reason);
continue;
}
/* Return all matching principal names from the cert */
for (i = 0; i < cert->cert->nprincipals; i++) {
if (match_pattern(cert->cert->principals[i], cp)) {
if ((r = sshbuf_putf(nprincipals, "%s%s",
sshbuf_len(nprincipals) != 0 ? "," : "",
cert->cert->principals[i])) != 0) {
error_f("buffer error");
goto out;
}
}
}
}
if (sshbuf_len(nprincipals) == 0) {
error("%s:%lu: no valid principals found", path, linenum);
r = SSH_ERR_KEY_CERT_INVALID;
goto out;
}
if ((principals = sshbuf_dup_string(nprincipals)) == NULL) {
error_f("buffer error");
goto out;
}
/* success */
success = 1;
*principalsp = principals;
out:
sshbuf_free(nprincipals);
free(oprincipals);
return success ? 0 : r;
}
static int
check_allowed_keys_line(const char *path, u_long linenum, char *line,
const struct sshkey *sign_key, const char *principal,
const char *sig_namespace, uint64_t verify_time, char **principalsp)
{
struct sshkey *found_key = NULL;
char *principals = NULL;
int r, success = 0;
const char *reason = NULL;
struct sshsigopt *sigopts = NULL;
char tvalid[64], tverify[64];
if (principalsp != NULL)
*principalsp = NULL;
/* Parse the line */
if ((r = parse_principals_key_and_options(path, linenum, line,
principal, &principals, &found_key, &sigopts)) != 0) {
/* error already logged */
goto done;
}
if (!sigopts->ca && sshkey_equal(found_key, sign_key)) {
/* Exact match of key */
debug("%s:%lu: matched key", path, linenum);
} else if (sigopts->ca && sshkey_is_cert(sign_key) &&
sshkey_equal_public(sign_key->cert->signature_key, found_key)) {
if (principal) {
/* Match certificate CA key with specified principal */
if ((r = sshkey_cert_check_authority(sign_key, 0, 1, 0,
verify_time, principal, &reason)) != 0) {
error("%s:%lu: certificate not authorized: %s",
path, linenum, reason);
goto done;
}
debug("%s:%lu: matched certificate CA key",
path, linenum);
} else {
/* No principal specified - find all matching ones */
if ((r = cert_filter_principals(path, linenum,
&principals, sign_key, verify_time)) != 0) {
/* error already displayed */
debug_r(r, "%s:%lu: cert_filter_principals",
path, linenum);
goto done;
}
debug("%s:%lu: matched certificate CA key",
path, linenum);
}
} else {
/* Didn't match key */
goto done;
}
/* Check whether options preclude the use of this key */
if (sigopts->namespaces != NULL && sig_namespace != NULL &&
match_pattern_list(sig_namespace, sigopts->namespaces, 0) != 1) {
error("%s:%lu: key is not permitted for use in signature "
"namespace \"%s\"", path, linenum, sig_namespace);
goto done;
}
/* check key time validity */
format_absolute_time((uint64_t)verify_time, tverify, sizeof(tverify));
if (sigopts->valid_after != 0 &&
(uint64_t)verify_time < sigopts->valid_after) {
format_absolute_time(sigopts->valid_after,
tvalid, sizeof(tvalid));
error("%s:%lu: key is not yet valid: "
"verify time %s < valid-after %s", path, linenum,
tverify, tvalid);
goto done;
}
if (sigopts->valid_before != 0 &&
(uint64_t)verify_time > sigopts->valid_before) {
format_absolute_time(sigopts->valid_before,
tvalid, sizeof(tvalid));
error("%s:%lu: key has expired: "
"verify time %s > valid-before %s", path, linenum,
tverify, tvalid);
goto done;
}
success = 1;
done:
if (success && principalsp != NULL) {
*principalsp = principals;
principals = NULL; /* transferred */
}
free(principals);
sshkey_free(found_key);
sshsigopt_free(sigopts);
return success ? 0 : SSH_ERR_KEY_NOT_FOUND;
}
int
sshsig_check_allowed_keys(const char *path, const struct sshkey *sign_key,
const char *principal, const char *sig_namespace, uint64_t verify_time)
{
FILE *f = NULL;
char *line = NULL;
size_t linesize = 0;
u_long linenum = 0;
int r = SSH_ERR_INTERNAL_ERROR, oerrno;
/* Check key and principal against file */
if ((f = fopen(path, "r")) == NULL) {
oerrno = errno;
error("Unable to open allowed keys file \"%s\": %s",
path, strerror(errno));
errno = oerrno;
return SSH_ERR_SYSTEM_ERROR;
}
while (getline(&line, &linesize, f) != -1) {
linenum++;
r = check_allowed_keys_line(path, linenum, line, sign_key,
principal, sig_namespace, verify_time, NULL);
free(line);
line = NULL;
linesize = 0;
if (r == SSH_ERR_KEY_NOT_FOUND)
continue;
else if (r == 0) {
/* success */
fclose(f);
return 0;
} else
break;
}
/* Either we hit an error parsing or we simply didn't find the key */
fclose(f);
free(line);
return r == 0 ? SSH_ERR_KEY_NOT_FOUND : r;
}
int
sshsig_find_principals(const char *path, const struct sshkey *sign_key,
uint64_t verify_time, char **principals)
{
FILE *f = NULL;
char *line = NULL;
size_t linesize = 0;
u_long linenum = 0;
int r = SSH_ERR_INTERNAL_ERROR, oerrno;
if ((f = fopen(path, "r")) == NULL) {
oerrno = errno;
error("Unable to open allowed keys file \"%s\": %s",
path, strerror(errno));
errno = oerrno;
return SSH_ERR_SYSTEM_ERROR;
}
r = SSH_ERR_KEY_NOT_FOUND;
while (getline(&line, &linesize, f) != -1) {
linenum++;
r = check_allowed_keys_line(path, linenum, line,
sign_key, NULL, NULL, verify_time, principals);
free(line);
line = NULL;
linesize = 0;
if (r == SSH_ERR_KEY_NOT_FOUND)
continue;
else if (r == 0) {
/* success */
fclose(f);
return 0;
} else
break;
}
free(line);
/* Either we hit an error parsing or we simply didn't find the key */
if (ferror(f) != 0) {
oerrno = errno;
fclose(f);
error("Unable to read allowed keys file \"%s\": %s",
path, strerror(errno));
errno = oerrno;
return SSH_ERR_SYSTEM_ERROR;
}
fclose(f);
return r == 0 ? SSH_ERR_KEY_NOT_FOUND : r;
}
int
sshsig_match_principals(const char *path, const char *principal,
char ***principalsp, size_t *nprincipalsp)
{
FILE *f = NULL;
char *found, *line = NULL, **principals = NULL, **tmp;
size_t i, nprincipals = 0, linesize = 0;
u_long linenum = 0;
int oerrno = 0, r, ret = 0;
if (principalsp != NULL)
*principalsp = NULL;
if (nprincipalsp != NULL)
*nprincipalsp = 0;
/* Check key and principal against file */
if ((f = fopen(path, "r")) == NULL) {
oerrno = errno;
error("Unable to open allowed keys file \"%s\": %s",
path, strerror(errno));
errno = oerrno;
return SSH_ERR_SYSTEM_ERROR;
}
while (getline(&line, &linesize, f) != -1) {
linenum++;
/* Parse the line */
if ((r = parse_principals_key_and_options(path, linenum, line,
principal, &found, NULL, NULL)) != 0) {
if (r == SSH_ERR_KEY_NOT_FOUND)
continue;
ret = r;
oerrno = errno;
break; /* unexpected error */
}
if ((tmp = recallocarray(principals, nprincipals,
nprincipals + 1, sizeof(*principals))) == NULL) {
ret = SSH_ERR_ALLOC_FAIL;
free(found);
break;
}
principals = tmp;
principals[nprincipals++] = found; /* transferred */
free(line);
line = NULL;
linesize = 0;
}
fclose(f);
if (ret == 0) {
if (nprincipals == 0)
ret = SSH_ERR_KEY_NOT_FOUND;
if (principalsp != NULL) {
*principalsp = principals;
principals = NULL; /* transferred */
}
if (nprincipalsp != 0) {
*nprincipalsp = nprincipals;
nprincipals = 0;
}
}
for (i = 0; i < nprincipals; i++)
free(principals[i]);
free(principals);
errno = oerrno;
return ret;
}
int
sshsig_get_pubkey(struct sshbuf *signature, struct sshkey **pubkey)
{
struct sshkey *pk = NULL;
int r = SSH_ERR_SIGNATURE_INVALID;
if (pubkey == NULL)
return SSH_ERR_INTERNAL_ERROR;
if ((r = sshsig_parse_preamble(signature)) != 0)
return r;
if ((r = sshkey_froms(signature, &pk)) != 0)
return r;
*pubkey = pk;
pk = NULL;
return 0;
}