blob: 8c7aba1b93f8fbc9e8ff2a923ce2d000b7b1c615 [file] [log] [blame]
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +00001/*
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
Damien Miller1a72c0d2019-09-03 18:44:10 +100017#include "includes.h"
18
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +000019#include <stdio.h>
20#include <stdlib.h>
21#include <stdarg.h>
22#include <errno.h>
23#include <string.h>
24#include <unistd.h>
25
26#include "authfd.h"
27#include "authfile.h"
28#include "log.h"
29#include "misc.h"
30#include "sshbuf.h"
31#include "sshsig.h"
32#include "ssherr.h"
33#include "sshkey.h"
34#include "match.h"
35#include "digest.h"
36
37#define SIG_VERSION 0x01
38#define MAGIC_PREAMBLE "SSHSIG"
39#define MAGIC_PREAMBLE_LEN (sizeof(MAGIC_PREAMBLE) - 1)
40#define BEGIN_SIGNATURE "-----BEGIN SSH SIGNATURE-----\n"
41#define END_SIGNATURE "-----END SSH SIGNATURE-----"
42#define RSA_SIGN_ALG "rsa-sha2-512" /* XXX maybe make configurable */
43#define RSA_SIGN_ALLOWED "rsa-sha2-512,rsa-sha2-256"
44#define HASHALG_DEFAULT "sha512" /* XXX maybe make configurable */
45#define HASHALG_ALLOWED "sha256,sha512"
46
47int
48sshsig_armor(const struct sshbuf *blob, struct sshbuf **out)
49{
50 struct sshbuf *buf = NULL;
51 int r = SSH_ERR_INTERNAL_ERROR;
52
53 *out = NULL;
54
55 if ((buf = sshbuf_new()) == NULL) {
56 error("%s: sshbuf_new failed", __func__);
57 r = SSH_ERR_ALLOC_FAIL;
58 goto out;
59 }
60
61 if ((r = sshbuf_put(buf, BEGIN_SIGNATURE,
62 sizeof(BEGIN_SIGNATURE)-1)) != 0) {
63 error("%s: sshbuf_putf failed: %s", __func__, ssh_err(r));
64 goto out;
65 }
66
67 if ((r = sshbuf_dtob64(blob, buf, 1)) != 0) {
68 error("%s: Couldn't base64 encode signature blob: %s",
69 __func__, ssh_err(r));
70 goto out;
71 }
72
73 if ((r = sshbuf_put(buf, END_SIGNATURE,
74 sizeof(END_SIGNATURE)-1)) != 0 ||
75 (r = sshbuf_put_u8(buf, '\n')) != 0) {
76 error("%s: sshbuf_put failed: %s", __func__, ssh_err(r));
77 goto out;
78 }
79 /* success */
80 *out = buf;
81 buf = NULL; /* transferred */
82 r = 0;
83 out:
84 sshbuf_free(buf);
85 return r;
86}
87
88int
89sshsig_dearmor(struct sshbuf *sig, struct sshbuf **out)
90{
91 int r;
92 size_t eoffset = 0;
93 struct sshbuf *buf = NULL;
94 struct sshbuf *sbuf = NULL;
95 char *b64 = NULL;
96
97 if ((sbuf = sshbuf_fromb(sig)) == NULL) {
98 error("%s: sshbuf_fromb failed", __func__);
99 return SSH_ERR_ALLOC_FAIL;
100 }
101
102 if ((r = sshbuf_cmp(sbuf, 0,
103 BEGIN_SIGNATURE, sizeof(BEGIN_SIGNATURE)-1)) != 0) {
104 error("Couldn't parse signature: missing header");
105 goto done;
106 }
107
108 if ((r = sshbuf_consume(sbuf, sizeof(BEGIN_SIGNATURE)-1)) != 0) {
109 error("%s: sshbuf_consume failed: %s", __func__, ssh_err(r));
110 goto done;
111 }
112
113 if ((r = sshbuf_find(sbuf, 0, "\n" END_SIGNATURE,
114 sizeof("\n" END_SIGNATURE)-1, &eoffset)) != 0) {
115 error("Couldn't parse signature: missing footer");
116 goto done;
117 }
118
119 if ((r = sshbuf_consume_end(sbuf, sshbuf_len(sbuf)-eoffset)) != 0) {
120 error("%s: sshbuf_consume failed: %s", __func__, ssh_err(r));
121 goto done;
122 }
123
124 if ((b64 = sshbuf_dup_string(sbuf)) == NULL) {
125 error("%s: sshbuf_dup_string failed", __func__);
126 r = SSH_ERR_ALLOC_FAIL;
127 goto done;
128 }
129
130 if ((buf = sshbuf_new()) == NULL) {
131 error("%s: sshbuf_new() failed", __func__);
132 r = SSH_ERR_ALLOC_FAIL;
133 goto done;
134 }
135
136 if ((r = sshbuf_b64tod(buf, b64)) != 0) {
naddy@openbsd.org0f44e592019-09-03 20:51:49 +0000137 error("Couldn't decode signature: %s", ssh_err(r));
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000138 goto done;
139 }
140
141 /* success */
142 *out = buf;
143 r = 0;
144 buf = NULL; /* transferred */
145done:
146 sshbuf_free(buf);
147 sshbuf_free(sbuf);
148 free(b64);
149 return r;
150}
151
152static int
153sshsig_wrap_sign(struct sshkey *key, const char *hashalg,
djm@openbsd.org9a14c642019-10-31 21:23:19 +0000154 const char *sk_provider, const struct sshbuf *h_message,
155 const char *sig_namespace, struct sshbuf **out,
156 sshsig_signer *signer, void *signer_ctx)
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000157{
158 int r;
159 size_t slen = 0;
160 u_char *sig = NULL;
161 struct sshbuf *blob = NULL;
162 struct sshbuf *tosign = NULL;
163 const char *sign_alg = NULL;
164
165 if ((tosign = sshbuf_new()) == NULL ||
166 (blob = sshbuf_new()) == NULL) {
167 error("%s: sshbuf_new failed", __func__);
168 r = SSH_ERR_ALLOC_FAIL;
169 goto done;
170 }
171
172 if ((r = sshbuf_put(tosign, MAGIC_PREAMBLE, MAGIC_PREAMBLE_LEN)) != 0 ||
173 (r = sshbuf_put_cstring(tosign, sig_namespace)) != 0 ||
174 (r = sshbuf_put_string(tosign, NULL, 0)) != 0 || /* reserved */
175 (r = sshbuf_put_cstring(tosign, hashalg)) != 0 ||
djm@openbsd.orgb5a89ee2019-10-02 08:07:13 +0000176 (r = sshbuf_put_stringb(tosign, h_message)) != 0) {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000177 error("Couldn't construct message to sign: %s", ssh_err(r));
178 goto done;
179 }
180
181 /* If using RSA keys then default to a good signature algorithm */
182 if (sshkey_type_plain(key->type) == KEY_RSA)
183 sign_alg = RSA_SIGN_ALG;
184
185 if (signer != NULL) {
186 if ((r = signer(key, &sig, &slen,
187 sshbuf_ptr(tosign), sshbuf_len(tosign),
djm@openbsd.org9a14c642019-10-31 21:23:19 +0000188 sign_alg, sk_provider, 0, signer_ctx)) != 0) {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000189 error("Couldn't sign message: %s", ssh_err(r));
190 goto done;
191 }
192 } else {
193 if ((r = sshkey_sign(key, &sig, &slen,
194 sshbuf_ptr(tosign), sshbuf_len(tosign),
djm@openbsd.org9a14c642019-10-31 21:23:19 +0000195 sign_alg, sk_provider, 0)) != 0) {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000196 error("Couldn't sign message: %s", ssh_err(r));
197 goto done;
198 }
199 }
200
201 if ((r = sshbuf_put(blob, MAGIC_PREAMBLE, MAGIC_PREAMBLE_LEN)) != 0 ||
202 (r = sshbuf_put_u32(blob, SIG_VERSION)) != 0 ||
203 (r = sshkey_puts(key, blob)) != 0 ||
204 (r = sshbuf_put_cstring(blob, sig_namespace)) != 0 ||
205 (r = sshbuf_put_string(blob, NULL, 0)) != 0 || /* reserved */
206 (r = sshbuf_put_cstring(blob, hashalg)) != 0 ||
207 (r = sshbuf_put_string(blob, sig, slen)) != 0) {
208 error("Couldn't populate blob: %s", ssh_err(r));
209 goto done;
210 }
211
212 *out = blob;
213 blob = NULL;
214 r = 0;
215done:
216 free(sig);
217 sshbuf_free(blob);
218 sshbuf_free(tosign);
219 return r;
220}
221
222/* Check preamble and version. */
223static int
224sshsig_parse_preamble(struct sshbuf *buf)
225{
226 int r = SSH_ERR_INTERNAL_ERROR;
227 uint32_t sversion;
228
229 if ((r = sshbuf_cmp(buf, 0, MAGIC_PREAMBLE, MAGIC_PREAMBLE_LEN)) != 0 ||
230 (r = sshbuf_consume(buf, (sizeof(MAGIC_PREAMBLE)-1))) != 0 ||
231 (r = sshbuf_get_u32(buf, &sversion)) != 0) {
232 error("Couldn't verify signature: invalid format");
233 return r;
234 }
235
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000236 if (sversion > SIG_VERSION) {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000237 error("Signature version %lu is larger than supported "
238 "version %u", (unsigned long)sversion, SIG_VERSION);
239 return SSH_ERR_INVALID_FORMAT;
240 }
241 return 0;
242}
243
244static int
245sshsig_check_hashalg(const char *hashalg)
246{
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000247 if (hashalg == NULL ||
248 match_pattern_list(hashalg, HASHALG_ALLOWED, 0) == 1)
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000249 return 0;
250 error("%s: unsupported hash algorithm \"%.100s\"", __func__, hashalg);
251 return SSH_ERR_SIGN_ALG_UNSUPPORTED;
252}
253
254static int
255sshsig_peek_hashalg(struct sshbuf *signature, char **hashalgp)
256{
257 struct sshbuf *buf = NULL;
258 char *hashalg = NULL;
259 int r = SSH_ERR_INTERNAL_ERROR;
260
261 if (hashalgp != NULL)
262 *hashalgp = NULL;
263 if ((buf = sshbuf_fromb(signature)) == NULL)
264 return SSH_ERR_ALLOC_FAIL;
265 if ((r = sshsig_parse_preamble(buf)) != 0)
266 goto done;
267 if ((r = sshbuf_get_string_direct(buf, NULL, NULL)) != 0 ||
268 (r = sshbuf_get_string_direct(buf, NULL, NULL)) != 0 ||
269 (r = sshbuf_get_string(buf, NULL, NULL)) != 0 ||
270 (r = sshbuf_get_cstring(buf, &hashalg, NULL)) != 0 ||
271 (r = sshbuf_get_string_direct(buf, NULL, NULL)) != 0) {
272 error("Couldn't parse signature blob: %s", ssh_err(r));
273 goto done;
274 }
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000275
276 /* success */
277 r = 0;
278 *hashalgp = hashalg;
279 hashalg = NULL;
280 done:
281 free(hashalg);
282 sshbuf_free(buf);
283 return r;
284}
285
286static int
287sshsig_wrap_verify(struct sshbuf *signature, const char *hashalg,
288 const struct sshbuf *h_message, const char *expect_namespace,
289 struct sshkey **sign_keyp)
290{
291 int r = SSH_ERR_INTERNAL_ERROR;
292 struct sshbuf *buf = NULL, *toverify = NULL;
293 struct sshkey *key = NULL;
294 const u_char *sig;
295 char *got_namespace = NULL, *sigtype = NULL, *sig_hashalg = NULL;
296 size_t siglen;
297
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000298 debug("%s: verify message length %zu", __func__, sshbuf_len(h_message));
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000299 if (sign_keyp != NULL)
300 *sign_keyp = NULL;
301
302 if ((toverify = sshbuf_new()) == NULL) {
303 error("%s: sshbuf_new failed", __func__);
304 r = SSH_ERR_ALLOC_FAIL;
305 goto done;
306 }
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000307 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 ||
djm@openbsd.orgb5a89ee2019-10-02 08:07:13 +0000312 (r = sshbuf_put_stringb(toverify, h_message)) != 0) {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000313 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
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000385static int
386hash_buffer(const struct sshbuf *m, const char *hashalg, struct sshbuf **bp)
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000387{
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000388 char *hex, hash[SSH_DIGEST_MAX_LENGTH];
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000389 int alg, r = SSH_ERR_INTERNAL_ERROR;
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000390 struct sshbuf *b = NULL;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000391
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000392 *bp = NULL;
393 memset(hash, 0, sizeof(hash));
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000394
395 if ((r = sshsig_check_hashalg(hashalg)) != 0)
396 return r;
397 if ((alg = ssh_digest_alg_by_name(hashalg)) == -1) {
398 error("%s: can't look up hash algorithm %s",
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000399 __func__, hashalg);
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000400 return SSH_ERR_INTERNAL_ERROR;
401 }
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000402 if ((r = ssh_digest_buffer(alg, m, hash, sizeof(hash))) != 0) {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000403 error("%s: ssh_digest_buffer failed: %s", __func__, ssh_err(r));
404 return r;
405 }
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000406 if ((hex = tohex(hash, ssh_digest_bytes(alg))) != NULL) {
407 debug3("%s: final hash: %s", __func__, hex);
408 freezero(hex, strlen(hex));
409 }
410 if ((b = sshbuf_new()) == NULL) {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000411 r = SSH_ERR_ALLOC_FAIL;
412 goto out;
413 }
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000414 if ((r = sshbuf_put(b, hash, ssh_digest_bytes(alg))) != 0) {
415 error("%s: sshbuf_put: %s", __func__, ssh_err(r));
416 goto out;
417 }
418 *bp = b;
419 b = NULL; /* transferred */
420 /* success */
421 r = 0;
422 out:
423 sshbuf_free(b);
424 explicit_bzero(hash, sizeof(hash));
425 return 0;
426}
427
428int
djm@openbsd.org9a14c642019-10-31 21:23:19 +0000429sshsig_signb(struct sshkey *key, const char *hashalg, const char *sk_provider,
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000430 const struct sshbuf *message, const char *sig_namespace,
431 struct sshbuf **out, sshsig_signer *signer, void *signer_ctx)
432{
433 struct sshbuf *b = NULL;
434 int r = SSH_ERR_INTERNAL_ERROR;
435
436 if (hashalg == NULL)
437 hashalg = HASHALG_DEFAULT;
438 if (out != NULL)
439 *out = NULL;
440 if ((r = hash_buffer(message, hashalg, &b)) != 0) {
441 error("%s: hash_buffer failed: %s", __func__, ssh_err(r));
442 goto out;
443 }
djm@openbsd.org9a14c642019-10-31 21:23:19 +0000444 if ((r = sshsig_wrap_sign(key, hashalg, sk_provider, b,
445 sig_namespace, out, signer, signer_ctx)) != 0)
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000446 goto out;
447 /* success */
448 r = 0;
449 out:
450 sshbuf_free(b);
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000451 return r;
452}
453
454int
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000455sshsig_verifyb(struct sshbuf *signature, const struct sshbuf *message,
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000456 const char *expect_namespace, struct sshkey **sign_keyp)
457{
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000458 struct sshbuf *b = NULL;
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000459 int r = SSH_ERR_INTERNAL_ERROR;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000460 char *hashalg = NULL;
461
462 if (sign_keyp != NULL)
463 *sign_keyp = NULL;
464
465 if ((r = sshsig_peek_hashalg(signature, &hashalg)) != 0)
466 return r;
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000467 debug("%s: signature made with hash \"%s\"", __func__, hashalg);
468 if ((r = hash_buffer(message, hashalg, &b)) != 0) {
469 error("%s: hash_buffer failed: %s", __func__, ssh_err(r));
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000470 goto out;
471 }
472 if ((r = sshsig_wrap_verify(signature, hashalg, b, expect_namespace,
473 sign_keyp)) != 0)
474 goto out;
475 /* success */
476 r = 0;
477 out:
478 sshbuf_free(b);
479 free(hashalg);
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000480 return r;
481}
482
483static int
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000484hash_file(int fd, const char *hashalg, struct sshbuf **bp)
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000485{
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000486 char *hex, rbuf[8192], hash[SSH_DIGEST_MAX_LENGTH];
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000487 ssize_t n, total = 0;
488 struct ssh_digest_ctx *ctx;
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000489 int alg, oerrno, r = SSH_ERR_INTERNAL_ERROR;
490 struct sshbuf *b = NULL;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000491
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000492 *bp = NULL;
493 memset(hash, 0, sizeof(hash));
494
495 if ((r = sshsig_check_hashalg(hashalg)) != 0)
496 return r;
497 if ((alg = ssh_digest_alg_by_name(hashalg)) == -1) {
498 error("%s: can't look up hash algorithm %s",
499 __func__, hashalg);
500 return SSH_ERR_INTERNAL_ERROR;
501 }
502 if ((ctx = ssh_digest_start(alg)) == NULL) {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000503 error("%s: ssh_digest_start failed", __func__);
504 return SSH_ERR_INTERNAL_ERROR;
505 }
506 for (;;) {
507 if ((n = read(fd, rbuf, sizeof(rbuf))) == -1) {
508 if (errno == EINTR || errno == EAGAIN)
509 continue;
510 oerrno = errno;
511 error("%s: read: %s", __func__, strerror(errno));
512 ssh_digest_free(ctx);
513 errno = oerrno;
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000514 r = SSH_ERR_SYSTEM_ERROR;
515 goto out;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000516 } else if (n == 0) {
517 debug2("%s: hashed %zu bytes", __func__, total);
518 break; /* EOF */
519 }
520 total += (size_t)n;
521 if ((r = ssh_digest_update(ctx, rbuf, (size_t)n)) != 0) {
522 error("%s: ssh_digest_update: %s",
523 __func__, ssh_err(r));
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000524 goto out;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000525 }
526 }
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000527 if ((r = ssh_digest_final(ctx, hash, sizeof(hash))) != 0) {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000528 error("%s: ssh_digest_final: %s", __func__, ssh_err(r));
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000529 goto out;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000530 }
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000531 if ((hex = tohex(hash, ssh_digest_bytes(alg))) != NULL) {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000532 debug3("%s: final hash: %s", __func__, hex);
533 freezero(hex, strlen(hex));
534 }
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000535 if ((b = sshbuf_new()) == NULL) {
536 r = SSH_ERR_ALLOC_FAIL;
537 goto out;
538 }
539 if ((r = sshbuf_put(b, hash, ssh_digest_bytes(alg))) != 0) {
540 error("%s: sshbuf_put: %s", __func__, ssh_err(r));
541 goto out;
542 }
543 *bp = b;
544 b = NULL; /* transferred */
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000545 /* success */
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000546 r = 0;
547 out:
548 sshbuf_free(b);
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000549 ssh_digest_free(ctx);
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000550 explicit_bzero(hash, sizeof(hash));
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000551 return 0;
552}
553
554int
djm@openbsd.org9a14c642019-10-31 21:23:19 +0000555sshsig_sign_fd(struct sshkey *key, const char *hashalg, const char *sk_provider,
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000556 int fd, const char *sig_namespace, struct sshbuf **out,
557 sshsig_signer *signer, void *signer_ctx)
558{
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000559 struct sshbuf *b = NULL;
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000560 int r = SSH_ERR_INTERNAL_ERROR;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000561
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000562 if (hashalg == NULL)
563 hashalg = HASHALG_DEFAULT;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000564 if (out != NULL)
565 *out = NULL;
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000566 if ((r = hash_file(fd, hashalg, &b)) != 0) {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000567 error("%s: hash_file failed: %s", __func__, ssh_err(r));
568 return r;
569 }
djm@openbsd.org9a14c642019-10-31 21:23:19 +0000570 if ((r = sshsig_wrap_sign(key, hashalg, sk_provider, b,
571 sig_namespace, out, signer, signer_ctx)) != 0)
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000572 goto out;
573 /* success */
574 r = 0;
575 out:
576 sshbuf_free(b);
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000577 return r;
578}
579
580int
581sshsig_verify_fd(struct sshbuf *signature, int fd,
582 const char *expect_namespace, struct sshkey **sign_keyp)
583{
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000584 struct sshbuf *b = NULL;
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000585 int r = SSH_ERR_INTERNAL_ERROR;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000586 char *hashalg = NULL;
587
588 if (sign_keyp != NULL)
589 *sign_keyp = NULL;
590
591 if ((r = sshsig_peek_hashalg(signature, &hashalg)) != 0)
592 return r;
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000593 debug("%s: signature made with hash \"%s\"", __func__, hashalg);
594 if ((r = hash_file(fd, hashalg, &b)) != 0) {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000595 error("%s: hash_file failed: %s", __func__, ssh_err(r));
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000596 goto out;
597 }
598 if ((r = sshsig_wrap_verify(signature, hashalg, b, expect_namespace,
599 sign_keyp)) != 0)
600 goto out;
601 /* success */
602 r = 0;
603 out:
604 sshbuf_free(b);
605 free(hashalg);
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000606 return r;
607}
608
djm@openbsd.orgbab6feb2019-09-05 04:55:32 +0000609struct sshsigopt {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000610 int ca;
611 char *namespaces;
612};
613
djm@openbsd.orgbab6feb2019-09-05 04:55:32 +0000614struct sshsigopt *
615sshsigopt_parse(const char *opts, const char *path, u_long linenum,
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000616 const char **errstrp)
617{
djm@openbsd.orgbab6feb2019-09-05 04:55:32 +0000618 struct sshsigopt *ret;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000619 int r;
620 const char *errstr = NULL;
621
622 if ((ret = calloc(1, sizeof(*ret))) == NULL)
623 return NULL;
624 if (opts == NULL || *opts == '\0')
625 return ret; /* Empty options yields empty options :) */
626
627 while (*opts && *opts != ' ' && *opts != '\t') {
628 /* flag options */
629 if ((r = opt_flag("cert-authority", 0, &opts)) != -1) {
630 ret->ca = 1;
631 } else if (opt_match(&opts, "namespaces")) {
632 if (ret->namespaces != NULL) {
633 errstr = "multiple \"namespaces\" clauses";
634 goto fail;
635 }
636 ret->namespaces = opt_dequote(&opts, &errstr);
637 if (ret->namespaces == NULL)
638 goto fail;
639 }
640 /*
641 * Skip the comma, and move to the next option
642 * (or break out if there are no more).
643 */
644 if (*opts == '\0' || *opts == ' ' || *opts == '\t')
645 break; /* End of options. */
646 /* Anything other than a comma is an unknown option */
647 if (*opts != ',') {
648 errstr = "unknown key option";
649 goto fail;
650 }
651 opts++;
652 if (*opts == '\0') {
653 errstr = "unexpected end-of-options";
654 goto fail;
655 }
656 }
657 /* success */
658 return ret;
659 fail:
660 if (errstrp != NULL)
661 *errstrp = errstr;
djm@openbsd.org69159af2019-09-05 05:42:59 +0000662 sshsigopt_free(ret);
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000663 return NULL;
664}
665
djm@openbsd.orgbab6feb2019-09-05 04:55:32 +0000666void
667sshsigopt_free(struct sshsigopt *opts)
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000668{
669 if (opts == NULL)
670 return;
671 free(opts->namespaces);
672 free(opts);
673}
674
675static int
676check_allowed_keys_line(const char *path, u_long linenum, char *line,
677 const struct sshkey *sign_key, const char *principal,
678 const char *sig_namespace)
679{
680 struct sshkey *found_key = NULL;
681 char *cp, *opts = NULL, *identities = NULL;
682 int r, found = 0;
683 const char *reason = NULL;
djm@openbsd.orgbab6feb2019-09-05 04:55:32 +0000684 struct sshsigopt *sigopts = NULL;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000685
686 if ((found_key = sshkey_new(KEY_UNSPEC)) == NULL) {
687 error("%s: sshkey_new failed", __func__);
688 return SSH_ERR_ALLOC_FAIL;
689 }
690
691 /* format: identity[,identity...] [option[,option...]] key */
692 cp = line;
693 cp = cp + strspn(cp, " \t"); /* skip leading whitespace */
694 if (*cp == '#' || *cp == '\0')
695 goto done;
696 if ((identities = strdelimw(&cp)) == NULL) {
697 error("%s:%lu: invalid line", path, linenum);
698 goto done;
699 }
700 if (match_pattern_list(principal, identities, 0) != 1) {
701 /* principal didn't match */
702 goto done;
703 }
704 debug("%s: %s:%lu: matched principal \"%s\"",
705 __func__, path, linenum, principal);
706
707 if (sshkey_read(found_key, &cp) != 0) {
708 /* no key? Check for options */
709 opts = cp;
710 if (sshkey_advance_past_options(&cp) != 0) {
711 error("%s:%lu: invalid options",
712 path, linenum);
713 goto done;
714 }
715 *cp++ = '\0';
716 skip_space(&cp);
717 if (sshkey_read(found_key, &cp) != 0) {
718 error("%s:%lu: invalid key", path,
719 linenum);
720 goto done;
721 }
722 }
723 debug3("%s:%lu: options %s", path, linenum, opts == NULL ? "" : opts);
djm@openbsd.orgbab6feb2019-09-05 04:55:32 +0000724 if ((sigopts = sshsigopt_parse(opts, path, linenum, &reason)) == NULL) {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000725 error("%s:%lu: bad options: %s", path, linenum, reason);
726 goto done;
727 }
728
729 /* Check whether options preclude the use of this key */
730 if (sigopts->namespaces != NULL &&
731 match_pattern_list(sig_namespace, sigopts->namespaces, 0) != 1) {
732 error("%s:%lu: key is not permitted for use in signature "
733 "namespace \"%s\"", path, linenum, sig_namespace);
734 goto done;
735 }
736
737 if (!sigopts->ca && sshkey_equal(found_key, sign_key)) {
738 /* Exact match of key */
739 debug("%s:%lu: matched key and principal", path, linenum);
740 /* success */
741 found = 1;
742 } else if (sigopts->ca && sshkey_is_cert(sign_key) &&
743 sshkey_equal_public(sign_key->cert->signature_key, found_key)) {
744 /* Match of certificate's CA key */
745 if ((r = sshkey_cert_check_authority(sign_key, 0, 1,
746 principal, &reason)) != 0) {
747 error("%s:%lu: certificate not authorized: %s",
748 path, linenum, reason);
749 goto done;
750 }
751 debug("%s:%lu: matched certificate CA key", path, linenum);
752 /* success */
753 found = 1;
754 } else {
755 /* Principal matched but key didn't */
756 goto done;
757 }
758 done:
759 sshkey_free(found_key);
djm@openbsd.orgbab6feb2019-09-05 04:55:32 +0000760 sshsigopt_free(sigopts);
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000761 return found ? 0 : SSH_ERR_KEY_NOT_FOUND;
762}
763
764int
765sshsig_check_allowed_keys(const char *path, const struct sshkey *sign_key,
766 const char *principal, const char *sig_namespace)
767{
768 FILE *f = NULL;
769 char *line = NULL;
770 size_t linesize = 0;
771 u_long linenum = 0;
772 int r, oerrno;
773
774 /* Check key and principal against file */
775 if ((f = fopen(path, "r")) == NULL) {
776 oerrno = errno;
777 error("Unable to open allowed keys file \"%s\": %s",
778 path, strerror(errno));
779 errno = oerrno;
780 return SSH_ERR_SYSTEM_ERROR;
781 }
782
783 while (getline(&line, &linesize, f) != -1) {
784 linenum++;
785 r = check_allowed_keys_line(path, linenum, line, sign_key,
786 principal, sig_namespace);
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000787 free(line);
788 line = NULL;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000789 if (r == SSH_ERR_KEY_NOT_FOUND)
790 continue;
791 else if (r == 0) {
792 /* success */
793 fclose(f);
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000794 return 0;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000795 } else
796 break;
797 }
798 /* Either we hit an error parsing or we simply didn't find the key */
799 fclose(f);
800 free(line);
801 return r == 0 ? SSH_ERR_KEY_NOT_FOUND : r;
802}