summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordjm@openbsd.org <djm@openbsd.org>2019-09-03 08:34:19 +0000
committerDamien Miller <djm@mindrot.org>2019-09-03 18:40:23 +1000
commit2a9c9f7272c1e8665155118fe6536bebdafb6166 (patch)
tree177a8c032d9396249708e4a5cb65321d9250fdee
parent5485f8d50a5bc46aeed829075ebf5d9c617027ea (diff)
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
-rw-r--r--Makefile.in2
-rw-r--r--PROTOCOL.sshsig99
-rw-r--r--ssh-keygen.1123
-rw-r--r--ssh-keygen.c325
-rw-r--r--sshsig.c787
-rw-r--r--sshsig.h78
6 files changed, 1407 insertions, 7 deletions
diff --git a/Makefile.in b/Makefile.in
index c923c6ce9..8d9495091 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -184,7 +184,7 @@ ssh-add$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-add.o
184ssh-agent$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-agent.o ssh-pkcs11-client.o 184ssh-agent$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-agent.o ssh-pkcs11-client.o
185 $(LD) -o $@ ssh-agent.o ssh-pkcs11-client.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) 185 $(LD) -o $@ ssh-agent.o ssh-pkcs11-client.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
186 186
187ssh-keygen$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-keygen.o 187ssh-keygen$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-keygen.o sshsig.o
188 $(LD) -o $@ ssh-keygen.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) 188 $(LD) -o $@ ssh-keygen.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
189 189
190ssh-keysign$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-keysign.o readconf.o uidswap.o compat.o 190ssh-keysign$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-keysign.o readconf.o uidswap.o compat.o
diff --git a/PROTOCOL.sshsig b/PROTOCOL.sshsig
new file mode 100644
index 000000000..806c35da6
--- /dev/null
+++ b/PROTOCOL.sshsig
@@ -0,0 +1,99 @@
1This document describes a lightweight SSH Signature format
2that is compatible with SSH keys and wire formats.
3
4At present, only detached and armored signatures are supported.
5
61. Armored format
7
8The Armored SSH signatures consist of a header, a base64
9encoded blob, and a footer.
10
11The header is the string “-----BEGIN SSH SIGNATURE-----”
12followed by a newline. The footer is the string
13“-----END SSH SIGNATURE-----” immediately after a newline.
14
15The header MUST be present at the start of every signature.
16Files containing the signature MUST start with the header.
17Likewise, the footer MUST be present at the end of every
18signature.
19
20The base64 encoded blob SHOULD be broken up by newlines
21every 76 characters.
22
23Example:
24
25-----BEGIN SSH SIGNATURE-----
26U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgJKxoLBJBivUPNTUJUSslQTt2hD
27jozKvHarKeN8uYFqgAAAADZm9vAAAAAAAAAFMAAAALc3NoLWVkMjU1MTkAAABAKNC4IEbt
28Tq0Fb56xhtuE1/lK9H9RZJfON4o6hE9R4ZGFX98gy0+fFJ/1d2/RxnZky0Y7GojwrZkrHT
29FgCqVWAQ==
30-----END SSH SIGNATURE-----
31
322. Blob format
33
34#define MAGIC_PREAMBLE "SSHSIG"
35#define SIG_VERSION 0x01
36
37 byte[6] MAGIC_PREAMBLE
38 uint32 SIG_VERSION
39 string publickey
40 string namespace
41 string reserved
42 string hash_algorithm
43 string signature
44
45The publickey field MUST contain the serialisation of the
46public key used to make the signature using the usual SSH
47encoding rules, i.e RFC4253, RFC5656,
48draft-ietf-curdle-ssh-ed25519-ed448, etc.
49
50Verifiers MUST reject signatures with versions greater than those
51they support.
52
53The purpose of the namespace value is to specify a unambiguous
54interpretation domain for the signature, e.g. file signing.
55This prevents cross-protocol attacks caused by signatures
56intended for one intended domain being accepted in another.
57The namespace value MUST NOT be the empty string.
58
59The reserved value is present to encode future information
60(e.g. tags) into the signature. Implementations should ignore
61the reserved field if it is not empty.
62
63Data to be signed is first hashed with the specified hash_algorithm.
64This is done to limit the amount of data presented to the signature
65operation, which may be of concern if the signing key is held in limited
66or slow hardware or on a remote ssh-agent. The supported hash algorithms
67are "sha256" and "sha512".
68
69The signature itself is made using the SSH signature algorithm and
70encoding rules for the chosen key type. For RSA signatures, the
71signature algorithm must be "rsa-sha2-512" or "rsa-sha2-256" (i.e.
72not the legacy RSA-SHA1 "ssh-rsa").
73
74This blob is encoded as a string using the RFC4243 encoding
75rules and base64 encoded to form the middle part of the
76armored signature.
77
78
793. Signed Data, of which the signature goes into the blob above
80
81#define MAGIC_PREAMBLE "SSHSIG"
82
83 byte[6] MAGIC_PREAMBLE
84 string namespace
85 string reserved
86 string hash_algorithm
87 string H(message)
88
89The preamble is the six-byte sequence "SSHSIG". It is included to
90ensure that manual signatures can never be confused with any message
91signed during SSH user or host authentication.
92
93The reserved value is present to encode future information
94(e.g. tags) into the signature. Implementations should ignore
95the reserved field if it is not empty.
96
97The data is concatenated and passed to the SSH signing
98function.
99
diff --git a/ssh-keygen.1 b/ssh-keygen.1
index b4bc336f2..93c76ef8a 100644
--- a/ssh-keygen.1
+++ b/ssh-keygen.1
@@ -1,4 +1,4 @@
1.\" $OpenBSD: ssh-keygen.1,v 1.162 2019/07/19 03:38:01 djm Exp $ 1.\" $OpenBSD: ssh-keygen.1,v 1.163 2019/09/03 08:34:19 djm Exp $
2.\" 2.\"
3.\" Author: Tatu Ylonen <ylo@cs.hut.fi> 3.\" Author: Tatu Ylonen <ylo@cs.hut.fi>
4.\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 4.\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -35,7 +35,7 @@
35.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 35.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
36.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37.\" 37.\"
38.Dd $Mdocdate: July 19 2019 $ 38.Dd $Mdocdate: September 3 2019 $
39.Dt SSH-KEYGEN 1 39.Dt SSH-KEYGEN 1
40.Os 40.Os
41.Sh NAME 41.Sh NAME
@@ -141,6 +141,18 @@
141.Fl Q 141.Fl Q
142.Fl f Ar krl_file 142.Fl f Ar krl_file
143.Ar 143.Ar
144.Nm ssh-keygen
145.Fl Y Cm sign
146.Fl f Ar key_file
147.Fl n Ar namespace
148.Ar
149.Nm ssh-keygen
150.Fl Y Cm verify
151.Fl I Ar signer_identity
152.Fl f Ar allowed_keys_file
153.Fl n Ar namespace
154.Fl s Ar signature_file
155.Op Fl r Ar revocation_file
144.Ek 156.Ek
145.Sh DESCRIPTION 157.Sh DESCRIPTION
146.Nm 158.Nm
@@ -649,6 +661,62 @@ Specify desired generator when testing candidate moduli for DH-GEX.
649.It Fl y 661.It Fl y
650This option will read a private 662This option will read a private
651OpenSSH format file and print an OpenSSH public key to stdout. 663OpenSSH format file and print an OpenSSH public key to stdout.
664.It Fl Y Ar sign
665Cryptographically sign a file or some data using a SSH key.
666When signing,
667.Nm
668accepts zero or more files to sign on the command-line - if no files
669are specified then
670.Nm
671will sign data presented on standard input.
672Signatures are written to the path of the input file with
673.Dq .sig
674appended, or to standard output if the message to be signed was read from
675standard input.
676.Pp
677The key used for signing is specified using the
678.Fl f
679option and may refer to either a private key, or a public key with the private
680half available via
681.Xr ssh-agent 1 .
682An additional signature namespace, used to prevent signature confusion across
683different domains of use (e.g. file signing vs email signing) must be provided
684via the
685.Fl n
686flag.
687Namespaces are arbitrary strings, and may include:
688.Dq file
689for file signing,
690.Dq email
691for email signing.
692For custom uses, it is recommended to use names following a
693NAMESPACE@YOUR.DOMAIN pattern to generate unambiguous namespaces.
694.It Fl Y Ar verify
695Request to verify a signature generated using
696.Nm
697.Fl Y sign
698as described above.
699When verifying a signature,
700.Nm
701accepts a message on standard input and a signature namespace using
702.Fl n .
703A file containing the corresponding signature must also be supplied using the
704.Fl s
705flag, along with the identity of the signer using
706.Fl I
707and a list of allowed signers via the
708.Fl f
709flag.
710The format of the allowed signers file is documented in the
711.Sx ALLOWED SIGNERS
712section below.
713A file containing revoked keys can be passed using the
714.Fl r
715flag. The revocation file may be a KRL or a one-per-line list
716of public keys.
717Successful verification by an authorized signer is signalled by
718.Nm
719returning a zero exit status.
652.It Fl z Ar serial_number 720.It Fl z Ar serial_number
653Specifies a serial number to be embedded in the certificate to distinguish 721Specifies a serial number to be embedded in the certificate to distinguish
654this certificate from others from the same CA. 722this certificate from others from the same CA.
@@ -885,6 +953,57 @@ then
885.Nm 953.Nm
886will exit with a non-zero exit status. 954will exit with a non-zero exit status.
887A zero exit status will only be returned if no key was revoked. 955A zero exit status will only be returned if no key was revoked.
956.Sh ALLOWED SIGNERS
957When verifying signatures,
958.Nm
959uses a simple list of identities and keys to determine whether a signature
960comes from an authorized source.
961This "allowed signers" file uses a format patterned after the
962AUTHORIZED_KEYS FILE FORMAT described in
963.Xr sshd(8) .
964Each line of the file contains the following space-separated fields:
965principals, options, keytype, base64-encoded key.
966Empty lines and lines starting with a
967.Ql #
968are ignored as comments.
969.Pp
970The principals field is a pattern-list (See PATTERNS in
971.Xr ssh_config 5 )
972consisting of one or more comma-separated USER@DOMAIN identity patterns
973that are accepted for signing.
974When verifying, the identity presented via the
975.Fl I option
976must match a principals pattern in order for the corresponding key to be
977considered acceptable for verification.
978.Pp
979The options (if present) consist of comma-separated option specifications.
980No spaces are permitted, except within double quotes.
981The following option specifications are supported (note that option keywords
982are case-insensitive):
983.Bl -tag -width Ds
984.It Cm cert-authority
985Indicates that this key is accepted as a certificate authority (CA) and
986that certificates signed by this CA may be accepted for verification.
987.It Cm namespaces="namespace-list"
988Specifies a pattern-list of namespaces that are accepted for this key.
989If this option is present, the the signature namespace embedded in the
990signature object and presented on the verification command-line must
991match the specified list before the key will be considered acceptable.
992.El
993.Pp
994When verifying signatures made by certificates, the expected principal
995name must match both the principals pattern in the allowed signers file and
996the principals embedded in the certificate itself.
997.Pp
998An example allowed signers file:
999.Bd -literal -offset 3n
1000# Comments allowed at start of line
1001user1@example.com,user2@example.com ssh-rsa AAAAX1...
1002# A certificate authority, trusted for all principals in a domain.
1003*@example.com cert-authority ssh-ed25519 AAAB4...
1004# A key that is accepted only for file signing.
1005user2@example.com namespaces="file" ssh-ed25519 AAA41...
1006.Ed
888.Sh FILES 1007.Sh FILES
889.Bl -tag -width Ds -compact 1008.Bl -tag -width Ds -compact
890.It Pa ~/.ssh/id_dsa 1009.It Pa ~/.ssh/id_dsa
diff --git a/ssh-keygen.c b/ssh-keygen.c
index 3c32513bf..76bc41b2f 100644
--- a/ssh-keygen.c
+++ b/ssh-keygen.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: ssh-keygen.c,v 1.343 2019/09/03 08:27:52 djm Exp $ */ 1/* $OpenBSD: ssh-keygen.c,v 1.344 2019/09/03 08:34:19 djm Exp $ */
2/* 2/*
3 * Author: Tatu Ylonen <ylo@cs.hut.fi> 3 * Author: Tatu Ylonen <ylo@cs.hut.fi>
4 * Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 4 * Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -59,6 +59,7 @@
59#include "digest.h" 59#include "digest.h"
60#include "utf8.h" 60#include "utf8.h"
61#include "authfd.h" 61#include "authfd.h"
62#include "sshsig.h"
62 63
63#ifdef WITH_OPENSSL 64#ifdef WITH_OPENSSL
64# define DEFAULT_KEY_TYPE_NAME "rsa" 65# define DEFAULT_KEY_TYPE_NAME "rsa"
@@ -2422,6 +2423,279 @@ do_check_krl(struct passwd *pw, int argc, char **argv)
2422 exit(ret); 2423 exit(ret);
2423} 2424}
2424 2425
2426static struct sshkey *
2427load_sign_key(const char *keypath, const struct sshkey *pubkey)
2428{
2429 size_t i, slen, plen = strlen(keypath);
2430 char *privpath = xstrdup(keypath);
2431 const char *suffixes[] = { "-cert.pub", ".pub", NULL };
2432 struct sshkey *ret = NULL, *privkey = NULL;
2433 int r;
2434
2435 /*
2436 * If passed a public key filename, then try to locate the correponding
2437 * private key. This lets us specify certificates on the command-line
2438 * and have ssh-keygen find the appropriate private key.
2439 */
2440 for (i = 0; suffixes[i]; i++) {
2441 slen = strlen(suffixes[i]);
2442 if (plen <= slen ||
2443 strcmp(privpath + plen - slen, suffixes[i]) != 0)
2444 continue;
2445 privpath[plen - slen] = '\0';
2446 debug("%s: %s looks like a public key, using private key "
2447 "path %s instead", __func__, keypath, privpath);
2448 }
2449 if ((privkey = load_identity(privpath, NULL)) == NULL) {
2450 error("Couldn't load identity %s", keypath);
2451 goto done;
2452 }
2453 if (!sshkey_equal_public(pubkey, privkey)) {
2454 error("Public key %s doesn't match private %s",
2455 keypath, privpath);
2456 goto done;
2457 }
2458 if (sshkey_is_cert(pubkey) && !sshkey_is_cert(privkey)) {
2459 /*
2460 * Graft the certificate onto the private key to make
2461 * it capable of signing.
2462 */
2463 if ((r = sshkey_to_certified(privkey)) != 0) {
2464 error("%s: sshkey_to_certified: %s", __func__,
2465 ssh_err(r));
2466 goto done;
2467 }
2468 if ((r = sshkey_cert_copy(pubkey, privkey)) != 0) {
2469 error("%s: sshkey_cert_copy: %s", __func__, ssh_err(r));
2470 goto done;
2471 }
2472 }
2473 /* success */
2474 ret = privkey;
2475 privkey = NULL;
2476 done:
2477 sshkey_free(privkey);
2478 free(privpath);
2479 return ret;
2480}
2481
2482static int
2483sign_one(struct sshkey *signkey, const char *filename, int fd,
2484 const char *sig_namespace, sshsig_signer *signer, void *signer_ctx)
2485{
2486 struct sshbuf *sigbuf = NULL, *abuf = NULL;
2487 int r = SSH_ERR_INTERNAL_ERROR, wfd = -1, oerrno;
2488 char *wfile = NULL;
2489 char *asig = NULL;
2490
2491 if (!quiet) {
2492 if (fd == STDIN_FILENO)
2493 fprintf(stderr, "Signing data on standard input\n");
2494 else
2495 fprintf(stderr, "Signing file %s\n", filename);
2496 }
2497 if ((r = sshsig_sign_fd(signkey, NULL, fd, sig_namespace,
2498 &sigbuf, signer, signer_ctx)) != 0) {
2499 error("Signing %s failed: %s", filename, ssh_err(r));
2500 goto out;
2501 }
2502 if ((r = sshsig_armor(sigbuf, &abuf)) != 0) {
2503 error("%s: sshsig_armor: %s", __func__, ssh_err(r));
2504 goto out;
2505 }
2506 if ((asig = sshbuf_dup_string(abuf)) == NULL) {
2507 error("%s: buffer error", __func__);
2508 r = SSH_ERR_ALLOC_FAIL;
2509 goto out;
2510 }
2511
2512 if (fd == STDIN_FILENO) {
2513 fputs(asig, stdout);
2514 fflush(stdout);
2515 } else {
2516 xasprintf(&wfile, "%s.sig", filename);
2517 if (confirm_overwrite(wfile)) {
2518 if ((wfd = open(wfile, O_WRONLY|O_CREAT|O_TRUNC,
2519 0666)) == -1) {
2520 oerrno = errno;
2521 error("Cannot open %s: %s",
2522 wfile, strerror(errno));
2523 errno = oerrno;
2524 r = SSH_ERR_SYSTEM_ERROR;
2525 goto out;
2526 }
2527 if (atomicio(vwrite, wfd, asig,
2528 strlen(asig)) != strlen(asig)) {
2529 oerrno = errno;
2530 error("Cannot write to %s: %s",
2531 wfile, strerror(errno));
2532 errno = oerrno;
2533 r = SSH_ERR_SYSTEM_ERROR;
2534 goto out;
2535 }
2536 if (!quiet) {
2537 fprintf(stderr, "Write signature to %s\n",
2538 wfile);
2539 }
2540 }
2541 }
2542 /* success */
2543 r = 0;
2544 out:
2545 free(wfile);
2546 free(asig);
2547 sshbuf_free(abuf);
2548 sshbuf_free(sigbuf);
2549 if (wfd != -1)
2550 close(wfd);
2551 return r;
2552}
2553
2554static int
2555sign(const char *keypath, const char *sig_namespace, int argc, char **argv)
2556{
2557 int i, fd = -1, r, ret = -1;
2558 int agent_fd = -1;
2559 struct sshkey *pubkey = NULL, *privkey = NULL, *signkey = NULL;
2560 sshsig_signer *signer = NULL;
2561
2562 /* Check file arguments. */
2563 for (i = 0; i < argc; i++) {
2564 if (strcmp(argv[i], "-") != 0)
2565 continue;
2566 if (i > 0 || argc > 1)
2567 fatal("Cannot sign mix of paths and standard input");
2568 }
2569
2570 if ((r = sshkey_load_public(keypath, &pubkey, NULL)) != 0) {
2571 error("Couldn't load public key %s: %s", keypath, ssh_err(r));
2572 goto done;
2573 }
2574
2575 if ((r = ssh_get_authentication_socket(&agent_fd)) != 0)
2576 debug("Couldn't get agent socket: %s", ssh_err(r));
2577 else {
2578 if ((r = ssh_agent_has_key(agent_fd, pubkey)) == 0)
2579 signer = agent_signer;
2580 else
2581 debug("Couldn't find key in agent: %s", ssh_err(r));
2582 }
2583
2584 if (signer == NULL) {
2585 /* Not using agent - try to load private key */
2586 if ((privkey = load_sign_key(keypath, pubkey)) == NULL)
2587 goto done;
2588 signkey = privkey;
2589 } else {
2590 /* Will use key in agent */
2591 signkey = pubkey;
2592 }
2593
2594 if (argc == 0) {
2595 if ((r = sign_one(signkey, "(stdin)", STDIN_FILENO,
2596 sig_namespace, signer, &agent_fd)) != 0)
2597 goto done;
2598 } else {
2599 for (i = 0; i < argc; i++) {
2600 if (strcmp(argv[i], "-") == 0)
2601 fd = STDIN_FILENO;
2602 else if ((fd = open(argv[i], O_RDONLY)) == -1) {
2603 error("Cannot open %s for signing: %s",
2604 argv[i], strerror(errno));
2605 goto done;
2606 }
2607 if ((r = sign_one(signkey, argv[i], fd, sig_namespace,
2608 signer, &agent_fd)) != 0)
2609 goto done;
2610 if (fd != STDIN_FILENO)
2611 close(fd);
2612 fd = -1;
2613 }
2614 }
2615
2616 ret = 0;
2617done:
2618 if (fd != -1 && fd != STDIN_FILENO)
2619 close(fd);
2620 sshkey_free(pubkey);
2621 sshkey_free(privkey);
2622 return ret;
2623}
2624
2625static int
2626verify(const char *signature, const char *sig_namespace, const char *principal,
2627 const char *allowed_keys, const char *revoked_keys)
2628{
2629 int r, ret = -1, sigfd = -1;
2630 struct sshbuf *sigbuf = NULL, *abuf = NULL;
2631 struct sshkey *sign_key = NULL;
2632 char *fp = NULL;
2633
2634 if ((abuf = sshbuf_new()) == NULL)
2635 fatal("%s: sshbuf_new() failed", __func__);
2636
2637 if ((sigfd = open(signature, O_RDONLY)) < 0) {
2638 error("Couldn't open signature file %s", signature);
2639 goto done;
2640 }
2641
2642 if ((r = sshkey_load_file(sigfd, abuf)) != 0) {
2643 error("Couldn't read signature file: %s", ssh_err(r));
2644 goto done;
2645 }
2646 if ((r = sshsig_dearmor(abuf, &sigbuf)) != 0) {
2647 error("%s: sshsig_armor: %s", __func__, ssh_err(r));
2648 return r;
2649 }
2650 if ((r = sshsig_verify_fd(sigbuf, STDIN_FILENO, sig_namespace,
2651 &sign_key)) != 0)
2652 goto done; /* sshsig_verify() prints error */
2653
2654 if ((fp = sshkey_fingerprint(sign_key, fingerprint_hash,
2655 SSH_FP_DEFAULT)) == NULL)
2656 fatal("%s: sshkey_fingerprint failed", __func__);
2657 debug("Valid (unverified) signature from key %s", fp);
2658 free(fp);
2659 fp = NULL;
2660
2661 if (revoked_keys != NULL) {
2662 if ((r = sshkey_check_revoked(sign_key, revoked_keys)) != 0) {
2663 debug3("sshkey_check_revoked failed: %s", ssh_err(r));
2664 goto done;
2665 }
2666 }
2667
2668 if ((r = sshsig_check_allowed_keys(allowed_keys, sign_key,
2669 principal, sig_namespace)) != 0) {
2670 debug3("sshsig_check_allowed_keys failed: %s", ssh_err(r));
2671 goto done;
2672 }
2673 /* success */
2674 ret = 0;
2675done:
2676 if (!quiet) {
2677 if (ret == 0) {
2678 if ((fp = sshkey_fingerprint(sign_key, fingerprint_hash,
2679 SSH_FP_DEFAULT)) == NULL) {
2680 fatal("%s: sshkey_fingerprint failed",
2681 __func__);
2682 }
2683 printf("Good \"%s\" signature for %s with %s key %s\n",
2684 sig_namespace, principal,
2685 sshkey_type(sign_key), fp);
2686 } else {
2687 printf("Could not verify signature.\n");
2688 }
2689 }
2690 if (sigfd != -1)
2691 close(sigfd);
2692 sshbuf_free(sigbuf);
2693 sshbuf_free(abuf);
2694 sshkey_free(sign_key);
2695 free(fp);
2696 return ret;
2697}
2698
2425static void 2699static void
2426usage(void) 2700usage(void)
2427{ 2701{
@@ -2457,7 +2731,10 @@ usage(void)
2457 " ssh-keygen -A\n" 2731 " ssh-keygen -A\n"
2458 " ssh-keygen -k -f krl_file [-u] [-s ca_public] [-z version_number]\n" 2732 " ssh-keygen -k -f krl_file [-u] [-s ca_public] [-z version_number]\n"
2459 " file ...\n" 2733 " file ...\n"
2460 " ssh-keygen -Q -f krl_file file ...\n"); 2734 " ssh-keygen -Q -f le file ...\n"
2735 " ssh-keygen -Y sign -f sign_key -n namespace\n"
2736 " ssh-keygen -Y verify -I signer_identity -s signature_file\n"
2737 " -n namespace -f allowed_keys [-r revoked_keys]\n");
2461 exit(1); 2738 exit(1);
2462} 2739}
2463 2740
@@ -2484,6 +2761,7 @@ main(int argc, char **argv)
2484 FILE *f; 2761 FILE *f;
2485 const char *errstr; 2762 const char *errstr;
2486 int log_level = SYSLOG_LEVEL_INFO; 2763 int log_level = SYSLOG_LEVEL_INFO;
2764 char *sign_op = NULL;
2487#ifdef WITH_OPENSSL 2765#ifdef WITH_OPENSSL
2488 /* Moduli generation/screening */ 2766 /* Moduli generation/screening */
2489 char out_file[PATH_MAX], *checkpoint = NULL; 2767 char out_file[PATH_MAX], *checkpoint = NULL;
@@ -2514,9 +2792,9 @@ main(int argc, char **argv)
2514 if (gethostname(hostname, sizeof(hostname)) == -1) 2792 if (gethostname(hostname, sizeof(hostname)) == -1)
2515 fatal("gethostname: %s", strerror(errno)); 2793 fatal("gethostname: %s", strerror(errno));
2516 2794
2517 /* Remaining characters: Ydw */ 2795 /* Remaining characters: dw */
2518 while ((opt = getopt(argc, argv, "ABHLQUXceghiklopquvxy" 2796 while ((opt = getopt(argc, argv, "ABHLQUXceghiklopquvxy"
2519 "C:D:E:F:G:I:J:K:M:N:O:P:R:S:T:V:W:Z:" 2797 "C:D:E:F:G:I:J:K:M:N:O:P:R:S:T:V:W:Y:Z:"
2520 "a:b:f:g:j:m:n:r:s:t:z:")) != -1) { 2798 "a:b:f:g:j:m:n:r:s:t:z:")) != -1) {
2521 switch (opt) { 2799 switch (opt) {
2522 case 'A': 2800 case 'A':
@@ -2672,6 +2950,9 @@ main(int argc, char **argv)
2672 case 'V': 2950 case 'V':
2673 parse_cert_times(optarg); 2951 parse_cert_times(optarg);
2674 break; 2952 break;
2953 case 'Y':
2954 sign_op = optarg;
2955 break;
2675 case 'z': 2956 case 'z':
2676 errno = 0; 2957 errno = 0;
2677 if (*optarg == '+') { 2958 if (*optarg == '+') {
@@ -2739,6 +3020,42 @@ main(int argc, char **argv)
2739 argv += optind; 3020 argv += optind;
2740 argc -= optind; 3021 argc -= optind;
2741 3022
3023 if (sign_op != NULL) {
3024 if (cert_principals == NULL) {
3025 error("Too few arguments for sign/verify: "
3026 "missing namespace");
3027 exit(1);
3028 }
3029 if (strncmp(sign_op, "sign", 4) == 0) {
3030 if (!have_identity) {
3031 error("Too few arguments for sign: "
3032 "missing key");
3033 exit(1);
3034 }
3035 return sign(identity_file, cert_principals, argc, argv);
3036 } else if (strncmp(sign_op, "verify", 6) == 0) {
3037 if (ca_key_path == NULL) {
3038 error("Too few arguments for verify: "
3039 "missing signature file");
3040 exit(1);
3041 }
3042 if (!have_identity) {
3043 error("Too few arguments for sign: "
3044 "missing allowed keys file");
3045 exit(1);
3046 }
3047 if (cert_key_id == NULL) {
3048 error("Too few arguments for verify: "
3049 "missing principal ID");
3050 exit(1);
3051 }
3052 return verify(ca_key_path, cert_principals,
3053 cert_key_id, identity_file, rr_hostname);
3054 }
3055 usage();
3056 /* NOTREACHED */
3057 }
3058
2742 if (ca_key_path != NULL) { 3059 if (ca_key_path != NULL) {
2743 if (argc < 1 && !gen_krl) { 3060 if (argc < 1 && !gen_krl) {
2744 error("Too few arguments."); 3061 error("Too few arguments.");
diff --git a/sshsig.c b/sshsig.c
new file mode 100644
index 000000000..0a1e14627
--- /dev/null
+++ b/sshsig.c
@@ -0,0 +1,787 @@
1/*
2 * Copyright (c) 2019 Google LLC
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17#include <stdio.h>
18#include <stdlib.h>
19#include <stdarg.h>
20#include <errno.h>
21#include <string.h>
22#include <unistd.h>
23
24#include "authfd.h"
25#include "authfile.h"
26#include "log.h"
27#include "misc.h"
28#include "sshbuf.h"
29#include "sshsig.h"
30#include "ssherr.h"
31#include "sshkey.h"
32#include "match.h"
33#include "digest.h"
34
35#define SIG_VERSION 0x01
36#define MAGIC_PREAMBLE "SSHSIG"
37#define MAGIC_PREAMBLE_LEN (sizeof(MAGIC_PREAMBLE) - 1)
38#define BEGIN_SIGNATURE "-----BEGIN SSH SIGNATURE-----\n"
39#define END_SIGNATURE "-----END SSH SIGNATURE-----"
40#define RSA_SIGN_ALG "rsa-sha2-512" /* XXX maybe make configurable */
41#define RSA_SIGN_ALLOWED "rsa-sha2-512,rsa-sha2-256"
42#define HASHALG_DEFAULT "sha512" /* XXX maybe make configurable */
43#define HASHALG_ALLOWED "sha256,sha512"
44
45int
46sshsig_armor(const struct sshbuf *blob, struct sshbuf **out)
47{
48 struct sshbuf *buf = NULL;
49 int r = SSH_ERR_INTERNAL_ERROR;
50
51 *out = NULL;
52
53 if ((buf = sshbuf_new()) == NULL) {
54 error("%s: sshbuf_new failed", __func__);
55 r = SSH_ERR_ALLOC_FAIL;
56 goto out;
57 }
58
59 if ((r = sshbuf_put(buf, BEGIN_SIGNATURE,
60 sizeof(BEGIN_SIGNATURE)-1)) != 0) {
61 error("%s: sshbuf_putf failed: %s", __func__, ssh_err(r));
62 goto out;
63 }
64
65 if ((r = sshbuf_dtob64(blob, buf, 1)) != 0) {
66 error("%s: Couldn't base64 encode signature blob: %s",
67 __func__, ssh_err(r));
68 goto out;
69 }
70
71 if ((r = sshbuf_put(buf, END_SIGNATURE,
72 sizeof(END_SIGNATURE)-1)) != 0 ||
73 (r = sshbuf_put_u8(buf, '\n')) != 0) {
74 error("%s: sshbuf_put failed: %s", __func__, ssh_err(r));
75 goto out;
76 }
77 /* success */
78 *out = buf;
79 buf = NULL; /* transferred */
80 r = 0;
81 out:
82 sshbuf_free(buf);
83 return r;
84}
85
86int
87sshsig_dearmor(struct sshbuf *sig, struct sshbuf **out)
88{
89 int r;
90 size_t eoffset = 0;
91 struct sshbuf *buf = NULL;
92 struct sshbuf *sbuf = NULL;
93 char *b64 = NULL;
94
95 if ((sbuf = sshbuf_fromb(sig)) == NULL) {
96 error("%s: sshbuf_fromb failed", __func__);
97 return SSH_ERR_ALLOC_FAIL;
98 }
99
100 if ((r = sshbuf_cmp(sbuf, 0,
101 BEGIN_SIGNATURE, sizeof(BEGIN_SIGNATURE)-1)) != 0) {
102 error("Couldn't parse signature: missing header");
103 goto done;
104 }
105
106 if ((r = sshbuf_consume(sbuf, sizeof(BEGIN_SIGNATURE)-1)) != 0) {
107 error("%s: sshbuf_consume failed: %s", __func__, ssh_err(r));
108 goto done;
109 }
110
111 if ((r = sshbuf_find(sbuf, 0, "\n" END_SIGNATURE,
112 sizeof("\n" END_SIGNATURE)-1, &eoffset)) != 0) {
113 error("Couldn't parse signature: missing footer");
114 goto done;
115 }
116
117 if ((r = sshbuf_consume_end(sbuf, sshbuf_len(sbuf)-eoffset)) != 0) {
118 error("%s: sshbuf_consume failed: %s", __func__, ssh_err(r));
119 goto done;
120 }
121
122 if ((b64 = sshbuf_dup_string(sbuf)) == NULL) {
123 error("%s: sshbuf_dup_string failed", __func__);
124 r = SSH_ERR_ALLOC_FAIL;
125 goto done;
126 }
127
128 if ((buf = sshbuf_new()) == NULL) {
129 error("%s: sshbuf_new() failed", __func__);
130 r = SSH_ERR_ALLOC_FAIL;
131 goto done;
132 }
133
134 if ((r = sshbuf_b64tod(buf, b64)) != 0) {
135 error("Coundn't decode signature: %s", ssh_err(r));
136 goto done;
137 }
138
139 /* success */
140 *out = buf;
141 r = 0;
142 buf = NULL; /* transferred */
143done:
144 sshbuf_free(buf);
145 sshbuf_free(sbuf);
146 free(b64);
147 return r;
148}
149
150static int
151sshsig_wrap_sign(struct sshkey *key, const char *hashalg,
152 const struct sshbuf *h_message, const char *sig_namespace,
153 struct sshbuf **out, sshsig_signer *signer, void *signer_ctx)
154{
155 int r;
156 size_t slen = 0;
157 u_char *sig = NULL;
158 struct sshbuf *blob = NULL;
159 struct sshbuf *tosign = NULL;
160 const char *sign_alg = NULL;
161
162 if ((tosign = sshbuf_new()) == NULL ||
163 (blob = sshbuf_new()) == NULL) {
164 error("%s: sshbuf_new failed", __func__);
165 r = SSH_ERR_ALLOC_FAIL;
166 goto done;
167 }
168
169 if ((r = sshbuf_put(tosign, MAGIC_PREAMBLE, MAGIC_PREAMBLE_LEN)) != 0 ||
170 (r = sshbuf_put_cstring(tosign, sig_namespace)) != 0 ||
171 (r = sshbuf_put_string(tosign, NULL, 0)) != 0 || /* reserved */
172 (r = sshbuf_put_cstring(tosign, hashalg)) != 0 ||
173 (r = sshbuf_putb(tosign, h_message)) != 0) {
174 error("Couldn't construct message to sign: %s", ssh_err(r));
175 goto done;
176 }
177
178 /* If using RSA keys then default to a good signature algorithm */
179 if (sshkey_type_plain(key->type) == KEY_RSA)
180 sign_alg = RSA_SIGN_ALG;
181
182 if (signer != NULL) {
183 if ((r = signer(key, &sig, &slen,
184 sshbuf_ptr(tosign), sshbuf_len(tosign),
185 sign_alg, 0, signer_ctx)) != 0) {
186 error("Couldn't sign message: %s", ssh_err(r));
187 goto done;
188 }
189 } else {
190 if ((r = sshkey_sign(key, &sig, &slen,
191 sshbuf_ptr(tosign), sshbuf_len(tosign),
192 sign_alg, 0)) != 0) {
193 error("Couldn't sign message: %s", ssh_err(r));
194 goto done;
195 }
196 }
197
198 if ((r = sshbuf_put(blob, MAGIC_PREAMBLE, MAGIC_PREAMBLE_LEN)) != 0 ||
199 (r = sshbuf_put_u32(blob, SIG_VERSION)) != 0 ||
200 (r = sshkey_puts(key, blob)) != 0 ||
201 (r = sshbuf_put_cstring(blob, sig_namespace)) != 0 ||
202 (r = sshbuf_put_string(blob, NULL, 0)) != 0 || /* reserved */
203 (r = sshbuf_put_cstring(blob, hashalg)) != 0 ||
204 (r = sshbuf_put_string(blob, sig, slen)) != 0) {
205 error("Couldn't populate blob: %s", ssh_err(r));
206 goto done;
207 }
208
209 *out = blob;
210 blob = NULL;
211 r = 0;
212done:
213 free(sig);
214 sshbuf_free(blob);
215 sshbuf_free(tosign);
216 return r;
217}
218
219/* Check preamble and version. */
220static int
221sshsig_parse_preamble(struct sshbuf *buf)
222{
223 int r = SSH_ERR_INTERNAL_ERROR;
224 uint32_t sversion;
225
226 if ((r = sshbuf_cmp(buf, 0, MAGIC_PREAMBLE, MAGIC_PREAMBLE_LEN)) != 0 ||
227 (r = sshbuf_consume(buf, (sizeof(MAGIC_PREAMBLE)-1))) != 0 ||
228 (r = sshbuf_get_u32(buf, &sversion)) != 0) {
229 error("Couldn't verify signature: invalid format");
230 return r;
231 }
232
233 if (sversion < SIG_VERSION) {
234 error("Signature version %lu is larger than supported "
235 "version %u", (unsigned long)sversion, SIG_VERSION);
236 return SSH_ERR_INVALID_FORMAT;
237 }
238 return 0;
239}
240
241static int
242sshsig_check_hashalg(const char *hashalg)
243{
244 if (match_pattern_list(hashalg, HASHALG_ALLOWED, 0) == 1)
245 return 0;
246 error("%s: unsupported hash algorithm \"%.100s\"", __func__, hashalg);
247 return SSH_ERR_SIGN_ALG_UNSUPPORTED;
248}
249
250static int
251sshsig_peek_hashalg(struct sshbuf *signature, char **hashalgp)
252{
253 struct sshbuf *buf = NULL;
254 char *hashalg = NULL;
255 int r = SSH_ERR_INTERNAL_ERROR;
256
257 if (hashalgp != NULL)
258 *hashalgp = NULL;
259 if ((buf = sshbuf_fromb(signature)) == NULL)
260 return SSH_ERR_ALLOC_FAIL;
261 if ((r = sshsig_parse_preamble(buf)) != 0)
262 goto done;
263 if ((r = sshbuf_get_string_direct(buf, NULL, NULL)) != 0 ||
264 (r = sshbuf_get_string_direct(buf, NULL, NULL)) != 0 ||
265 (r = sshbuf_get_string(buf, NULL, NULL)) != 0 ||
266 (r = sshbuf_get_cstring(buf, &hashalg, NULL)) != 0 ||
267 (r = sshbuf_get_string_direct(buf, NULL, NULL)) != 0) {
268 error("Couldn't parse signature blob: %s", ssh_err(r));
269 goto done;
270 }
271 if ((r = sshsig_check_hashalg(hashalg)) != 0)
272 goto done;
273
274 /* success */
275 r = 0;
276 *hashalgp = hashalg;
277 hashalg = NULL;
278 done:
279 free(hashalg);
280 sshbuf_free(buf);
281 return r;
282}
283
284static int
285sshsig_wrap_verify(struct sshbuf *signature, const char *hashalg,
286 const struct sshbuf *h_message, const char *expect_namespace,
287 struct sshkey **sign_keyp)
288{
289 int r = SSH_ERR_INTERNAL_ERROR;
290 struct sshbuf *buf = NULL, *toverify = NULL;
291 struct sshkey *key = NULL;
292 const u_char *sig;
293 char *got_namespace = NULL, *sigtype = NULL, *sig_hashalg = NULL;
294 size_t siglen;
295
296 if (sign_keyp != NULL)
297 *sign_keyp = NULL;
298
299 if ((toverify = sshbuf_new()) == NULL) {
300 error("%s: sshbuf_new failed", __func__);
301 r = SSH_ERR_ALLOC_FAIL;
302 goto done;
303 }
304 if ((r = sshsig_check_hashalg(hashalg)) != 0)
305 goto done;
306
307 if ((r = sshbuf_put(toverify, MAGIC_PREAMBLE,
308 MAGIC_PREAMBLE_LEN)) != 0 ||
309 (r = sshbuf_put_cstring(toverify, expect_namespace)) != 0 ||
310 (r = sshbuf_put_string(toverify, NULL, 0)) != 0 || /* reserved */
311 (r = sshbuf_put_cstring(toverify, hashalg)) != 0 ||
312 (r = sshbuf_putb(toverify, h_message)) != 0) {
313 error("Couldn't construct message to verify: %s", ssh_err(r));
314 goto done;
315 }
316
317 if ((r = sshsig_parse_preamble(signature)) != 0)
318 goto done;
319
320 if ((r = sshkey_froms(signature, &key)) != 0 ||
321 (r = sshbuf_get_cstring(signature, &got_namespace, NULL)) != 0 ||
322 (r = sshbuf_get_string(signature, NULL, NULL)) != 0 ||
323 (r = sshbuf_get_cstring(signature, &sig_hashalg, NULL)) != 0 ||
324 (r = sshbuf_get_string_direct(signature, &sig, &siglen)) != 0) {
325 error("Couldn't parse signature blob: %s", ssh_err(r));
326 goto done;
327 }
328
329 if (sshbuf_len(signature) != 0) {
330 error("Signature contains trailing data");
331 r = SSH_ERR_INVALID_FORMAT;
332 goto done;
333 }
334
335 if (strcmp(expect_namespace, got_namespace) != 0) {
336 error("Couldn't verify signature: namespace does not match");
337 debug("%s: expected namespace \"%s\" received \"%s\"",
338 __func__, expect_namespace, got_namespace);
339 r = SSH_ERR_SIGNATURE_INVALID;
340 goto done;
341 }
342 if (strcmp(hashalg, sig_hashalg) != 0) {
343 error("Couldn't verify signature: hash algorithm mismatch");
344 debug("%s: expected algorithm \"%s\" received \"%s\"",
345 __func__, hashalg, sig_hashalg);
346 r = SSH_ERR_SIGNATURE_INVALID;
347 goto done;
348 }
349 /* Ensure that RSA keys use an acceptable signature algorithm */
350 if (sshkey_type_plain(key->type) == KEY_RSA) {
351 if ((r = sshkey_get_sigtype(sig, siglen, &sigtype)) != 0) {
352 error("Couldn't verify signature: unable to get "
353 "signature type: %s", ssh_err(r));
354 goto done;
355 }
356 if (match_pattern_list(sigtype, RSA_SIGN_ALLOWED, 0) != 1) {
357 error("Couldn't verify signature: unsupported RSA "
358 "signature algorithm %s", sigtype);
359 r = SSH_ERR_SIGN_ALG_UNSUPPORTED;
360 goto done;
361 }
362 }
363 if ((r = sshkey_verify(key, sig, siglen, sshbuf_ptr(toverify),
364 sshbuf_len(toverify), NULL, 0)) != 0) {
365 error("Signature verification failed: %s", ssh_err(r));
366 goto done;
367 }
368
369 /* success */
370 r = 0;
371 if (sign_keyp != NULL) {
372 *sign_keyp = key;
373 key = NULL; /* transferred */
374 }
375done:
376 free(got_namespace);
377 free(sigtype);
378 free(sig_hashalg);
379 sshbuf_free(buf);
380 sshbuf_free(toverify);
381 sshkey_free(key);
382 return r;
383}
384
385int
386sshsig_sign_message(struct sshkey *key, const char *hashalg,
387 const struct sshbuf *message, const char *sig_namespace,
388 struct sshbuf **out, sshsig_signer *signer, void *signer_ctx)
389{
390 u_char hash[SSH_DIGEST_MAX_LENGTH];
391 struct sshbuf *b = NULL;
392 int alg, r = SSH_ERR_INTERNAL_ERROR;
393
394 if (out != NULL)
395 *out = NULL;
396 if (hashalg == NULL)
397 hashalg = HASHALG_DEFAULT;
398
399 if ((r = sshsig_check_hashalg(hashalg)) != 0)
400 return r;
401 if ((alg = ssh_digest_alg_by_name(hashalg)) == -1) {
402 error("%s: can't look up hash algorithm %s",
403 __func__, HASHALG_DEFAULT);
404 return SSH_ERR_INTERNAL_ERROR;
405 }
406 if ((r = ssh_digest_buffer(alg, message, hash, sizeof(hash))) != 0) {
407 error("%s: ssh_digest_buffer failed: %s", __func__, ssh_err(r));
408 return r;
409 }
410 if ((b = sshbuf_from(hash, ssh_digest_bytes(alg))) == NULL) {
411 error("%s: sshbuf_from failed", __func__);
412 r = SSH_ERR_ALLOC_FAIL;
413 goto out;
414 }
415 if ((r = sshsig_wrap_sign(key, hashalg, b, sig_namespace, out,
416 signer, signer_ctx)) != 0)
417 goto out;
418 /* success */
419 r = 0;
420 out:
421 sshbuf_free(b);
422 explicit_bzero(hash, sizeof(hash));
423 return r;
424}
425
426int
427sshsig_verify_message(struct sshbuf *signature, const struct sshbuf *message,
428 const char *expect_namespace, struct sshkey **sign_keyp)
429{
430 u_char hash[SSH_DIGEST_MAX_LENGTH];
431 struct sshbuf *b = NULL;
432 int alg, r = SSH_ERR_INTERNAL_ERROR;
433 char *hashalg = NULL;
434
435 if (sign_keyp != NULL)
436 *sign_keyp = NULL;
437
438 if ((r = sshsig_peek_hashalg(signature, &hashalg)) != 0)
439 return r;
440 if ((alg = ssh_digest_alg_by_name(hashalg)) == -1) {
441 error("%s: can't look up hash algorithm %s",
442 __func__, HASHALG_DEFAULT);
443 return SSH_ERR_INTERNAL_ERROR;
444 }
445 if ((r = ssh_digest_buffer(alg, message, hash, sizeof(hash))) != 0) {
446 error("%s: ssh_digest_buffer failed: %s", __func__, ssh_err(r));
447 goto out;
448 }
449 if ((b = sshbuf_from(hash, ssh_digest_bytes(alg))) == NULL) {
450 error("%s: sshbuf_from failed", __func__);
451 r = SSH_ERR_ALLOC_FAIL;
452 goto out;
453 }
454 if ((r = sshsig_wrap_verify(signature, hashalg, b, expect_namespace,
455 sign_keyp)) != 0)
456 goto out;
457 /* success */
458 r = 0;
459 out:
460 sshbuf_free(b);
461 free(hashalg);
462 explicit_bzero(hash, sizeof(hash));
463 return r;
464}
465
466static int
467hash_file(int fd, int hashalg, u_char *hash, size_t hashlen)
468{
469 char *hex, rbuf[8192];
470 ssize_t n, total = 0;
471 struct ssh_digest_ctx *ctx;
472 int r, oerrno;
473
474 memset(hash, 0, hashlen);
475 if ((ctx = ssh_digest_start(hashalg)) == NULL) {
476 error("%s: ssh_digest_start failed", __func__);
477 return SSH_ERR_INTERNAL_ERROR;
478 }
479 for (;;) {
480 if ((n = read(fd, rbuf, sizeof(rbuf))) == -1) {
481 if (errno == EINTR || errno == EAGAIN)
482 continue;
483 oerrno = errno;
484 error("%s: read: %s", __func__, strerror(errno));
485 ssh_digest_free(ctx);
486 errno = oerrno;
487 return SSH_ERR_SYSTEM_ERROR;
488 } else if (n == 0) {
489 debug2("%s: hashed %zu bytes", __func__, total);
490 break; /* EOF */
491 }
492 total += (size_t)n;
493 if ((r = ssh_digest_update(ctx, rbuf, (size_t)n)) != 0) {
494 error("%s: ssh_digest_update: %s",
495 __func__, ssh_err(r));
496 ssh_digest_free(ctx);
497 return r;
498 }
499 }
500 if ((r = ssh_digest_final(ctx, hash, hashlen)) != 0) {
501 error("%s: ssh_digest_final: %s", __func__, ssh_err(r));
502 ssh_digest_free(ctx);
503 }
504 if ((hex = tohex(hash, hashlen)) != NULL) {
505 debug3("%s: final hash: %s", __func__, hex);
506 freezero(hex, strlen(hex));
507 }
508 /* success */
509 ssh_digest_free(ctx);
510 return 0;
511}
512
513int
514sshsig_sign_fd(struct sshkey *key, const char *hashalg,
515 int fd, const char *sig_namespace, struct sshbuf **out,
516 sshsig_signer *signer, void *signer_ctx)
517{
518 u_char hash[SSH_DIGEST_MAX_LENGTH];
519 struct sshbuf *b = NULL;
520 int alg, r = SSH_ERR_INTERNAL_ERROR;
521
522 if (out != NULL)
523 *out = NULL;
524 if (hashalg == NULL)
525 hashalg = HASHALG_DEFAULT;
526
527 if ((r = sshsig_check_hashalg(hashalg)) != 0)
528 return r;
529 if ((alg = ssh_digest_alg_by_name(hashalg)) == -1) {
530 error("%s: can't look up hash algorithm %s",
531 __func__, HASHALG_DEFAULT);
532 return SSH_ERR_INTERNAL_ERROR;
533 }
534 if ((r = hash_file(fd, alg, hash, sizeof(hash))) != 0) {
535 error("%s: hash_file failed: %s", __func__, ssh_err(r));
536 return r;
537 }
538 if ((b = sshbuf_from(hash, ssh_digest_bytes(alg))) == NULL) {
539 error("%s: sshbuf_from failed", __func__);
540 r = SSH_ERR_ALLOC_FAIL;
541 goto out;
542 }
543 if ((r = sshsig_wrap_sign(key, hashalg, b, sig_namespace, out,
544 signer, signer_ctx)) != 0)
545 goto out;
546 /* success */
547 r = 0;
548 out:
549 sshbuf_free(b);
550 explicit_bzero(hash, sizeof(hash));
551 return r;
552}
553
554int
555sshsig_verify_fd(struct sshbuf *signature, int fd,
556 const char *expect_namespace, struct sshkey **sign_keyp)
557{
558 u_char hash[SSH_DIGEST_MAX_LENGTH];
559 struct sshbuf *b = NULL;
560 int alg, r = SSH_ERR_INTERNAL_ERROR;
561 char *hashalg = NULL;
562
563 if (sign_keyp != NULL)
564 *sign_keyp = NULL;
565
566 if ((r = sshsig_peek_hashalg(signature, &hashalg)) != 0)
567 return r;
568 if ((alg = ssh_digest_alg_by_name(hashalg)) == -1) {
569 error("%s: can't look up hash algorithm %s",
570 __func__, HASHALG_DEFAULT);
571 return SSH_ERR_INTERNAL_ERROR;
572 }
573 if ((r = hash_file(fd, alg, hash, sizeof(hash))) != 0) {
574 error("%s: hash_file failed: %s", __func__, ssh_err(r));
575 return r;
576 }
577 if ((b = sshbuf_from(hash, ssh_digest_bytes(alg))) == NULL) {
578 error("%s: sshbuf_from failed", __func__);
579 r = SSH_ERR_ALLOC_FAIL;
580 goto out;
581 }
582 if ((r = sshsig_wrap_verify(signature, hashalg, b, expect_namespace,
583 sign_keyp)) != 0)
584 goto out;
585 /* success */
586 r = 0;
587 out:
588 sshbuf_free(b);
589 free(hashalg);
590 explicit_bzero(hash, sizeof(hash));
591 return r;
592}
593
594struct sigopts {
595 int ca;
596 char *namespaces;
597};
598
599static struct sigopts *
600sigopts_parse(const char *opts, const char *path, u_long linenum,
601 const char **errstrp)
602{
603 struct sigopts *ret;
604 int r;
605 const char *errstr = NULL;
606
607 if ((ret = calloc(1, sizeof(*ret))) == NULL)
608 return NULL;
609 if (opts == NULL || *opts == '\0')
610 return ret; /* Empty options yields empty options :) */
611
612 while (*opts && *opts != ' ' && *opts != '\t') {
613 /* flag options */
614 if ((r = opt_flag("cert-authority", 0, &opts)) != -1) {
615 ret->ca = 1;
616 } else if (opt_match(&opts, "namespaces")) {
617 if (ret->namespaces != NULL) {
618 errstr = "multiple \"namespaces\" clauses";
619 goto fail;
620 }
621 ret->namespaces = opt_dequote(&opts, &errstr);
622 if (ret->namespaces == NULL)
623 goto fail;
624 }
625 /*
626 * Skip the comma, and move to the next option
627 * (or break out if there are no more).
628 */
629 if (*opts == '\0' || *opts == ' ' || *opts == '\t')
630 break; /* End of options. */
631 /* Anything other than a comma is an unknown option */
632 if (*opts != ',') {
633 errstr = "unknown key option";
634 goto fail;
635 }
636 opts++;
637 if (*opts == '\0') {
638 errstr = "unexpected end-of-options";
639 goto fail;
640 }
641 }
642 /* success */
643 return ret;
644 fail:
645 if (errstrp != NULL)
646 *errstrp = errstr;
647 free(ret);
648 return NULL;
649}
650
651static void
652sigopts_free(struct sigopts *opts)
653{
654 if (opts == NULL)
655 return;
656 free(opts->namespaces);
657 free(opts);
658}
659
660static int
661check_allowed_keys_line(const char *path, u_long linenum, char *line,
662 const struct sshkey *sign_key, const char *principal,
663 const char *sig_namespace)
664{
665 struct sshkey *found_key = NULL;
666 char *cp, *opts = NULL, *identities = NULL;
667 int r, found = 0;
668 const char *reason = NULL;
669 struct sigopts *sigopts = NULL;
670
671 if ((found_key = sshkey_new(KEY_UNSPEC)) == NULL) {
672 error("%s: sshkey_new failed", __func__);
673 return SSH_ERR_ALLOC_FAIL;
674 }
675
676 /* format: identity[,identity...] [option[,option...]] key */
677 cp = line;
678 cp = cp + strspn(cp, " \t"); /* skip leading whitespace */
679 if (*cp == '#' || *cp == '\0')
680 goto done;
681 if ((identities = strdelimw(&cp)) == NULL) {
682 error("%s:%lu: invalid line", path, linenum);
683 goto done;
684 }
685 if (match_pattern_list(principal, identities, 0) != 1) {
686 /* principal didn't match */
687 goto done;
688 }
689 debug("%s: %s:%lu: matched principal \"%s\"",
690 __func__, path, linenum, principal);
691
692 if (sshkey_read(found_key, &cp) != 0) {
693 /* no key? Check for options */
694 opts = cp;
695 if (sshkey_advance_past_options(&cp) != 0) {
696 error("%s:%lu: invalid options",
697 path, linenum);
698 goto done;
699 }
700 *cp++ = '\0';
701 skip_space(&cp);
702 if (sshkey_read(found_key, &cp) != 0) {
703 error("%s:%lu: invalid key", path,
704 linenum);
705 goto done;
706 }
707 }
708 debug3("%s:%lu: options %s", path, linenum, opts == NULL ? "" : opts);
709 if ((sigopts = sigopts_parse(opts, path, linenum, &reason)) == NULL) {
710 error("%s:%lu: bad options: %s", path, linenum, reason);
711 goto done;
712 }
713
714 /* Check whether options preclude the use of this key */
715 if (sigopts->namespaces != NULL &&
716 match_pattern_list(sig_namespace, sigopts->namespaces, 0) != 1) {
717 error("%s:%lu: key is not permitted for use in signature "
718 "namespace \"%s\"", path, linenum, sig_namespace);
719 goto done;
720 }
721
722 if (!sigopts->ca && sshkey_equal(found_key, sign_key)) {
723 /* Exact match of key */
724 debug("%s:%lu: matched key and principal", path, linenum);
725 /* success */
726 found = 1;
727 } else if (sigopts->ca && sshkey_is_cert(sign_key) &&
728 sshkey_equal_public(sign_key->cert->signature_key, found_key)) {
729 /* Match of certificate's CA key */
730 if ((r = sshkey_cert_check_authority(sign_key, 0, 1,
731 principal, &reason)) != 0) {
732 error("%s:%lu: certificate not authorized: %s",
733 path, linenum, reason);
734 goto done;
735 }
736 debug("%s:%lu: matched certificate CA key", path, linenum);
737 /* success */
738 found = 1;
739 } else {
740 /* Principal matched but key didn't */
741 goto done;
742 }
743 done:
744 sshkey_free(found_key);
745 sigopts_free(sigopts);
746 return found ? 0 : SSH_ERR_KEY_NOT_FOUND;
747}
748
749int
750sshsig_check_allowed_keys(const char *path, const struct sshkey *sign_key,
751 const char *principal, const char *sig_namespace)
752{
753 FILE *f = NULL;
754 char *line = NULL;
755 size_t linesize = 0;
756 u_long linenum = 0;
757 int r, oerrno;
758
759 /* Check key and principal against file */
760 if ((f = fopen(path, "r")) == NULL) {
761 oerrno = errno;
762 error("Unable to open allowed keys file \"%s\": %s",
763 path, strerror(errno));
764 errno = oerrno;
765 return SSH_ERR_SYSTEM_ERROR;
766 }
767
768 while (getline(&line, &linesize, f) != -1) {
769 linenum++;
770 r = check_allowed_keys_line(path, linenum, line, sign_key,
771 principal, sig_namespace);
772 if (r == SSH_ERR_KEY_NOT_FOUND)
773 continue;
774 else if (r == 0) {
775 /* success */
776 fclose(f);
777 free(line);
778 return 0;
779 /* XXX continue and check revocation? */
780 } else
781 break;
782 }
783 /* Either we hit an error parsing or we simply didn't find the key */
784 fclose(f);
785 free(line);
786 return r == 0 ? SSH_ERR_KEY_NOT_FOUND : r;
787}
diff --git a/sshsig.h b/sshsig.h
new file mode 100644
index 000000000..92c675e3a
--- /dev/null
+++ b/sshsig.h
@@ -0,0 +1,78 @@
1/*
2 * Copyright (c) 2019 Google LLC
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17#ifndef SSHSIG_H
18#define SSHSIG_H
19
20struct sshbuf;
21struct sshkey;
22
23typedef int sshsig_signer(struct sshkey *, u_char **, size_t *,
24 const u_char *, size_t, const char *, u_int, void *);
25
26/*
27 * Creates a detached SSH signature for a given message.
28 * Returns 0 on success or a negative SSH_ERR_* error code on failure.
29 * out is populated with the detached signature, or NULL on failure.
30 */
31int sshsig_sign_message(struct sshkey *key, const char *hashalg,
32 const struct sshbuf *message, const char *sig_namespace,
33 struct sshbuf **out, sshsig_signer *signer, void *signer_ctx);
34
35/*
36 * Creates a detached SSH signature for a given file.
37 * Returns 0 on success or a negative SSH_ERR_* error code on failure.
38 * out is populated with the detached signature, or NULL on failure.
39 */
40int sshsig_sign_fd(struct sshkey *key, const char *hashalg,
41 int fd, const char *sig_namespace, struct sshbuf **out,
42 sshsig_signer *signer, void *signer_ctx);
43
44/*
45 * Verifies that a detached signature is valid and optionally returns key
46 * used to sign via argument.
47 * Returns 0 on success or a negative SSH_ERR_* error code on failure.
48 */
49int sshsig_verify_message(struct sshbuf *signature,
50 const struct sshbuf *message, const char *sig_namespace,
51 struct sshkey **sign_keyp);
52
53/*
54 * Verifies that a detached signature over a file is valid and optionally
55 * returns key used to sign via argument.
56 * Returns 0 on success or a negative SSH_ERR_* error code on failure.
57 */
58int sshsig_verify_fd(struct sshbuf *signature, int fd,
59 const char *sig_namespace, struct sshkey **sign_keyp);
60
61/*
62 * Return a base64 encoded "ASCII armoured" version of a raw signature.
63 */
64int sshsig_armor(const struct sshbuf *blob, struct sshbuf **out);
65
66/*
67 * Decode a base64 encoded armoured signature to a raw signature.
68 */
69int sshsig_dearmor(struct sshbuf *sig, struct sshbuf **out);
70
71/*
72 * Checks whether a particular key/principal/namespace is permitted by
73 * an allowed_keys file. Returns 0 on success.
74 */
75int sshsig_check_allowed_keys(const char *path, const struct sshkey *sign_key,
76 const char *principal, const char *ns);
77
78#endif /* SSHSIG_H */