upstream: sshsig: lightweight signature and verification ability

for OpenSSH

This adds a simple manual signature scheme to OpenSSH.
Signatures can be made and verified using ssh-keygen -Y sign|verify

Signatures embed the key used to make them. At verification time, this
is matched via principal name against an authorized_keys-like list
of allowed signers.

Mostly by Sebastian Kinne w/ some tweaks by me

ok markus@

OpenBSD-Commit-ID: 2ab568e7114c933346616392579d72be65a4b8fb
This commit is contained in:
djm@openbsd.org 2019-09-03 08:34:19 +00:00 committed by Damien Miller
parent 5485f8d50a
commit 2a9c9f7272
6 changed files with 1407 additions and 7 deletions

View File

@ -184,7 +184,7 @@ ssh-add$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-add.o
ssh-agent$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-agent.o ssh-pkcs11-client.o
$(LD) -o $@ ssh-agent.o ssh-pkcs11-client.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
ssh-keygen$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-keygen.o
ssh-keygen$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-keygen.o sshsig.o
$(LD) -o $@ ssh-keygen.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
ssh-keysign$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-keysign.o readconf.o uidswap.o compat.o

99
PROTOCOL.sshsig Normal file
View File

@ -0,0 +1,99 @@
This document describes a lightweight SSH Signature format
that is compatible with SSH keys and wire formats.
At present, only detached and armored signatures are supported.
1. Armored format
The Armored SSH signatures consist of a header, a base64
encoded blob, and a footer.
The header is the string “-----BEGIN SSH SIGNATURE-----”
followed by a newline. The footer is the string
“-----END SSH SIGNATURE-----” immediately after a newline.
The header MUST be present at the start of every signature.
Files containing the signature MUST start with the header.
Likewise, the footer MUST be present at the end of every
signature.
The base64 encoded blob SHOULD be broken up by newlines
every 76 characters.
Example:
-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgJKxoLBJBivUPNTUJUSslQTt2hD
jozKvHarKeN8uYFqgAAAADZm9vAAAAAAAAAFMAAAALc3NoLWVkMjU1MTkAAABAKNC4IEbt
Tq0Fb56xhtuE1/lK9H9RZJfON4o6hE9R4ZGFX98gy0+fFJ/1d2/RxnZky0Y7GojwrZkrHT
FgCqVWAQ==
-----END SSH SIGNATURE-----
2. Blob format
#define MAGIC_PREAMBLE "SSHSIG"
#define SIG_VERSION 0x01
byte[6] MAGIC_PREAMBLE
uint32 SIG_VERSION
string publickey
string namespace
string reserved
string hash_algorithm
string signature
The publickey field MUST contain the serialisation of the
public key used to make the signature using the usual SSH
encoding rules, i.e RFC4253, RFC5656,
draft-ietf-curdle-ssh-ed25519-ed448, etc.
Verifiers MUST reject signatures with versions greater than those
they support.
The purpose of the namespace value is to specify a unambiguous
interpretation domain for the signature, e.g. file signing.
This prevents cross-protocol attacks caused by signatures
intended for one intended domain being accepted in another.
The namespace value MUST NOT be the empty string.
The reserved value is present to encode future information
(e.g. tags) into the signature. Implementations should ignore
the reserved field if it is not empty.
Data to be signed is first hashed with the specified hash_algorithm.
This is done to limit the amount of data presented to the signature
operation, which may be of concern if the signing key is held in limited
or slow hardware or on a remote ssh-agent. The supported hash algorithms
are "sha256" and "sha512".
The signature itself is made using the SSH signature algorithm and
encoding rules for the chosen key type. For RSA signatures, the
signature algorithm must be "rsa-sha2-512" or "rsa-sha2-256" (i.e.
not the legacy RSA-SHA1 "ssh-rsa").
This blob is encoded as a string using the RFC4243 encoding
rules and base64 encoded to form the middle part of the
armored signature.
3. Signed Data, of which the signature goes into the blob above
#define MAGIC_PREAMBLE "SSHSIG"
byte[6] MAGIC_PREAMBLE
string namespace
string reserved
string hash_algorithm
string H(message)
The preamble is the six-byte sequence "SSHSIG". It is included to
ensure that manual signatures can never be confused with any message
signed during SSH user or host authentication.
The reserved value is present to encode future information
(e.g. tags) into the signature. Implementations should ignore
the reserved field if it is not empty.
The data is concatenated and passed to the SSH signing
function.

View File

