blob: b993b7c1ead48c11c5c43e429c553325e71cd266 [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,
154 const struct sshbuf *h_message, const char *sig_namespace,
155 struct sshbuf **out, sshsig_signer *signer, void *signer_ctx)
156{
157 int r;
158 size_t slen = 0;
159 u_char *sig = NULL;
160 struct sshbuf *blob = NULL;
161 struct sshbuf *tosign = NULL;
162 const char *sign_alg = NULL;
163
164 if ((tosign = sshbuf_new()) == NULL ||
165 (blob = sshbuf_new()) == NULL) {
166 error("%s: sshbuf_new failed", __func__);
167 r = SSH_ERR_ALLOC_FAIL;
168 goto done;
169 }
170
171 if ((r = sshbuf_put(tosign, MAGIC_PREAMBLE, MAGIC_PREAMBLE_LEN)) != 0 ||
172 (r = sshbuf_put_cstring(tosign, sig_namespace)) != 0 ||
173 (r = sshbuf_put_string(tosign, NULL, 0)) != 0 || /* reserved */
174 (r = sshbuf_put_cstring(tosign, hashalg)) != 0 ||
175 (r = sshbuf_putb(tosign, h_message)) != 0) {
176 error("Couldn't construct message to sign: %s", ssh_err(r));
177 goto done;
178 }
179
180 /* If using RSA keys then default to a good signature algorithm */
181 if (sshkey_type_plain(key->type) == KEY_RSA)
182 sign_alg = RSA_SIGN_ALG;
183
184 if (signer != NULL) {
185 if ((r = signer(key, &sig, &slen,
186 sshbuf_ptr(tosign), sshbuf_len(tosign),
187 sign_alg, 0, signer_ctx)) != 0) {
188 error("Couldn't sign message: %s", ssh_err(r));
189 goto done;
190 }
191 } else {
192 if ((r = sshkey_sign(key, &sig, &slen,
193 sshbuf_ptr(tosign), sshbuf_len(tosign),
194 sign_alg, 0)) != 0) {
195 error("Couldn't sign message: %s", ssh_err(r));
196 goto done;
197 }
198 }
199
200 if ((r = sshbuf_put(blob, MAGIC_PREAMBLE, MAGIC_PREAMBLE_LEN)) != 0 ||
201 (r = sshbuf_put_u32(blob, SIG_VERSION)) != 0 ||
202 (r = sshkey_puts(key, blob)) != 0 ||
203 (r = sshbuf_put_cstring(blob, sig_namespace)) != 0 ||
204 (r = sshbuf_put_string(blob, NULL, 0)) != 0 || /* reserved */
205 (r = sshbuf_put_cstring(blob, hashalg)) != 0 ||
206 (r = sshbuf_put_string(blob, sig, slen)) != 0) {
207 error("Couldn't populate blob: %s", ssh_err(r));
208 goto done;
209 }
210
211 *out = blob;
212 blob = NULL;
213 r = 0;
214done:
215 free(sig);
216 sshbuf_free(blob);
217 sshbuf_free(tosign);
218 return r;
219}
220
221/* Check preamble and version. */
222static int
223sshsig_parse_preamble(struct sshbuf *buf)
224{
225 int r = SSH_ERR_INTERNAL_ERROR;
226 uint32_t sversion;
227
228 if ((r = sshbuf_cmp(buf, 0, MAGIC_PREAMBLE, MAGIC_PREAMBLE_LEN)) != 0 ||
229 (r = sshbuf_consume(buf, (sizeof(MAGIC_PREAMBLE)-1))) != 0 ||
230 (r = sshbuf_get_u32(buf, &sversion)) != 0) {
231 error("Couldn't verify signature: invalid format");
232 return r;
233 }
234
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000235 if (sversion > SIG_VERSION) {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000236 error("Signature version %lu is larger than supported "
237 "version %u", (unsigned long)sversion, SIG_VERSION);
238 return SSH_ERR_INVALID_FORMAT;
239 }
240 return 0;
241}
242
243static int
244sshsig_check_hashalg(const char *hashalg)
245{
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000246 if (hashalg == NULL ||
247 match_pattern_list(hashalg, HASHALG_ALLOWED, 0) == 1)
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000248 return 0;
249 error("%s: unsupported hash algorithm \"%.100s\"", __func__, hashalg);
250 return SSH_ERR_SIGN_ALG_UNSUPPORTED;
251}
252
253static int
254sshsig_peek_hashalg(struct sshbuf *signature, char **hashalgp)
255{
256 struct sshbuf *buf = NULL;
257 char *hashalg = NULL;
258 int r = SSH_ERR_INTERNAL_ERROR;
259
260 if (hashalgp != NULL)
261 *hashalgp = NULL;
262 if ((buf = sshbuf_fromb(signature)) == NULL)
263 return SSH_ERR_ALLOC_FAIL;
264 if ((r = sshsig_parse_preamble(buf)) != 0)
265 goto done;
266 if ((r = sshbuf_get_string_direct(buf, NULL, NULL)) != 0 ||
267 (r = sshbuf_get_string_direct(buf, NULL, NULL)) != 0 ||
268 (r = sshbuf_get_string(buf, NULL, NULL)) != 0 ||
269 (r = sshbuf_get_cstring(buf, &hashalg, NULL)) != 0 ||
270 (r = sshbuf_get_string_direct(buf, NULL, NULL)) != 0) {
271 error("Couldn't parse signature blob: %s", ssh_err(r));
272 goto done;
273 }
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000274
275 /* success */
276 r = 0;
277 *hashalgp = hashalg;
278 hashalg = NULL;
279 done:
280 free(hashalg);
281 sshbuf_free(buf);
282 return r;
283}
284
285static int
286sshsig_wrap_verify(struct sshbuf *signature, const char *hashalg,
287 const struct sshbuf *h_message, const char *expect_namespace,
288 struct sshkey **sign_keyp)
289{
290 int r = SSH_ERR_INTERNAL_ERROR;
291 struct sshbuf *buf = NULL, *toverify = NULL;
292 struct sshkey *key = NULL;
293 const u_char *sig;
294 char *got_namespace = NULL, *sigtype = NULL, *sig_hashalg = NULL;
295 size_t siglen;
296
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000297 debug("%s: verify message length %zu", __func__, sshbuf_len(h_message));
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000298 if (sign_keyp != NULL)
299 *sign_keyp = NULL;
300
301 if ((toverify = sshbuf_new()) == NULL) {
302 error("%s: sshbuf_new failed", __func__);
303 r = SSH_ERR_ALLOC_FAIL;
304 goto done;
305 }
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000306 if ((r = sshbuf_put(toverify, MAGIC_PREAMBLE,
307 MAGIC_PREAMBLE_LEN)) != 0 ||
308 (r = sshbuf_put_cstring(toverify, expect_namespace)) != 0 ||
309 (r = sshbuf_put_string(toverify, NULL, 0)) != 0 || /* reserved */
310 (r = sshbuf_put_cstring(toverify, hashalg)) != 0 ||
311 (r = sshbuf_putb(toverify, h_message)) != 0) {
312 error("Couldn't construct message to verify: %s", ssh_err(r));
313 goto done;
314 }
315
316 if ((r = sshsig_parse_preamble(signature)) != 0)
317 goto done;
318
319 if ((r = sshkey_froms(signature, &key)) != 0 ||
320 (r = sshbuf_get_cstring(signature, &got_namespace, NULL)) != 0 ||
321 (r = sshbuf_get_string(signature, NULL, NULL)) != 0 ||
322 (r = sshbuf_get_cstring(signature, &sig_hashalg, NULL)) != 0 ||
323 (r = sshbuf_get_string_direct(signature, &sig, &siglen)) != 0) {
324 error("Couldn't parse signature blob: %s", ssh_err(r));
325 goto done;
326 }
327
328 if (sshbuf_len(signature) != 0) {
329 error("Signature contains trailing data");
330 r = SSH_ERR_INVALID_FORMAT;
331 goto done;
332 }
333
334 if (strcmp(expect_namespace, got_namespace) != 0) {
335 error("Couldn't verify signature: namespace does not match");
336 debug("%s: expected namespace \"%s\" received \"%s\"",
337 __func__, expect_namespace, got_namespace);
338 r = SSH_ERR_SIGNATURE_INVALID;
339 goto done;
340 }
341 if (strcmp(hashalg, sig_hashalg) != 0) {
342 error("Couldn't verify signature: hash algorithm mismatch");
343 debug("%s: expected algorithm \"%s\" received \"%s\"",
344 __func__, hashalg, sig_hashalg);
345 r = SSH_ERR_SIGNATURE_INVALID;
346 goto done;
347 }
348 /* Ensure that RSA keys use an acceptable signature algorithm */
349 if (sshkey_type_plain(key->type) == KEY_RSA) {
350 if ((r = sshkey_get_sigtype(sig, siglen, &sigtype)) != 0) {
351 error("Couldn't verify signature: unable to get "
352 "signature type: %s", ssh_err(r));
353 goto done;
354 }
355 if (match_pattern_list(sigtype, RSA_SIGN_ALLOWED, 0) != 1) {
356 error("Couldn't verify signature: unsupported RSA "
357 "signature algorithm %s", sigtype);
358 r = SSH_ERR_SIGN_ALG_UNSUPPORTED;
359 goto done;
360 }
361 }
362 if ((r = sshkey_verify(key, sig, siglen, sshbuf_ptr(toverify),
363 sshbuf_len(toverify), NULL, 0)) != 0) {
364 error("Signature verification failed: %s", ssh_err(r));
365 goto done;
366 }
367
368 /* success */
369 r = 0;
370 if (sign_keyp != NULL) {
371 *sign_keyp = key;
372 key = NULL; /* transferred */
373 }
374done:
375 free(got_namespace);
376 free(sigtype);
377 free(sig_hashalg);
378 sshbuf_free(buf);
379 sshbuf_free(toverify);
380 sshkey_free(key);
381 return r;
382}
383
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000384static int
385hash_buffer(const struct sshbuf *m, const char *hashalg, struct sshbuf **bp)
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000386{
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000387 char *hex, hash[SSH_DIGEST_MAX_LENGTH];
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000388 int alg, r = SSH_ERR_INTERNAL_ERROR;
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000389 struct sshbuf *b = NULL;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000390
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000391 *bp = NULL;
392 memset(hash, 0, sizeof(hash));
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000393
394 if ((r = sshsig_check_hashalg(hashalg)) != 0)
395 return r;
396 if ((alg = ssh_digest_alg_by_name(hashalg)) == -1) {
397 error("%s: can't look up hash algorithm %s",
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000398 __func__, hashalg);
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000399 return SSH_ERR_INTERNAL_ERROR;
400 }
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000401 if ((r = ssh_digest_buffer(alg, m, hash, sizeof(hash))) != 0) {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000402 error("%s: ssh_digest_buffer failed: %s", __func__, ssh_err(r));
403 return r;
404 }
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000405 if ((hex = tohex(hash, ssh_digest_bytes(alg))) != NULL) {
406 debug3("%s: final hash: %s", __func__, hex);
407 freezero(hex, strlen(hex));
408 }
409 if ((b = sshbuf_new()) == NULL) {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000410 r = SSH_ERR_ALLOC_FAIL;
411 goto out;
412 }
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000413 if ((r = sshbuf_put(b, hash, ssh_digest_bytes(alg))) != 0) {
414 error("%s: sshbuf_put: %s", __func__, ssh_err(r));
415 goto out;
416 }
417 *bp = b;
418 b = NULL; /* transferred */
419 /* success */
420 r = 0;
421 out:
422 sshbuf_free(b);
423 explicit_bzero(hash, sizeof(hash));
424 return 0;
425}
426
427int
428sshsig_signb(struct sshkey *key, const char *hashalg,
429 const struct sshbuf *message, const char *sig_namespace,
430 struct sshbuf **out, sshsig_signer *signer, void *signer_ctx)
431{
432 struct sshbuf *b = NULL;
433 int r = SSH_ERR_INTERNAL_ERROR;
434
435 if (hashalg == NULL)
436 hashalg = HASHALG_DEFAULT;
437 if (out != NULL)
438 *out = NULL;
439 if ((r = hash_buffer(message, hashalg, &b)) != 0) {
440 error("%s: hash_buffer failed: %s", __func__, ssh_err(r));
441 goto out;
442 }
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000443 if ((r = sshsig_wrap_sign(key, hashalg, b, sig_namespace, out,
444 signer, signer_ctx)) != 0)
445 goto out;
446 /* success */
447 r = 0;
448 out:
449 sshbuf_free(b);
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000450 return r;
451}
452
453int
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000454sshsig_verifyb(struct sshbuf *signature, const struct sshbuf *message,
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000455 const char *expect_namespace, struct sshkey **sign_keyp)
456{
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000457 struct sshbuf *b = NULL;
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000458 int r = SSH_ERR_INTERNAL_ERROR;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000459 char *hashalg = NULL;
460
461 if (sign_keyp != NULL)
462 *sign_keyp = NULL;
463
464 if ((r = sshsig_peek_hashalg(signature, &hashalg)) != 0)
465 return r;
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000466 debug("%s: signature made with hash \"%s\"", __func__, hashalg);
467 if ((r = hash_buffer(message, hashalg, &b)) != 0) {
468 error("%s: hash_buffer failed: %s", __func__, ssh_err(r));
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000469 goto out;
470 }
471 if ((r = sshsig_wrap_verify(signature, hashalg, b, expect_namespace,
472 sign_keyp)) != 0)
473 goto out;
474 /* success */
475 r = 0;
476 out:
477 sshbuf_free(b);
478 free(hashalg);
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000479 return r;
480}
481
482static int
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000483hash_file(int fd, const char *hashalg, struct sshbuf **bp)
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000484{
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000485 char *hex, rbuf[8192], hash[SSH_DIGEST_MAX_LENGTH];
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000486 ssize_t n, total = 0;
487 struct ssh_digest_ctx *ctx;
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000488 int alg, oerrno, r = SSH_ERR_INTERNAL_ERROR;
489 struct sshbuf *b = NULL;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000490
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000491 *bp = NULL;
492 memset(hash, 0, sizeof(hash));
493
494 if ((r = sshsig_check_hashalg(hashalg)) != 0)
495 return r;
496 if ((alg = ssh_digest_alg_by_name(hashalg)) == -1) {
497 error("%s: can't look up hash algorithm %s",
498 __func__, hashalg);
499 return SSH_ERR_INTERNAL_ERROR;
500 }
501 if ((ctx = ssh_digest_start(alg)) == NULL) {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000502 error("%s: ssh_digest_start failed", __func__);
503 return SSH_ERR_INTERNAL_ERROR;
504 }
505 for (;;) {
506 if ((n = read(fd, rbuf, sizeof(rbuf))) == -1) {
507 if (errno == EINTR || errno == EAGAIN)
508 continue;
509 oerrno = errno;
510 error("%s: read: %s", __func__, strerror(errno));
511 ssh_digest_free(ctx);
512 errno = oerrno;
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000513 r = SSH_ERR_SYSTEM_ERROR;
514 goto out;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000515 } else if (n == 0) {
516 debug2("%s: hashed %zu bytes", __func__, total);
517 break; /* EOF */
518 }
519 total += (size_t)n;
520 if ((r = ssh_digest_update(ctx, rbuf, (size_t)n)) != 0) {
521 error("%s: ssh_digest_update: %s",
522 __func__, ssh_err(r));
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000523 goto out;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000524 }
525 }
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000526 if ((r = ssh_digest_final(ctx, hash, sizeof(hash))) != 0) {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000527 error("%s: ssh_digest_final: %s", __func__, ssh_err(r));
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000528 goto out;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000529 }
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000530 if ((hex = tohex(hash, ssh_digest_bytes(alg))) != NULL) {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000531 debug3("%s: final hash: %s", __func__, hex);
532 freezero(hex, strlen(hex));
533 }
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000534 if ((b = sshbuf_new()) == NULL) {
535 r = SSH_ERR_ALLOC_FAIL;
536 goto out;
537 }
538 if ((r = sshbuf_put(b, hash, ssh_digest_bytes(alg))) != 0) {
539 error("%s: sshbuf_put: %s", __func__, ssh_err(r));
540 goto out;
541 }
542 *bp = b;
543 b = NULL; /* transferred */
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000544 /* success */
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000545 r = 0;
546 out:
547 sshbuf_free(b);
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000548 ssh_digest_free(ctx);
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000549 explicit_bzero(hash, sizeof(hash));
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000550 return 0;
551}
552
553int
554sshsig_sign_fd(struct sshkey *key, const char *hashalg,
555 int fd, const char *sig_namespace, struct sshbuf **out,
556 sshsig_signer *signer, void *signer_ctx)
557{
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000558 struct sshbuf *b = NULL;
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000559 int r = SSH_ERR_INTERNAL_ERROR;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000560
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000561 if (hashalg == NULL)
562 hashalg = HASHALG_DEFAULT;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000563 if (out != NULL)
564 *out = NULL;
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000565 if ((r = hash_file(fd, hashalg, &b)) != 0) {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000566 error("%s: hash_file failed: %s", __func__, ssh_err(r));
567 return r;
568 }
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000569 if ((r = sshsig_wrap_sign(key, hashalg, b, sig_namespace, out,
570 signer, signer_ctx)) != 0)
571 goto out;
572 /* success */
573 r = 0;
574 out:
575 sshbuf_free(b);
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000576 return r;
577}
578
579int
580sshsig_verify_fd(struct sshbuf *signature, int fd,
581 const char *expect_namespace, struct sshkey **sign_keyp)
582{
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000583 struct sshbuf *b = NULL;
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000584 int r = SSH_ERR_INTERNAL_ERROR;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000585 char *hashalg = NULL;
586
587 if (sign_keyp != NULL)
588 *sign_keyp = NULL;
589
590 if ((r = sshsig_peek_hashalg(signature, &hashalg)) != 0)
591 return r;
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000592 debug("%s: signature made with hash \"%s\"", __func__, hashalg);
593 if ((r = hash_file(fd, hashalg, &b)) != 0) {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000594 error("%s: hash_file failed: %s", __func__, ssh_err(r));
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000595 goto out;
596 }
597 if ((r = sshsig_wrap_verify(signature, hashalg, b, expect_namespace,
598 sign_keyp)) != 0)
599 goto out;
600 /* success */
601 r = 0;
602 out:
603 sshbuf_free(b);
604 free(hashalg);
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000605 return r;
606}
607
djm@openbsd.orgbab6feb2019-09-05 04:55:32 +0000608struct sshsigopt {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000609 int ca;
610 char *namespaces;
611};
612
djm@openbsd.orgbab6feb2019-09-05 04:55:32 +0000613struct sshsigopt *
614sshsigopt_parse(const char *opts, const char *path, u_long linenum,
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000615 const char **errstrp)
616{
djm@openbsd.orgbab6feb2019-09-05 04:55:32 +0000617 struct sshsigopt *ret;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000618 int r;
619 const char *errstr = NULL;
620
621 if ((ret = calloc(1, sizeof(*ret))) == NULL)
622 return NULL;
623 if (opts == NULL || *opts == '\0')
624 return ret; /* Empty options yields empty options :) */
625
626 while (*opts && *opts != ' ' && *opts != '\t') {
627 /* flag options */
628 if ((r = opt_flag("cert-authority", 0, &opts)) != -1) {
629 ret->ca = 1;
630 } else if (opt_match(&opts, "namespaces")) {
631 if (ret->namespaces != NULL) {
632 errstr = "multiple \"namespaces\" clauses";
633 goto fail;
634 }
635 ret->namespaces = opt_dequote(&opts, &errstr);
636 if (ret->namespaces == NULL)
637 goto fail;
638 }
639 /*
640 * Skip the comma, and move to the next option
641 * (or break out if there are no more).
642 */
643 if (*opts == '\0' || *opts == ' ' || *opts == '\t')
644 break; /* End of options. */
645 /* Anything other than a comma is an unknown option */
646 if (*opts != ',') {
647 errstr = "unknown key option";
648 goto fail;
649 }
650 opts++;
651 if (*opts == '\0') {
652 errstr = "unexpected end-of-options";
653 goto fail;
654 }
655 }
656 /* success */
657 return ret;
658 fail:
659 if (errstrp != NULL)
660 *errstrp = errstr;
djm@openbsd.org69159af2019-09-05 05:42:59 +0000661 sshsigopt_free(ret);
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000662 return NULL;
663}
664
djm@openbsd.orgbab6feb2019-09-05 04:55:32 +0000665void
666sshsigopt_free(struct sshsigopt *opts)
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000667{
668 if (opts == NULL)
669 return;
670 free(opts->namespaces);
671 free(opts);
672}
673
674static int
675check_allowed_keys_line(const char *path, u_long linenum, char *line,
676 const struct sshkey *sign_key, const char *principal,
677 const char *sig_namespace)
678{
679 struct sshkey *found_key = NULL;
680 char *cp, *opts = NULL, *identities = NULL;
681 int r, found = 0;
682 const char *reason = NULL;
djm@openbsd.orgbab6feb2019-09-05 04:55:32 +0000683 struct sshsigopt *sigopts = NULL;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000684
685 if ((found_key = sshkey_new(KEY_UNSPEC)) == NULL) {
686 error("%s: sshkey_new failed", __func__);
687 return SSH_ERR_ALLOC_FAIL;
688 }
689
690 /* format: identity[,identity...] [option[,option...]] key */
691 cp = line;
692 cp = cp + strspn(cp, " \t"); /* skip leading whitespace */
693 if (*cp == '#' || *cp == '\0')
694 goto done;
695 if ((identities = strdelimw(&cp)) == NULL) {
696 error("%s:%lu: invalid line", path, linenum);
697 goto done;
698 }
699 if (match_pattern_list(principal, identities, 0) != 1) {
700 /* principal didn't match */
701 goto done;
702 }
703 debug("%s: %s:%lu: matched principal \"%s\"",
704 __func__, path, linenum, principal);
705
706 if (sshkey_read(found_key, &cp) != 0) {
707 /* no key? Check for options */
708 opts = cp;
709 if (sshkey_advance_past_options(&cp) != 0) {
710 error("%s:%lu: invalid options",
711 path, linenum);
712 goto done;
713 }
714 *cp++ = '\0';
715 skip_space(&cp);
716 if (sshkey_read(found_key, &cp) != 0) {
717 error("%s:%lu: invalid key", path,
718 linenum);
719 goto done;
720 }
721 }
722 debug3("%s:%lu: options %s", path, linenum, opts == NULL ? "" : opts);
djm@openbsd.orgbab6feb2019-09-05 04:55:32 +0000723 if ((sigopts = sshsigopt_parse(opts, path, linenum, &reason)) == NULL) {
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000724 error("%s:%lu: bad options: %s", path, linenum, reason);
725 goto done;
726 }
727
728 /* Check whether options preclude the use of this key */
729 if (sigopts->namespaces != NULL &&
730 match_pattern_list(sig_namespace, sigopts->namespaces, 0) != 1) {
731 error("%s:%lu: key is not permitted for use in signature "
732 "namespace \"%s\"", path, linenum, sig_namespace);
733 goto done;
734 }
735
736 if (!sigopts->ca && sshkey_equal(found_key, sign_key)) {
737 /* Exact match of key */
738 debug("%s:%lu: matched key and principal", path, linenum);
739 /* success */
740 found = 1;
741 } else if (sigopts->ca && sshkey_is_cert(sign_key) &&
742 sshkey_equal_public(sign_key->cert->signature_key, found_key)) {
743 /* Match of certificate's CA key */
744 if ((r = sshkey_cert_check_authority(sign_key, 0, 1,
745 principal, &reason)) != 0) {
746 error("%s:%lu: certificate not authorized: %s",
747 path, linenum, reason);
748 goto done;
749 }
750 debug("%s:%lu: matched certificate CA key", path, linenum);
751 /* success */
752 found = 1;
753 } else {
754 /* Principal matched but key didn't */
755 goto done;
756 }
757 done:
758 sshkey_free(found_key);
djm@openbsd.orgbab6feb2019-09-05 04:55:32 +0000759 sshsigopt_free(sigopts);
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000760 return found ? 0 : SSH_ERR_KEY_NOT_FOUND;
761}
762
763int
764sshsig_check_allowed_keys(const char *path, const struct sshkey *sign_key,
765 const char *principal, const char *sig_namespace)
766{
767 FILE *f = NULL;
768 char *line = NULL;
769 size_t linesize = 0;
770 u_long linenum = 0;
771 int r, oerrno;
772
773 /* Check key and principal against file */
774 if ((f = fopen(path, "r")) == NULL) {
775 oerrno = errno;
776 error("Unable to open allowed keys file \"%s\": %s",
777 path, strerror(errno));
778 errno = oerrno;
779 return SSH_ERR_SYSTEM_ERROR;
780 }
781
782 while (getline(&line, &linesize, f) != -1) {
783 linenum++;
784 r = check_allowed_keys_line(path, linenum, line, sign_key,
785 principal, sig_namespace);
djm@openbsd.orgd637c4a2019-09-03 08:35:27 +0000786 free(line);
787 line = NULL;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000788 if (r == SSH_ERR_KEY_NOT_FOUND)
789 continue;
790 else if (r == 0) {
791 /* success */
792 fclose(f);
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000793 return 0;
djm@openbsd.org2a9c9f72019-09-03 08:34:19 +0000794 } else
795 break;
796 }
797 /* Either we hit an error parsing or we simply didn't find the key */
798 fclose(f);
799 free(line);
800 return r == 0 ? SSH_ERR_KEY_NOT_FOUND : r;
801}