diff options
-rw-r--r-- | Makefile.in | 2 | ||||
-rw-r--r-- | PROTOCOL.sshsig | 99 | ||||
-rw-r--r-- | ssh-keygen.1 | 123 | ||||
-rw-r--r-- | ssh-keygen.c | 325 | ||||
-rw-r--r-- | sshsig.c | 787 | ||||
-rw-r--r-- | sshsig.h | 78 |
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 | |||
184 | ssh-agent$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-agent.o ssh-pkcs11-client.o | 184 | ssh-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 | ||
187 | ssh-keygen$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-keygen.o | 187 | ssh-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 | ||
190 | ssh-keysign$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-keysign.o readconf.o uidswap.o compat.o | 190 | ssh-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 @@ | |||
1 | This document describes a lightweight SSH Signature format | ||
2 | that is compatible with SSH keys and wire formats. | ||
3 | |||
4 | At present, only detached and armored signatures are supported. | ||
5 | |||
6 | 1. Armored format | ||
7 | |||
8 | The Armored SSH signatures consist of a header, a base64 | ||
9 | encoded blob, and a footer. | ||
10 | |||
11 | The header is the string “-----BEGIN SSH SIGNATURE-----” | ||
12 | followed by a newline. The footer is the string | ||
13 | “-----END SSH SIGNATURE-----” immediately after a newline. | ||
14 | |||
15 | The header MUST be present at the start of every signature. | ||
16 | Files containing the signature MUST start with the header. | ||
17 | Likewise, the footer MUST be present at the end of every | ||
18 | signature. | ||
19 | |||
20 | The base64 encoded blob SHOULD be broken up by newlines | ||
21 | every 76 characters. | ||
22 | |||
23 | Example: | ||
24 | |||
25 | -----BEGIN SSH SIGNATURE----- | ||
26 | U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgJKxoLBJBivUPNTUJUSslQTt2hD | ||
27 | jozKvHarKeN8uYFqgAAAADZm9vAAAAAAAAAFMAAAALc3NoLWVkMjU1MTkAAABAKNC4IEbt | ||
28 | Tq0Fb56xhtuE1/lK9H9RZJfON4o6hE9R4ZGFX98gy0+fFJ/1d2/RxnZky0Y7GojwrZkrHT | ||
29 | FgCqVWAQ== | ||
30 | -----END SSH SIGNATURE----- | ||
31 | |||
32 | 2. 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 | |||
45 | The publickey field MUST contain the serialisation of the | ||
46 | public key used to make the signature using the usual SSH | ||
47 | encoding rules, i.e RFC4253, RFC5656, | ||
48 | draft-ietf-curdle-ssh-ed25519-ed448, etc. | ||
49 | |||
50 | Verifiers MUST reject signatures with versions greater than those | ||
51 | they support. | ||
52 | |||
53 | The purpose of the namespace value is to specify a unambiguous | ||
54 | interpretation domain for the signature, e.g. file signing. | ||
55 | This prevents cross-protocol attacks caused by signatures | ||
56 | intended for one intended domain being accepted in another. | ||
57 | The namespace value MUST NOT be the empty string. | ||
58 | |||
59 | The reserved value is present to encode future information | ||
60 | (e.g. tags) into the signature. Implementations should ignore | ||
61 | the reserved field if it is not empty. | ||
62 | |||
63 | Data to be signed is first hashed with the specified hash_algorithm. | ||
64 | This is done to limit the amount of data presented to the signature | ||
65 | operation, which may be of concern if the signing key is held in limited | ||
66 | or slow hardware or on a remote ssh-agent. The supported hash algorithms | ||
67 | are "sha256" and "sha512". | ||
68 | |||
69 | The signature itself is made using the SSH signature algorithm and | ||
70 | encoding rules for the chosen key type. For RSA signatures, the | ||
71 | signature algorithm must be "rsa-sha2-512" or "rsa-sha2-256" (i.e. | ||
72 | not the legacy RSA-SHA1 "ssh-rsa"). | ||
73 | |||
74 | This blob is encoded as a string using the RFC4243 encoding | ||
75 | rules and base64 encoded to form the middle part of the | ||
76 | armored signature. | ||
77 | |||
78 | |||
79 | 3. 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 | |||
89 | The preamble is the six-byte sequence "SSHSIG". It is included to | ||
90 | ensure that manual signatures can never be confused with any message | ||
91 | signed during SSH user or host authentication. | ||
92 | |||
93 | The reserved value is present to encode future information | ||
94 | (e.g. tags) into the signature. Implementations should ignore | ||
95 | the reserved field if it is not empty. | ||
96 | |||
97 | The data is concatenated and passed to the SSH signing | ||
98 | function. | ||
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 |
650 | This option will read a private | 662 | This option will read a private |
651 | OpenSSH format file and print an OpenSSH public key to stdout. | 663 | OpenSSH format file and print an OpenSSH public key to stdout. |
664 | .It Fl Y Ar sign | ||
665 | Cryptographically sign a file or some data using a SSH key. | ||
666 | When signing, | ||
667 | .Nm | ||
668 | accepts zero or more files to sign on the command-line - if no files | ||
669 | are specified then | ||
670 | .Nm | ||
671 | will sign data presented on standard input. | ||
672 | Signatures are written to the path of the input file with | ||
673 | .Dq .sig | ||
674 | appended, or to standard output if the message to be signed was read from | ||
675 | standard input. | ||
676 | .Pp | ||
677 | The key used for signing is specified using the | ||
678 | .Fl f | ||
679 | option and may refer to either a private key, or a public key with the private | ||
680 | half available via | ||
681 | .Xr ssh-agent 1 . | ||
682 | An additional signature namespace, used to prevent signature confusion across | ||
683 | different domains of use (e.g. file signing vs email signing) must be provided | ||
684 | via the | ||
685 | .Fl n | ||
686 | flag. | ||
687 | Namespaces are arbitrary strings, and may include: | ||
688 | .Dq file | ||
689 | for file signing, | ||
690 | .Dq email | ||
691 | for email signing. | ||
692 | For custom uses, it is recommended to use names following a | ||
693 | NAMESPACE@YOUR.DOMAIN pattern to generate unambiguous namespaces. | ||
694 | .It Fl Y Ar verify | ||
695 | Request to verify a signature generated using | ||
696 | .Nm | ||
697 | .Fl Y sign | ||
698 | as described above. | ||
699 | When verifying a signature, | ||
700 | .Nm | ||
701 | accepts a message on standard input and a signature namespace using | ||
702 | .Fl n . | ||
703 | A file containing the corresponding signature must also be supplied using the | ||
704 | .Fl s | ||
705 | flag, along with the identity of the signer using | ||
706 | .Fl I | ||
707 | and a list of allowed signers via the | ||
708 | .Fl f | ||
709 | flag. | ||
710 | The format of the allowed signers file is documented in the | ||
711 | .Sx ALLOWED SIGNERS | ||
712 | section below. | ||
713 | A file containing revoked keys can be passed using the | ||
714 | .Fl r | ||
715 | flag. The revocation file may be a KRL or a one-per-line list | ||
716 | of public keys. | ||
717 | Successful verification by an authorized signer is signalled by | ||
718 | .Nm | ||
719 | returning a zero exit status. | ||
652 | .It Fl z Ar serial_number | 720 | .It Fl z Ar serial_number |
653 | Specifies a serial number to be embedded in the certificate to distinguish | 721 | Specifies a serial number to be embedded in the certificate to distinguish |
654 | this certificate from others from the same CA. | 722 | this certificate from others from the same CA. |
@@ -885,6 +953,57 @@ then | |||
885 | .Nm | 953 | .Nm |
886 | will exit with a non-zero exit status. | 954 | will exit with a non-zero exit status. |
887 | A zero exit status will only be returned if no key was revoked. | 955 | A zero exit status will only be returned if no key was revoked. |
956 | .Sh ALLOWED SIGNERS | ||
957 | When verifying signatures, | ||
958 | .Nm | ||
959 | uses a simple list of identities and keys to determine whether a signature | ||
960 | comes from an authorized source. | ||
961 | This "allowed signers" file uses a format patterned after the | ||
962 | AUTHORIZED_KEYS FILE FORMAT described in | ||
963 | .Xr sshd(8) . | ||
964 | Each line of the file contains the following space-separated fields: | ||
965 | principals, options, keytype, base64-encoded key. | ||
966 | Empty lines and lines starting with a | ||
967 | .Ql # | ||
968 | are ignored as comments. | ||
969 | .Pp | ||
970 | The principals field is a pattern-list (See PATTERNS in | ||
971 | .Xr ssh_config 5 ) | ||
972 | consisting of one or more comma-separated USER@DOMAIN identity patterns | ||
973 | that are accepted for signing. | ||
974 | When verifying, the identity presented via the | ||
975 | .Fl I option | ||
976 | must match a principals pattern in order for the corresponding key to be | ||
977 | considered acceptable for verification. | ||
978 | .Pp | ||
979 | The options (if present) consist of comma-separated option specifications. | ||
980 | No spaces are permitted, except within double quotes. | ||
981 | The following option specifications are supported (note that option keywords | ||
982 | are case-insensitive): | ||
983 | .Bl -tag -width Ds | ||
984 | .It Cm cert-authority | ||
985 | Indicates that this key is accepted as a certificate authority (CA) and | ||
986 | that certificates signed by this CA may be accepted for verification. | ||
987 | .It Cm namespaces="namespace-list" | ||
988 | Specifies a pattern-list of namespaces that are accepted for this key. | ||
989 | If this option is present, the the signature namespace embedded in the | ||
990 | signature object and presented on the verification command-line must | ||
991 | match the specified list before the key will be considered acceptable. | ||
992 | .El | ||
993 | .Pp | ||
994 | When verifying signatures made by certificates, the expected principal | ||
995 | name must match both the principals pattern in the allowed signers file and | ||
996 | the principals embedded in the certificate itself. | ||
997 | .Pp | ||
998 | An example allowed signers file: | ||
999 | .Bd -literal -offset 3n | ||
1000 | # Comments allowed at start of line | ||
1001 | user1@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. | ||
1005 | user2@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 | ||
2426 | static struct sshkey * | ||
2427 | load_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 | |||
2482 | static int | ||
2483 | sign_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 | |||
2554 | static int | ||
2555 | sign(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; | ||
2617 | done: | ||
2618 | if (fd != -1 && fd != STDIN_FILENO) | ||
2619 | close(fd); | ||
2620 | sshkey_free(pubkey); | ||
2621 | sshkey_free(privkey); | ||
2622 | return ret; | ||
2623 | } | ||
2624 | |||
2625 | static int | ||
2626 | verify(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; | ||
2675 | done: | ||
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 | |||
2425 | static void | 2699 | static void |
2426 | usage(void) | 2700 | usage(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 | |||
45 | int | ||
46 | sshsig_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 | |||
86 | int | ||
87 | sshsig_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 */ | ||
143 | done: | ||
144 | sshbuf_free(buf); | ||
145 | sshbuf_free(sbuf); | ||
146 | free(b64); | ||
147 | return r; | ||
148 | } | ||
149 | |||
150 | static int | ||
151 | sshsig_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; | ||
212 | done: | ||
213 | free(sig); | ||
214 | sshbuf_free(blob); | ||
215 | sshbuf_free(tosign); | ||
216 | return r; | ||
217 | } | ||
218 | |||
219 | /* Check preamble and version. */ | ||
220 | static int | ||
221 | sshsig_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 | |||
241 | static int | ||
242 | sshsig_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 | |||
250 | static int | ||
251 | sshsig_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 | |||
284 | static int | ||
285 | sshsig_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 | } | ||
375 | done: | ||
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 | |||
385 | int | ||
386 | sshsig_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 | |||
426 | int | ||
427 | sshsig_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 | |||
466 | static int | ||
467 | hash_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 | |||
513 | int | ||
514 | sshsig_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 | |||
554 | int | ||
555 | sshsig_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 | |||
594 | struct sigopts { | ||
595 | int ca; | ||
596 | char *namespaces; | ||
597 | }; | ||
598 | |||
599 | static struct sigopts * | ||
600 | sigopts_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 | |||
651 | static void | ||
652 | sigopts_free(struct sigopts *opts) | ||
653 | { | ||
654 | if (opts == NULL) | ||
655 | return; | ||
656 | free(opts->namespaces); | ||
657 | free(opts); | ||
658 | } | ||
659 | |||
660 | static int | ||
661 | check_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 | |||
749 | int | ||
750 | sshsig_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 | |||
20 | struct sshbuf; | ||
21 | struct sshkey; | ||
22 | |||
23 | typedef 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 | */ | ||
31 | int 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 | */ | ||
40 | int 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 | */ | ||
49 | int 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 | */ | ||
58 | int 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 | */ | ||
64 | int sshsig_armor(const struct sshbuf *blob, struct sshbuf **out); | ||
65 | |||
66 | /* | ||
67 | * Decode a base64 encoded armoured signature to a raw signature. | ||
68 | */ | ||
69 | int 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 | */ | ||
75 | int sshsig_check_allowed_keys(const char *path, const struct sshkey *sign_key, | ||
76 | const char *principal, const char *ns); | ||
77 | |||
78 | #endif /* SSHSIG_H */ | ||