@ -1,4 +1,4 @@
.\" $OpenBSD: ssh-keygen.1,v 1.162 2019/07/19 03:38:01 djm Exp $
.\" $OpenBSD: ssh-keygen.1,v 1.163 2019/09/03 08:34:19 djm Exp $
.\"
.\" Author: Tatu Ylonen <ylo@cs.hut.fi>
.\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -35,7 +35,7 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
.Dd $Mdocdate: July 19 2019 $
.Dd $Mdocdate: September 3 2019 $
.Dt SSH-KEYGEN 1
.Os
.Sh NAME
@ -141,6 +141,18 @@
.Fl Q
.Fl f Ar krl_file
.Ar
.Nm ssh-keygen
.Fl Y Cm sign
.Fl f Ar key_file
.Fl n Ar namespace
.Ar
.Nm ssh-keygen
.Fl Y Cm verify
.Fl I Ar signer_identity
.Fl f Ar allowed_keys_file
.Fl n Ar namespace
.Fl s Ar signature_file
.Op Fl r Ar revocation_file
.Ek
.Sh DESCRIPTION
.Nm
@ -649,6 +661,62 @@ Specify desired generator when testing candidate moduli for DH-GEX.
.It Fl y
This option will read a private
OpenSSH format file and print an OpenSSH public key to stdout.
.It Fl Y Ar sign
Cryptographically sign a file or some data using a SSH key.
When signing,
.Nm
accepts zero or more files to sign on the command-line - if no files
are specified then
.Nm
will sign data presented on standard input.
Signatures are written to the path of the input file with
.Dq .sig
appended, or to standard output if the message to be signed was read from
standard input.
.Pp
The key used for signing is specified using the
.Fl f
option and may refer to either a private key, or a public key with the private
half available via
.Xr ssh-agent 1 .
An additional signature namespace, used to prevent signature confusion across
different domains of use (e.g. file signing vs email signing) must be provided
via the
.Fl n
flag.
Namespaces are arbitrary strings, and may include:
.Dq file
for file signing,
.Dq email
for email signing.
For custom uses, it is recommended to use names following a
NAMESPACE@YOUR.DOMAIN pattern to generate unambiguous namespaces.
.It Fl Y Ar verify
Request to verify a signature generated using
.Nm
.Fl Y sign
as described above.
When verifying a signature,
.Nm
accepts a message on standard input and a signature namespace using
.Fl n .
A file containing the corresponding signature must also be supplied using the
.Fl s
flag, along with the identity of the signer using
.Fl I
and a list of allowed signers via the
.Fl f
flag.
The format of the allowed signers file is documented in the
.Sx ALLOWED SIGNERS
section below.
A file containing revoked keys can be passed using the
.Fl r
flag. The revocation file may be a KRL or a one-per-line list
of public keys.
Successful verification by an authorized signer is signalled by
.Nm
returning a zero exit status.
.It Fl z Ar serial_number
Specifies a serial number to be embedded in the certificate to distinguish
this certificate from others from the same CA.
@ -885,6 +953,57 @@ then
.Nm
will exit with a non-zero exit status.
A zero exit status will only be returned if no key was revoked.
.Sh ALLOWED SIGNERS
When verifying signatures,
.Nm
uses a simple list of identities and keys to determine whether a signature
comes from an authorized source.
This "allowed signers" file uses a format patterned after the
AUTHORIZED_KEYS FILE FORMAT described in
.Xr sshd(8) .
Each line of the file contains the following space-separated fields:
principals, options, keytype, base64-encoded key.
Empty lines and lines starting with a
.Ql #
are ignored as comments.
.Pp
The principals field is a pattern-list (See PATTERNS in
.Xr ssh_config 5 )
consisting of one or more comma-separated USER@DOMAIN identity patterns
that are accepted for signing.
When verifying, the identity presented via the
.Fl I option
must match a principals pattern in order for the corresponding key to be
considered acceptable for verification.
.Pp
The options (if present) consist of comma-separated option specifications.
No spaces are permitted, except within double quotes.
The following option specifications are supported (note that option keywords
are case-insensitive):
.Bl -tag -width Ds
.It Cm cert-authority
Indicates that this key is accepted as a certificate authority (CA) and
that certificates signed by this CA may be accepted for verification.
.It Cm namespaces="namespace-list"
Specifies a pattern-list of namespaces that are accepted for this key.
If this option is present, the the signature namespace embedded in the
signature object and presented on the verification command-line must
match the specified list before the key will be considered acceptable.
.El
.Pp
When verifying signatures made by certificates, the expected principal
name must match both the principals pattern in the allowed signers file and
the principals embedded in the certificate itself.
.Pp
An example allowed signers file:
.Bd -literal -offset 3n
# Comments allowed at start of line
user1@example.com,user2@example.com ssh-rsa AAAAX1...
# A certificate authority, trusted for all principals in a domain.
*@example.com cert-authority ssh-ed25519 AAAB4...
# A key that is accepted only for file signing.
user2@example.com namespaces="file" ssh-ed25519 AAA41...
.Ed
.Sh FILES
.Bl -tag -width Ds -compact
.It Pa ~/.ssh/id_dsa

