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:
parent
5485f8d50a
commit
2a9c9f7272
|
@ -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
|
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)
|
$(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)
|
$(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
|
ssh-keysign$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-keysign.o readconf.o uidswap.o compat.o
|
||||||
|
|
|
@ -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.
|
||||||
|
|
123
ssh-keygen.1
123
ssh-keygen.1
|
@ -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>
|
.\" Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||||
.\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
.\" 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
|
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
.\" 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
|
.Dt SSH-KEYGEN 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
@ -141,6 +141,18 @@
|
||||||
.Fl Q
|
.Fl Q
|
||||||
.Fl f Ar krl_file
|
.Fl f Ar krl_file
|
||||||
.Ar
|
.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
|
.Ek
|
||||||
.Sh DESCRIPTION
|
.Sh DESCRIPTION
|
||||||
.Nm
|
.Nm
|
||||||
|
@ -649,6 +661,62 @@ Specify desired generator when testing candidate moduli for DH-GEX.
|
||||||
.It Fl y
|
.It Fl y
|
||||||
This option will read a private
|
This option will read a private
|
||||||
OpenSSH format file and print an OpenSSH public key to stdout.
|
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
|
.It Fl z Ar serial_number
|
||||||
Specifies a serial number to be embedded in the certificate to distinguish
|
Specifies a serial number to be embedded in the certificate to distinguish
|
||||||
this certificate from others from the same CA.
|
this certificate from others from the same CA.
|
||||||
|
@ -885,6 +953,57 @@ then
|
||||||
.Nm
|
.Nm
|
||||||
will exit with a non-zero exit status.
|
will exit with a non-zero exit status.
|
||||||
A zero exit status will only be returned if no key was revoked.
|
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
|
.Sh FILES
|
||||||
.Bl -tag -width Ds -compact
|
.Bl -tag -width Ds -compact
|
||||||
.It Pa ~/.ssh/id_dsa
|
.It Pa ~/.ssh/id_dsa
|
||||||
|
|
325
ssh-keygen.c
325
ssh-keygen.c
|
@ -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>
|
* Author: Tatu Ylonen <ylo@cs.hut.fi>
|
||||||
* Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
* Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
|
||||||
|
@ -59,6 +59,7 @@
|
||||||
#include "digest.h"
|
#include "digest.h"
|
||||||
#include "utf8.h"
|
#include "utf8.h"
|
||||||
#include "authfd.h"
|
#include "authfd.h"
|
||||||
|
#include "sshsig.h"
|
||||||
|
|
||||||
#ifdef WITH_OPENSSL
|
#ifdef WITH_OPENSSL
|
||||||
# define DEFAULT_KEY_TYPE_NAME "rsa"
|
# define DEFAULT_KEY_TYPE_NAME "rsa"
|
||||||
|
@ -2422,6 +2423,279 @@ do_check_krl(struct passwd *pw, int argc, char **argv)
|
||||||
exit(ret);
|
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
|
static void
|
||||||
usage(void)
|
usage(void)
|
||||||
{
|
{
|
||||||
|
@ -2457,7 +2731,10 @@ usage(void)
|
||||||
" ssh-keygen -A\n"
|
" ssh-keygen -A\n"
|
||||||
" ssh-keygen -k -f krl_file [-u] [-s ca_public] [-z version_number]\n"
|
" ssh-keygen -k -f krl_file [-u] [-s ca_public] [-z version_number]\n"
|
||||||
" file ...\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);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2484,6 +2761,7 @@ main(int argc, char **argv)
|
||||||
FILE *f;
|
FILE *f;
|
||||||
const char *errstr;
|
const char *errstr;
|
||||||
int log_level = SYSLOG_LEVEL_INFO;
|
int log_level = SYSLOG_LEVEL_INFO;
|
||||||
|
char *sign_op = NULL;
|
||||||
#ifdef WITH_OPENSSL
|
#ifdef WITH_OPENSSL
|
||||||
/* Moduli generation/screening */
|
/* Moduli generation/screening */
|
||||||
char out_file[PATH_MAX], *checkpoint = NULL;
|
char out_file[PATH_MAX], *checkpoint = NULL;
|
||||||
|
@ -2514,9 +2792,9 @@ main(int argc, char **argv)
|
||||||
if (gethostname(hostname, sizeof(hostname)) == -1)
|
if (gethostname(hostname, sizeof(hostname)) == -1)
|
||||||
fatal("gethostname: %s", strerror(errno));
|
fatal("gethostname: %s", strerror(errno));
|
||||||
|
|
||||||
/* Remaining characters: Ydw */
|
/* Remaining characters: dw */
|
||||||
while ((opt = getopt(argc, argv, "ABHLQUXceghiklopquvxy"
|
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) {
|
"a:b:f:g:j:m:n:r:s:t:z:")) != -1) {
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 'A':
|
case 'A':
|
||||||
|
@ -2672,6 +2950,9 @@ main(int argc, char **argv)
|
||||||
case 'V':
|
case 'V':
|
||||||
parse_cert_times(optarg);
|
parse_cert_times(optarg);
|
||||||
break;
|
break;
|
||||||
|
case 'Y':
|
||||||
|
sign_op = optarg;
|
||||||
|
break;
|
||||||
case 'z':
|
case 'z':
|
||||||
errno = 0;
|
errno = 0;
|
||||||
if (*optarg == '+') {
|
if (*optarg == '+') {
|
||||||
|
@ -2739,6 +3020,42 @@ main(int argc, char **argv)
|
||||||
argv += optind;
|
argv += optind;
|
||||||
argc -= 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 (ca_key_path != NULL) {
|
||||||
if (argc < 1 && !gen_krl) {
|
if (argc < 1 && !gen_krl) {
|
||||||
error("Too few arguments.");
|
error("Too few arguments.");
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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 */
|
Loading…
Reference in New Issue