diff options
author | Colin Watson <cjwatson@debian.org> | 2019-10-09 22:59:48 +0100 |
---|---|---|
committer | Colin Watson <cjwatson@debian.org> | 2019-10-09 23:39:39 +0100 |
commit | 767ee84d3465b6d244a9108de5c167a9ab866df9 (patch) | |
tree | 69b14ef6a62d7f133298a21d2ad6046f130b7801 /sshsig.c | |
parent | ddeaf9ee7d5c6612b88f1c4a83fc6fbccb93bf60 (diff) | |
parent | efef12825b9582c1710da3b7e50135870963d4f4 (diff) |
New upstream release (8.1p1)
Diffstat (limited to 'sshsig.c')
-rw-r--r-- | sshsig.c | 801 |
1 files changed, 801 insertions, 0 deletions
diff --git a/sshsig.c b/sshsig.c new file mode 100644 index 000000000..b19cd077d --- /dev/null +++ b/sshsig.c | |||
@@ -0,0 +1,801 @@ | |||
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 "includes.h" | ||
18 | |||
19 | #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 | |||
47 | int | ||
48 | sshsig_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 | |||
88 | int | ||
89 | sshsig_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) { | ||
137 | error("Couldn't decode signature: %s", ssh_err(r)); | ||
138 | goto done; | ||
139 | } | ||
140 | |||
141 | /* success */ | ||
142 | *out = buf; | ||
143 | r = 0; | ||
144 | buf = NULL; /* transferred */ | ||
145 | done: | ||
146 | sshbuf_free(buf); | ||
147 | sshbuf_free(sbuf); | ||
148 | free(b64); | ||
149 | return r; | ||
150 | } | ||
151 | |||
152 | static int | ||
153 | sshsig_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_put_stringb(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; | ||
214 | done: | ||
215 | free(sig); | ||
216 | sshbuf_free(blob); | ||
217 | sshbuf_free(tosign); | ||
218 | return r; | ||
219 | } | ||
220 | |||
221 | /* Check preamble and version. */ | ||
222 | static int | ||
223 | sshsig_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 | |||
235 | if (sversion > SIG_VERSION) { | ||
236 | 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 | |||
243 | static int | ||
244 | sshsig_check_hashalg(const char *hashalg) | ||
245 | { | ||
246 | if (hashalg == NULL || | ||
247 | match_pattern_list(hashalg, HASHALG_ALLOWED, 0) == 1) | ||
248 | return 0; | ||
249 | error("%s: unsupported hash algorithm \"%.100s\"", __func__, hashalg); | ||
250 | return SSH_ERR_SIGN_ALG_UNSUPPORTED; | ||
251 | } | ||
252 | |||
253 | static int | ||
254 | sshsig_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 | } | ||
274 | |||
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 | |||
285 | static int | ||
286 | sshsig_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 | |||
297 | debug("%s: verify message length %zu", __func__, sshbuf_len(h_message)); | ||
298 | 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 | } | ||
306 | 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_put_stringb(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 | } | ||
374 | done: | ||
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 | |||
384 | static int | ||
385 | hash_buffer(const struct sshbuf *m, const char *hashalg, struct sshbuf **bp) | ||
386 | { | ||
387 | char *hex, hash[SSH_DIGEST_MAX_LENGTH]; | ||
388 | int alg, r = SSH_ERR_INTERNAL_ERROR; | ||
389 | struct sshbuf *b = NULL; | ||
390 | |||
391 | *bp = NULL; | ||
392 | memset(hash, 0, sizeof(hash)); | ||
393 | |||
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", | ||
398 | __func__, hashalg); | ||
399 | return SSH_ERR_INTERNAL_ERROR; | ||
400 | } | ||
401 | if ((r = ssh_digest_buffer(alg, m, hash, sizeof(hash))) != 0) { | ||
402 | error("%s: ssh_digest_buffer failed: %s", __func__, ssh_err(r)); | ||
403 | return r; | ||
404 | } | ||
405 | 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) { | ||
410 | r = SSH_ERR_ALLOC_FAIL; | ||
411 | goto out; | ||
412 | } | ||
413 | 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 | |||
427 | int | ||
428 | sshsig_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 | } | ||
443 | 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); | ||
450 | return r; | ||
451 | } | ||
452 | |||
453 | int | ||
454 | sshsig_verifyb(struct sshbuf *signature, const struct sshbuf *message, | ||
455 | const char *expect_namespace, struct sshkey **sign_keyp) | ||
456 | { | ||
457 | struct sshbuf *b = NULL; | ||
458 | int r = SSH_ERR_INTERNAL_ERROR; | ||
459 | 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; | ||
466 | 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)); | ||
469 | 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); | ||
479 | return r; | ||
480 | } | ||
481 | |||
482 | static int | ||
483 | hash_file(int fd, const char *hashalg, struct sshbuf **bp) | ||
484 | { | ||
485 | char *hex, rbuf[8192], hash[SSH_DIGEST_MAX_LENGTH]; | ||
486 | ssize_t n, total = 0; | ||
487 | struct ssh_digest_ctx *ctx; | ||
488 | int alg, oerrno, r = SSH_ERR_INTERNAL_ERROR; | ||
489 | struct sshbuf *b = NULL; | ||
490 | |||
491 | *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) { | ||
502 | 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; | ||
513 | r = SSH_ERR_SYSTEM_ERROR; | ||
514 | goto out; | ||
515 | } 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)); | ||
523 | goto out; | ||
524 | } | ||
525 | } | ||
526 | if ((r = ssh_digest_final(ctx, hash, sizeof(hash))) != 0) { | ||
527 | error("%s: ssh_digest_final: %s", __func__, ssh_err(r)); | ||
528 | goto out; | ||
529 | } | ||
530 | if ((hex = tohex(hash, ssh_digest_bytes(alg))) != NULL) { | ||
531 | debug3("%s: final hash: %s", __func__, hex); | ||
532 | freezero(hex, strlen(hex)); | ||
533 | } | ||
534 | 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 */ | ||
544 | /* success */ | ||
545 | r = 0; | ||
546 | out: | ||
547 | sshbuf_free(b); | ||
548 | ssh_digest_free(ctx); | ||
549 | explicit_bzero(hash, sizeof(hash)); | ||
550 | return 0; | ||
551 | } | ||
552 | |||
553 | int | ||
554 | sshsig_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 | { | ||
558 | struct sshbuf *b = NULL; | ||
559 | int r = SSH_ERR_INTERNAL_ERROR; | ||
560 | |||
561 | if (hashalg == NULL) | ||
562 | hashalg = HASHALG_DEFAULT; | ||
563 | if (out != NULL) | ||
564 | *out = NULL; | ||
565 | if ((r = hash_file(fd, hashalg, &b)) != 0) { | ||
566 | error("%s: hash_file failed: %s", __func__, ssh_err(r)); | ||
567 | return r; | ||
568 | } | ||
569 | 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); | ||
576 | return r; | ||
577 | } | ||
578 | |||
579 | int | ||
580 | sshsig_verify_fd(struct sshbuf *signature, int fd, | ||
581 | const char *expect_namespace, struct sshkey **sign_keyp) | ||
582 | { | ||
583 | struct sshbuf *b = NULL; | ||
584 | int r = SSH_ERR_INTERNAL_ERROR; | ||
585 | 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; | ||
592 | debug("%s: signature made with hash \"%s\"", __func__, hashalg); | ||
593 | if ((r = hash_file(fd, hashalg, &b)) != 0) { | ||
594 | error("%s: hash_file failed: %s", __func__, ssh_err(r)); | ||
595 | 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); | ||
605 | return r; | ||
606 | } | ||
607 | |||
608 | struct sshsigopt { | ||
609 | int ca; | ||
610 | char *namespaces; | ||
611 | }; | ||
612 | |||
613 | struct sshsigopt * | ||
614 | sshsigopt_parse(const char *opts, const char *path, u_long linenum, | ||
615 | const char **errstrp) | ||
616 | { | ||
617 | struct sshsigopt *ret; | ||
618 | 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; | ||
661 | sshsigopt_free(ret); | ||
662 | return NULL; | ||
663 | } | ||
664 | |||
665 | void | ||
666 | sshsigopt_free(struct sshsigopt *opts) | ||
667 | { | ||
668 | if (opts == NULL) | ||
669 | return; | ||
670 | free(opts->namespaces); | ||
671 | free(opts); | ||
672 | } | ||
673 | |||
674 | static int | ||
675 | check_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; | ||
683 | struct sshsigopt *sigopts = NULL; | ||
684 | |||
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); | ||
723 | if ((sigopts = sshsigopt_parse(opts, path, linenum, &reason)) == NULL) { | ||
724 | 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); | ||
759 | sshsigopt_free(sigopts); | ||
760 | return found ? 0 : SSH_ERR_KEY_NOT_FOUND; | ||
761 | } | ||
762 | |||
763 | int | ||
764 | sshsig_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); | ||
786 | free(line); | ||
787 | line = NULL; | ||
788 | if (r == SSH_ERR_KEY_NOT_FOUND) | ||
789 | continue; | ||
790 | else if (r == 0) { | ||
791 | /* success */ | ||
792 | fclose(f); | ||
793 | return 0; | ||
794 | } 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 | } | ||