View File

@ -1,4 +1,4 @@
/* $OpenBSD: ssh-keygen.c,v 1.343 2019/09/03 08:27:52 djm Exp $ */
/* $OpenBSD: ssh-keygen.c,v 1.344 2019/09/03 08:34:19 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -59,6 +59,7 @@
#include "digest.h"
#include "utf8.h"
#include "authfd.h"
#include "sshsig.h"
#ifdef WITH_OPENSSL
# define DEFAULT_KEY_TYPE_NAME "rsa"
@ -2422,6 +2423,279 @@ do_check_krl(struct passwd *pw, int argc, char **argv)
exit(ret);
}
static struct sshkey *
load_sign_key(const char *keypath, const struct sshkey *pubkey)
{
size_t i, slen, plen = strlen(keypath);
char *privpath = xstrdup(keypath);
const char *suffixes[] = { "-cert.pub", ".pub", NULL };
struct sshkey *ret = NULL, *privkey = NULL;
int r;
/*
* If passed a public key filename, then try to locate the correponding
* private key. This lets us specify certificates on the command-line
* and have ssh-keygen find the appropriate private key.
*/
for (i = 0; suffixes[i]; i++) {
slen = strlen(suffixes[i]);
if (plen <= slen ||
strcmp(privpath + plen - slen, suffixes[i]) != 0)
continue;
privpath[plen - slen] = '\0';
debug("%s: %s looks like a public key, using private key "
"path %s instead", __func__, keypath, privpath);
}
if ((privkey = load_identity(privpath, NULL)) == NULL) {
error("Couldn't load identity %s", keypath);
goto done;
}
if (!sshkey_equal_public(pubkey, privkey)) {
error("Public key %s doesn't match private %s",
keypath, privpath);
goto done;
}
if (sshkey_is_cert(pubkey) && !sshkey_is_cert(privkey)) {
/*
* Graft the certificate onto the private key to make
* it capable of signing.
*/
if ((r = sshkey_to_certified(privkey)) != 0) {
error("%s: sshkey_to_certified: %s", __func__,
ssh_err(r));
goto done;
}
if ((r = sshkey_cert_copy(pubkey, privkey)) != 0) {
error("%s: sshkey_cert_copy: %s", __func__, ssh_err(r));
goto done;
}
}
/* success */
ret = privkey;
privkey = NULL;
done:
sshkey_free(privkey);
free(privpath);
return ret;
}
static int
sign_one(struct sshkey *signkey, const char *filename, int fd,
const char *sig_namespace, sshsig_signer *signer, void *signer_ctx)
{
struct sshbuf *sigbuf = NULL, *abuf = NULL;
int r = SSH_ERR_INTERNAL_ERROR, wfd = -1, oerrno;
char *wfile = NULL;
char *asig = NULL;
if (!quiet) {
if (fd == STDIN_FILENO)
fprintf(stderr, "Signing data on standard input\n");
else
fprintf(stderr, "Signing file %s\n", filename);
}
if ((r = sshsig_sign_fd(signkey, NULL, fd, sig_namespace,
&sigbuf, signer, signer_ctx)) != 0) {
error("Signing %s failed: %s", filename, ssh_err(r));
goto out;
}
if ((r = sshsig_armor(sigbuf, &abuf)) != 0) {
error("%s: sshsig_armor: %s", __func__, ssh_err(r));
goto out;
}
if ((asig = sshbuf_dup_string(abuf)) == NULL) {
error("%s: buffer error", __func__);
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if (fd == STDIN_FILENO) {
fputs(asig, stdout);
fflush(stdout);
} else {
xasprintf(&wfile, "%s.sig", filename);
if (confirm_overwrite(wfile)) {
if ((wfd = open(wfile, O_WRONLY|O_CREAT|O_TRUNC,
0666)) == -1) {
oerrno = errno;
error("Cannot open %s: %s",
wfile, strerror(errno));
errno = oerrno;
r = SSH_ERR_SYSTEM_ERROR;
goto out;
}
if (atomicio(vwrite, wfd, asig,
strlen(asig)) != strlen(asig)) {
oerrno = errno;
error("Cannot write to %s: %s",
wfile, strerror(errno));
errno = oerrno;
r = SSH_ERR_SYSTEM_ERROR;
goto out;
}
if (!quiet) {
fprintf(stderr, "Write signature to %s\n",
wfile);
}
}
}
/* success */
r = 0;
out:
free(wfile);
free(asig);
sshbuf_free(abuf);
sshbuf_free(sigbuf);
if (wfd != -1)
close(wfd);
return r;
}
static int
sign(const char *keypath, const char *sig_namespace, int argc, char **argv)
{
int i, fd = -1, r, ret = -1;
int agent_fd = -1;
struct sshkey *pubkey = NULL, *privkey = NULL, *signkey = NULL;
sshsig_signer *signer = NULL;
/* Check file arguments. */
for (i = 0; i < argc; i++) {
if (strcmp(argv[i], "-") != 0)
continue;
if (i > 0 || argc > 1)
fatal("Cannot sign mix of paths and standard input");
}
if ((r = sshkey_load_public(keypath, &pubkey, NULL)) != 0) {
error("Couldn't load public key %s: %s", keypath, ssh_err(r));
goto done;
}
if ((r = ssh_get_authentication_socket(&agent_fd)) != 0)
debug("Couldn't get agent socket: %s", ssh_err(r));
else {
if ((r = ssh_agent_has_key(agent_fd, pubkey)) == 0)
signer = agent_signer;
else
debug("Couldn't find key in agent: %s", ssh_err(r));
}
if (signer == NULL) {
/* Not using agent - try to load private key */
if ((privkey = load_sign_key(keypath, pubkey)) == NULL)
goto done;
signkey = privkey;
} else {
/* Will use key in agent */
signkey = pubkey;
}
if (argc == 0) {
if ((r = sign_one(signkey, "(stdin)", STDIN_FILENO,
sig_namespace, signer, &agent_fd)) != 0)
goto done;
} else {
for (i = 0; i < argc; i++) {
if (strcmp(argv[i], "-") == 0)
fd = STDIN_FILENO;
else if ((fd = open(argv[i], O_RDONLY)) == -1) {
error("Cannot open %s for signing: %s",
argv[i], strerror(errno));
goto done;
}
if ((r = sign_one(signkey, argv[i], fd, sig_namespace,
signer, &agent_fd)) != 0)
goto done;
if (fd != STDIN_FILENO)
close(fd);
fd = -1;
}
}
ret = 0;
done:
if (fd != -1 && fd != STDIN_FILENO)
close(fd);
sshkey_free(pubkey);
sshkey_free(privkey);
return ret;
}
static int
verify(const char *signature, const char *sig_namespace, const char *principal,
const char *allowed_keys, const char *revoked_keys)
{
int r, ret = -1, sigfd = -1;
struct sshbuf *sigbuf = NULL, *abuf = NULL;
struct sshkey *sign_key = NULL;
char *fp = NULL;
if ((abuf = sshbuf_new()) == NULL)
fatal("%s: sshbuf_new() failed", __func__);
if ((sigfd = open(signature, O_RDONLY)) < 0) {
error("Couldn't open signature file %s", signature);
goto done;
}
if ((r = sshkey_load_file(sigfd, abuf)) != 0) {
error("Couldn't read signature file: %s", ssh_err(r));
goto done;
}
if ((r = sshsig_dearmor(abuf, &sigbuf)) != 0) {
error("%s: sshsig_armor: %s", __func__, ssh_err(r));
return r;
}
if ((r = sshsig_verify_fd(sigbuf, STDIN_FILENO, sig_namespace,
&sign_key)) != 0)
goto done; /* sshsig_verify() prints error */
if ((fp = sshkey_fingerprint(sign_key, fingerprint_hash,
SSH_FP_DEFAULT)) == NULL)
fatal("%s: sshkey_fingerprint failed", __func__);
debug("Valid (unverified) signature from key %s", fp);
free(fp);
fp = NULL;
if (revoked_keys != NULL) {
if ((r = sshkey_check_revoked(sign_key, revoked_keys)) != 0) {
debug3("sshkey_check_revoked failed: %s", ssh_err(r));
goto done;
}
}
if ((r = sshsig_check_allowed_keys(allowed_keys, sign_key,
principal, sig_namespace)) != 0) {
debug3("sshsig_check_allowed_keys failed: %s", ssh_err(r));
goto done;
}
/* success */
ret = 0;
done:
if (!quiet) {
if (ret == 0) {
if ((fp = sshkey_fingerprint(sign_key, fingerprint_hash,
SSH_FP_DEFAULT)) == NULL) {
fatal("%s: sshkey_fingerprint failed",
__func__);
}
printf("Good \"%s\" signature for %s with %s key %s\n",
sig_namespace, principal,
sshkey_type(sign_key), fp);
} else {
printf("Could not verify signature.\n");
}
}
if (sigfd != -1)
close(sigfd);
sshbuf_free(sigbuf);
sshbuf_free(abuf);
sshkey_free(sign_key);
free(fp);
return ret;
}
static void
usage(void)
{
@ -2457,7 +2731,10 @@ usage(void)
" ssh-keygen -A\n"
" ssh-keygen -k -f krl_file [-u] [-s ca_public] [-z version_number]\n"
" file ...\n"
" ssh-keygen -Q -f krl_file file ...\n");
" ssh-keygen -Q -f le file ...\n"
" ssh-keygen -Y sign -f sign_key -n namespace\n"
" ssh-keygen -Y verify -I signer_identity -s signature_file\n"
" -n namespace -f allowed_keys [-r revoked_keys]\n");
exit(1);
}
@ -2484,6 +2761,7 @@ main(int argc, char **argv)
FILE *f;
const char *errstr;
int log_level = SYSLOG_LEVEL_INFO;
char *sign_op = NULL;
#ifdef WITH_OPENSSL
/* Moduli generation/screening */
char out_file[PATH_MAX], *checkpoint = NULL;
@ -2514,9 +2792,9 @@ main(int argc, char **argv)
if (gethostname(hostname, sizeof(hostname)) == -1)
fatal("gethostname: %s", strerror(errno));
/* Remaining characters: Ydw */
/* Remaining characters: dw */
while ((opt = getopt(argc, argv, "ABHLQUXceghiklopquvxy"
"C:D:E:F:G:I:J:K:M:N:O:P:R:S:T:V:W:Z:"
"C:D:E:F:G:I:J:K:M:N:O:P:R:S:T:V:W:Y:Z:"
"a:b:f:g:j:m:n:r:s:t:z:")) != -1) {
switch (opt) {
case 'A':
@ -2672,6 +2950,9 @@ main(int argc, char **argv)
case 'V':
parse_cert_times(optarg);
break;
case 'Y':
sign_op = optarg;
break;
case 'z':
errno = 0;
if (*optarg == '+') {
@ -2739,6 +3020,42 @@ main(int argc, char **argv)
argv += optind;
argc -= optind;
if (sign_op != NULL) {
if (cert_principals == NULL) {
error("Too few arguments for sign/verify: "
"missing namespace");
exit(1);
}
if (strncmp(sign_op, "sign", 4) == 0) {
if (!have_identity) {
error("Too few arguments for sign: "
"missing key");
exit(1);
}
return sign(identity_file, cert_principals, argc, argv);
} else if (strncmp(sign_op, "verify", 6) == 0) {
if (ca_key_path == NULL) {
error("Too few arguments for verify: "
"missing signature file");
exit(1);
}
if (!have_identity) {
error("Too few arguments for sign: "
"missing allowed keys file");
exit(1);
}
if (cert_key_id == NULL) {
error("Too few arguments for verify: "
"missing principal ID");
exit(1);
}
return verify(ca_key_path, cert_principals,
cert_key_id, identity_file, rr_hostname);
}
usage();
/* NOTREACHED */
}
if (ca_key_path != NULL) {
if (argc < 1 && !gen_krl) {
error("Too few arguments.");

787
sshsig.c Normal file
View File

@ -0,0 +1,787 @@
/*
* 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 <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("%s: sshbuf_new failed", __func__);
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if ((r = sshbuf_put(buf, BEGIN_SIGNATURE,
sizeof(BEGIN_SIGNATURE)-1)) != 0) {
error("%s: sshbuf_putf failed: %s", __func__, ssh_err(r));
goto out;
}
if ((r = sshbuf_dtob64(blob, buf, 1)) != 0) {
error("%s: Couldn't base64 encode signature blob: %s",
__func__, ssh_err(r));
goto out;
}
if ((r = sshbuf_put(buf, END_SIGNATURE,
sizeof(END_SIGNATURE)-1)) != 0 ||
(r = sshbuf_put_u8(buf, '\n')) != 0) {
error("%s: sshbuf_put failed: %s", __func__, ssh_err(r));
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("%s: sshbuf_fromb failed", __func__);
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("%s: sshbuf_consume failed: %s", __func__, ssh_err(r));
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("%s: sshbuf_consume failed: %s", __func__, ssh_err(r));
goto done;
}
if ((b64 = sshbuf_dup_string(sbuf)) == NULL) {
error("%s: sshbuf_dup_string failed", __func__);
r = SSH_ERR_ALLOC_FAIL;
goto done;
}
if ((buf = sshbuf_new()) == NULL) {
error("%s: sshbuf_new() failed", __func__);
r = SSH_ERR_ALLOC_FAIL;
goto done;
}
if ((r = sshbuf_b64tod(buf, b64)) != 0) {
error("Coundn't decode signature: %s", ssh_err(r));
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 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("%s: sshbuf_new failed", __func__);
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_putb(tosign, h_message)) != 0) {
error("Couldn't construct message to sign: %s", ssh_err(r));
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, 0, signer_ctx)) != 0) {
error("Couldn't sign message: %s", ssh_err(r));
goto done;
}
} else {
if ((r = sshkey_sign(key, &sig, &slen,
sshbuf_ptr(tosign), sshbuf_len(tosign),
sign_alg, 0)) != 0) {
error("Couldn't sign message: %s", ssh_err(r));
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("Couldn't populate blob: %s", ssh_err(r));
goto done;
}
*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 (match_pattern_list(hashalg, HASHALG_ALLOWED, 0) == 1)
return 0;
error("%s: unsupported hash algorithm \"%.100s\"", __func__, 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("Couldn't parse signature blob: %s", ssh_err(r));
goto done;
}
if ((r = sshsig_check_hashalg(hashalg)) != 0)
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)
{
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;
if (sign_keyp != NULL)
*sign_keyp = NULL;
if ((toverify = sshbuf_new()) == NULL) {
error("%s: sshbuf_new failed", __func__);
r = SSH_ERR_ALLOC_FAIL;
goto done;
}
if ((r = sshsig_check_hashalg(hashalg)) != 0)
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_putb(toverify, h_message)) != 0) {
error("Couldn't construct message to verify: %s", ssh_err(r));
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("Couldn't parse signature blob: %s", ssh_err(r));
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("%s: expected namespace \"%s\" received \"%s\"",
__func__, 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("%s: expected algorithm \"%s\" received \"%s\"",
__func__, 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("Couldn't verify signature: unable to get "
"signature type: %s", ssh_err(r));
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)) != 0) {
error("Signature verification failed: %s", ssh_err(r));
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;
}
int
sshsig_sign_message(struct sshkey *key, const char *hashalg,
const struct sshbuf *message, const char *sig_namespace,
struct sshbuf **out, sshsig_signer *signer, void *signer_ctx)
{
u_char hash[SSH_DIGEST_MAX_LENGTH];
struct sshbuf *b = NULL;
int alg, r = SSH_ERR_INTERNAL_ERROR;
if (out != NULL)
*out = NULL;
if (hashalg == NULL)
hashalg = HASHALG_DEFAULT;
if ((r = sshsig_check_hashalg(hashalg)) != 0)
return r;
if ((alg = ssh_digest_alg_by_name(hashalg)) == -1) {
error("%s: can't look up hash algorithm %s",
__func__, HASHALG_DEFAULT);
return SSH_ERR_INTERNAL_ERROR;
}
if ((r = ssh_digest_buffer(alg, message, hash, sizeof(hash))) != 0) {
error("%s: ssh_digest_buffer failed: %s", __func__, ssh_err(r));
return r;
}
if ((b = sshbuf_from(hash, ssh_digest_bytes(alg))) == NULL) {
error("%s: sshbuf_from failed", __func__);
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if ((r = sshsig_wrap_sign(key, hashalg, b, sig_namespace, out,
signer, signer_ctx)) != 0)
goto out;
/* success */
r = 0;
out:
sshbuf_free(b);
explicit_bzero(hash, sizeof(hash));
return r;
}
int
sshsig_verify_message(struct sshbuf *signature, const struct sshbuf *message,
const char *expect_namespace, struct sshkey **sign_keyp)
{
u_char hash[SSH_DIGEST_MAX_LENGTH];
struct sshbuf *b = NULL;
int alg, r = SSH_ERR_INTERNAL_ERROR;
char *hashalg = NULL;
if (sign_keyp != NULL)
*sign_keyp = NULL;
if ((r = sshsig_peek_hashalg(signature, &hashalg)) != 0)
return r;
if ((alg = ssh_digest_alg_by_name(hashalg)) == -1) {
error("%s: can't look up hash algorithm %s",
__func__, HASHALG_DEFAULT);
return SSH_ERR_INTERNAL_ERROR;
}
if ((r = ssh_digest_buffer(alg, message, hash, sizeof(hash))) != 0) {
error("%s: ssh_digest_buffer failed: %s", __func__, ssh_err(r));
goto out;
}
if ((b = sshbuf_from(hash, ssh_digest_bytes(alg))) == NULL) {
error("%s: sshbuf_from failed", __func__);
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if ((r = sshsig_wrap_verify(signature, hashalg, b, expect_namespace,
sign_keyp)) != 0)
goto out;
/* success */
r = 0;
out:
sshbuf_free(b);
free(hashalg);
explicit_bzero(hash, sizeof(hash));
return r;
}
static int
hash_file(int fd, int hashalg, u_char *hash, size_t hashlen)
{
char *hex, rbuf[8192];
ssize_t n, total = 0;
struct ssh_digest_ctx *ctx;
int r, oerrno;
memset(hash, 0, hashlen);
if ((ctx = ssh_digest_start(hashalg)) == NULL) {
error("%s: ssh_digest_start failed", __func__);
return SSH_ERR_INTERNAL_ERROR;
}
for (;;) {
if ((n = read(fd, rbuf, sizeof(rbuf))) == -1) {
if (errno == EINTR || errno == EAGAIN)
continue;
oerrno = errno;
error("%s: read: %s", __func__, strerror(errno));
ssh_digest_free(ctx);
errno = oerrno;
return SSH_ERR_SYSTEM_ERROR;
} else if (n == 0) {
debug2("%s: hashed %zu bytes", __func__, total);
break; /* EOF */
}
total += (size_t)n;
if ((r = ssh_digest_update(ctx, rbuf, (size_t)n)) != 0) {
error("%s: ssh_digest_update: %s",
__func__, ssh_err(r));
ssh_digest_free(ctx);
return r;
}
}
if ((r = ssh_digest_final(ctx, hash, hashlen)) != 0) {
error("%s: ssh_digest_final: %s", __func__, ssh_err(r));
ssh_digest_free(ctx);
}
if ((hex = tohex(hash, hashlen)) != NULL) {
debug3("%s: final hash: %s", __func__, hex);
freezero(hex, strlen(hex));
}
/* success */
ssh_digest_free(ctx);
return 0;
}
int
sshsig_sign_fd(struct sshkey *key, const char *hashalg,
int fd, const char *sig_namespace, struct sshbuf **out,
sshsig_signer *signer, void *signer_ctx)
{
u_char hash[SSH_DIGEST_MAX_LENGTH];
struct sshbuf *b = NULL;
int alg, r = SSH_ERR_INTERNAL_ERROR;
if (out != NULL)
*out = NULL;
if (hashalg == NULL)
hashalg = HASHALG_DEFAULT;
if ((r = sshsig_check_hashalg(hashalg)) != 0)
return r;
if ((alg = ssh_digest_alg_by_name(hashalg)) == -1) {
error("%s: can't look up hash algorithm %s",
__func__, HASHALG_DEFAULT);
return SSH_ERR_INTERNAL_ERROR;
}
if ((r = hash_file(fd, alg, hash, sizeof(hash))) != 0) {
error("%s: hash_file failed: %s", __func__, ssh_err(r));
return r;
}
if ((b = sshbuf_from(hash, ssh_digest_bytes(alg))) == NULL) {
error("%s: sshbuf_from failed", __func__);
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if ((r = sshsig_wrap_sign(key, hashalg, b, sig_namespace, out,
signer, signer_ctx)) != 0)
goto out;
/* success */
r = 0;
out:
sshbuf_free(b);
explicit_bzero(hash, sizeof(hash));
return r;
}
int
sshsig_verify_fd(struct sshbuf *signature, int fd,
const char *expect_namespace, struct sshkey **sign_keyp)
{
u_char hash[SSH_DIGEST_MAX_LENGTH];
struct sshbuf *b = NULL;
int alg, r = SSH_ERR_INTERNAL_ERROR;
char *hashalg = NULL;
if (sign_keyp != NULL)
*sign_keyp = NULL;
if ((r = sshsig_peek_hashalg(signature, &hashalg)) != 0)
return r;
if ((alg = ssh_digest_alg_by_name(hashalg)) == -1) {
error("%s: can't look up hash algorithm %s",
__func__, HASHALG_DEFAULT);
return SSH_ERR_INTERNAL_ERROR;
}
if ((r = hash_file(fd, alg, hash, sizeof(hash))) != 0) {
error("%s: hash_file failed: %s", __func__, ssh_err(r));
return r;
}
if ((b = sshbuf_from(hash, ssh_digest_bytes(alg))) == NULL) {
error("%s: sshbuf_from failed", __func__);
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if ((r = sshsig_wrap_verify(signature, hashalg, b, expect_namespace,
sign_keyp)) != 0)
goto out;
/* success */
r = 0;
out:
sshbuf_free(b);
free(hashalg);
explicit_bzero(hash, sizeof(hash));
return r;
}
struct sigopts {
int ca;
char *namespaces;
};
static struct sigopts *
sigopts_parse(const char *opts, const char *path, u_long linenum,
const char **errstrp)
{
struct sigopts *ret;
int r;
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;
}
/*
* 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;
}
}
/* success */
return ret;
fail:
if (errstrp != NULL)
*errstrp = errstr;
free(ret);
return NULL;
}
static void
sigopts_free(struct sigopts *opts)
{
if (opts == NULL)
return;
free(opts->namespaces);
free(opts);
}
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)
{
struct sshkey *found_key = NULL;
char *cp, *opts = NULL, *identities = NULL;
int r, found = 0;
const char *reason = NULL;
struct sigopts *sigopts = NULL;
if ((found_key = sshkey_new(KEY_UNSPEC)) == NULL) {
error("%s: sshkey_new failed", __func__);
return SSH_ERR_ALLOC_FAIL;
}
/* format: identity[,identity...] [option[,option...]] key */
cp = line;
cp = cp + strspn(cp, " \t"); /* skip leading whitespace */
if (*cp == '#' || *cp == '\0')
goto done;
if ((identities = strdelimw(&cp)) == NULL) {
error("%s:%lu: invalid line", path, linenum);
goto done;
}
if (match_pattern_list(principal, identities, 0) != 1) {
/* principal didn't match */
goto done;
}
debug("%s: %s:%lu: matched principal \"%s\"",
__func__, path, linenum, principal);
if (sshkey_read(found_key, &cp) != 0) {
/* no key? Check for options */
opts = cp;
if (sshkey_advance_past_options(&cp) != 0) {
error("%s:%lu: invalid options",
path, linenum);
goto done;
}
*cp++ = '\0';
skip_space(&cp);
if (sshkey_read(found_key, &cp) != 0) {
error("%s:%lu: invalid key", path,
linenum);
goto done;
}
}
debug3("%s:%lu: options %s", path, linenum, opts == NULL ? "" : opts);
if ((sigopts = sigopts_parse(opts, path, linenum, &reason)) == NULL) {
error("%s:%lu: bad options: %s", path, linenum, reason);
goto done;
}
/* Check whether options preclude the use of this key */
if (sigopts->namespaces != 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;
}
if (!sigopts->ca && sshkey_equal(found_key, sign_key)) {
/* Exact match of key */
debug("%s:%lu: matched key and principal", path, linenum);
/* success */
found = 1;
} else if (sigopts->ca && sshkey_is_cert(sign_key) &&
sshkey_equal_public(sign_key->cert->signature_key, found_key)) {
/* Match of certificate's CA key */
if ((r = sshkey_cert_check_authority(sign_key, 0, 1,
principal, &reason)) != 0) {
error("%s:%lu: certificate not authorized: %s",
path, linenum, reason);
goto done;
}
debug("%s:%lu: matched certificate CA key", path, linenum);
/* success */
found = 1;
} else {
/* Principal matched but key didn't */
goto done;
}
done:
sshkey_free(found_key);
sigopts_free(sigopts);
return found ? 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)
{
FILE *f = NULL;
char *line = NULL;
size_t linesize = 0;
u_long linenum = 0;
int r, 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);
if (r == SSH_ERR_KEY_NOT_FOUND)
continue;
else if (r == 0) {
/* success */
fclose(f);
free(line);
return 0;
/* XXX continue and check revocation? */
} 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;
}

78
sshsig.h Normal file
View File

@ -0,0 +1,78 @@
/*
* 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.
*/
#ifndef SSHSIG_H
#define SSHSIG_H
struct sshbuf;
struct sshkey;
typedef int sshsig_signer(struct sshkey *, u_char **, size_t *,
const u_char *, size_t, const char *, u_int, void *);
/*
* Creates a detached SSH signature for a given message.
* Returns 0 on success or a negative SSH_ERR_* error code on failure.
* out is populated with the detached signature, or NULL on failure.
*/
int sshsig_sign_message(struct sshkey *key, const char *hashalg,
const struct sshbuf *message, const char *sig_namespace,
struct sshbuf **out, sshsig_signer *signer, void *signer_ctx);
/*
* Creates a detached SSH signature for a given file.
* Returns 0 on success or a negative SSH_ERR_* error code on failure.
* out is populated with the detached signature, or NULL on failure.
*/
int sshsig_sign_fd(struct sshkey *key, const char *hashalg,
int fd, const char *sig_namespace, struct sshbuf **out,
sshsig_signer *signer, void *signer_ctx);
/*
* Verifies that a detached signature is valid and optionally returns key
* used to sign via argument.
* Returns 0 on success or a negative SSH_ERR_* error code on failure.
*/
int sshsig_verify_message(struct sshbuf *signature,
const struct sshbuf *message, const char *sig_namespace,
struct sshkey **sign_keyp);
/*
* Verifies that a detached signature over a file is valid and optionally
* returns key used to sign via argument.
* Returns 0 on success or a negative SSH_ERR_* error code on failure.
*/
int sshsig_verify_fd(struct sshbuf *signature, int fd,
const char *sig_namespace, struct sshkey **sign_keyp);
/*
* Return a base64 encoded "ASCII armoured" version of a raw signature.
*/
int sshsig_armor(const struct sshbuf *blob, struct sshbuf **out);
/*
* Decode a base64 encoded armoured signature to a raw signature.
*/
int sshsig_dearmor(struct sshbuf *sig, struct sshbuf **out);
/*
* Checks whether a particular key/principal/namespace is permitted by
* an allowed_keys file. Returns 0 on success.
*/
int sshsig_check_allowed_keys(const char *path, const struct sshkey *sign_key,
const char *principal, const char *ns);
#endif /* SSHSIG_H */