summaryrefslogtreecommitdiff
path: root/sshsig.c
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 /sshsig.c
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
Diffstat (limited to 'sshsig.c')
-rw-r--r--sshsig.c787
1 files changed, 787 insertions, 0 deletions
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}