blob: 88e9ddf4de7d74a106be6c266527cfdfe8603065 [file] [log] [blame]
markus@openbsd.orgbf219922019-11-13 07:53:10 +00001/* $OpenBSD: sshkey-xmss.c,v 1.8 2019/11/13 07:53:10 markus Exp $ */
markus@openbsd.org1b11ea72018-02-23 15:58:37 +00002/*
3 * Copyright (c) 2017 Markus Friedl. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
Damien Millerf8854742018-02-26 12:18:14 +110026#include "includes.h"
Darren Tucker941e0d32018-02-28 19:59:35 +110027#ifdef WITH_XMSS
Damien Millerf8854742018-02-26 12:18:14 +110028
markus@openbsd.org1b11ea72018-02-23 15:58:37 +000029#include <sys/types.h>
30#include <sys/uio.h>
31
32#include <stdio.h>
33#include <string.h>
34#include <unistd.h>
35#include <fcntl.h>
36#include <errno.h>
Damien Millerf8854742018-02-26 12:18:14 +110037#ifdef HAVE_SYS_FILE_H
38# include <sys/file.h>
39#endif
markus@openbsd.org1b11ea72018-02-23 15:58:37 +000040
41#include "ssh2.h"
42#include "ssherr.h"
43#include "sshbuf.h"
44#include "cipher.h"
45#include "sshkey.h"
46#include "sshkey-xmss.h"
47#include "atomicio.h"
48
49#include "xmss_fast.h"
50
51/* opaque internal XMSS state */
52#define XMSS_MAGIC "xmss-state-v1"
53#define XMSS_CIPHERNAME "aes256-gcm@openssh.com"
54struct ssh_xmss_state {
55 xmss_params params;
56 u_int32_t n, w, h, k;
57
58 bds_state bds;
59 u_char *stack;
60 u_int32_t stackoffset;
61 u_char *stacklevels;
62 u_char *auth;
63 u_char *keep;
64 u_char *th_nodes;
65 u_char *retain;
66 treehash_inst *treehash;
67
68 u_int32_t idx; /* state read from file */
djm@openbsd.org001aa552018-04-10 00:10:49 +000069 u_int32_t maxidx; /* restricted # of signatures */
markus@openbsd.org1b11ea72018-02-23 15:58:37 +000070 int have_state; /* .state file exists */
71 int lockfd; /* locked in sshkey_xmss_get_state() */
markus@openbsd.orgbf219922019-11-13 07:53:10 +000072 u_char allow_update; /* allow sshkey_xmss_update_state() */
markus@openbsd.org1b11ea72018-02-23 15:58:37 +000073 char *enc_ciphername;/* encrypt state with cipher */
74 u_char *enc_keyiv; /* encrypt state with key */
75 u_int32_t enc_keyiv_len; /* length of enc_keyiv */
76};
77
78int sshkey_xmss_init_bds_state(struct sshkey *);
79int sshkey_xmss_init_enc_key(struct sshkey *, const char *);
80void sshkey_xmss_free_bds(struct sshkey *);
81int sshkey_xmss_get_state_from_file(struct sshkey *, const char *,
82 int *, sshkey_printfn *);
83int sshkey_xmss_encrypt_state(const struct sshkey *, struct sshbuf *,
84 struct sshbuf **);
85int sshkey_xmss_decrypt_state(const struct sshkey *, struct sshbuf *,
86 struct sshbuf **);
87int sshkey_xmss_serialize_enc_key(const struct sshkey *, struct sshbuf *);
88int sshkey_xmss_deserialize_enc_key(struct sshkey *, struct sshbuf *);
89
90#define PRINT(s...) do { if (pr) pr(s); } while (0)
91
92int
93sshkey_xmss_init(struct sshkey *key, const char *name)
94{
95 struct ssh_xmss_state *state;
96
97 if (key->xmss_state != NULL)
98 return SSH_ERR_INVALID_FORMAT;
99 if (name == NULL)
100 return SSH_ERR_INVALID_FORMAT;
101 state = calloc(sizeof(struct ssh_xmss_state), 1);
102 if (state == NULL)
103 return SSH_ERR_ALLOC_FAIL;
104 if (strcmp(name, XMSS_SHA2_256_W16_H10_NAME) == 0) {
105 state->n = 32;
106 state->w = 16;
107 state->h = 10;
108 } else if (strcmp(name, XMSS_SHA2_256_W16_H16_NAME) == 0) {
109 state->n = 32;
110 state->w = 16;
111 state->h = 16;
112 } else if (strcmp(name, XMSS_SHA2_256_W16_H20_NAME) == 0) {
113 state->n = 32;
114 state->w = 16;
115 state->h = 20;
116 } else {
117 free(state);
118 return SSH_ERR_KEY_TYPE_UNKNOWN;
119 }
120 if ((key->xmss_name = strdup(name)) == NULL) {
121 free(state);
122 return SSH_ERR_ALLOC_FAIL;
123 }
124 state->k = 2; /* XXX hardcoded */
125 state->lockfd = -1;
126 if (xmss_set_params(&state->params, state->n, state->h, state->w,
127 state->k) != 0) {
128 free(state);
129 return SSH_ERR_INVALID_FORMAT;
130 }
131 key->xmss_state = state;
132 return 0;
133}
134
135void
136sshkey_xmss_free_state(struct sshkey *key)
137{
138 struct ssh_xmss_state *state = key->xmss_state;
139
140 sshkey_xmss_free_bds(key);
141 if (state) {
142 if (state->enc_keyiv) {
143 explicit_bzero(state->enc_keyiv, state->enc_keyiv_len);
144 free(state->enc_keyiv);
145 }
146 free(state->enc_ciphername);
147 free(state);
148 }
149 key->xmss_state = NULL;
150}
151
152#define SSH_XMSS_K2_MAGIC "k=2"
153#define num_stack(x) ((x->h+1)*(x->n))
154#define num_stacklevels(x) (x->h+1)
155#define num_auth(x) ((x->h)*(x->n))
156#define num_keep(x) ((x->h >> 1)*(x->n))
157#define num_th_nodes(x) ((x->h - x->k)*(x->n))
158#define num_retain(x) (((1ULL << x->k) - x->k - 1) * (x->n))
159#define num_treehash(x) ((x->h) - (x->k))
160
161int
162sshkey_xmss_init_bds_state(struct sshkey *key)
163{
164 struct ssh_xmss_state *state = key->xmss_state;
165 u_int32_t i;
166
167 state->stackoffset = 0;
168 if ((state->stack = calloc(num_stack(state), 1)) == NULL ||
169 (state->stacklevels = calloc(num_stacklevels(state), 1))== NULL ||
170 (state->auth = calloc(num_auth(state), 1)) == NULL ||
171 (state->keep = calloc(num_keep(state), 1)) == NULL ||
172 (state->th_nodes = calloc(num_th_nodes(state), 1)) == NULL ||
173 (state->retain = calloc(num_retain(state), 1)) == NULL ||
174 (state->treehash = calloc(num_treehash(state),
175 sizeof(treehash_inst))) == NULL) {
176 sshkey_xmss_free_bds(key);
177 return SSH_ERR_ALLOC_FAIL;
178 }
179 for (i = 0; i < state->h - state->k; i++)
180 state->treehash[i].node = &state->th_nodes[state->n*i];
181 xmss_set_bds_state(&state->bds, state->stack, state->stackoffset,
182 state->stacklevels, state->auth, state->keep, state->treehash,
183 state->retain, 0);
184 return 0;
185}
186
187void
188sshkey_xmss_free_bds(struct sshkey *key)
189{
190 struct ssh_xmss_state *state = key->xmss_state;
191
192 if (state == NULL)
193 return;
194 free(state->stack);
195 free(state->stacklevels);
196 free(state->auth);
197 free(state->keep);
198 free(state->th_nodes);
199 free(state->retain);
200 free(state->treehash);
201 state->stack = NULL;
202 state->stacklevels = NULL;
203 state->auth = NULL;
204 state->keep = NULL;
205 state->th_nodes = NULL;
206 state->retain = NULL;
207 state->treehash = NULL;
208}
209
210void *
211sshkey_xmss_params(const struct sshkey *key)
212{
213 struct ssh_xmss_state *state = key->xmss_state;
214
215 if (state == NULL)
216 return NULL;
217 return &state->params;
218}
219
220void *
221sshkey_xmss_bds_state(const struct sshkey *key)
222{
223 struct ssh_xmss_state *state = key->xmss_state;
224
225 if (state == NULL)
226 return NULL;
227 return &state->bds;
228}
229
230int
231sshkey_xmss_siglen(const struct sshkey *key, size_t *lenp)
232{
233 struct ssh_xmss_state *state = key->xmss_state;
234
235 if (lenp == NULL)
236 return SSH_ERR_INVALID_ARGUMENT;
237 if (state == NULL)
238 return SSH_ERR_INVALID_FORMAT;
239 *lenp = 4 + state->n +
240 state->params.wots_par.keysize +
241 state->h * state->n;
242 return 0;
243}
244
245size_t
246sshkey_xmss_pklen(const struct sshkey *key)
247{
248 struct ssh_xmss_state *state = key->xmss_state;
249
250 if (state == NULL)
251 return 0;
252 return state->n * 2;
253}
254
255size_t
256sshkey_xmss_sklen(const struct sshkey *key)
257{
258 struct ssh_xmss_state *state = key->xmss_state;
259
260 if (state == NULL)
261 return 0;
262 return state->n * 4 + 4;
263}
264
265int
266sshkey_xmss_init_enc_key(struct sshkey *k, const char *ciphername)
267{
268 struct ssh_xmss_state *state = k->xmss_state;
269 const struct sshcipher *cipher;
270 size_t keylen = 0, ivlen = 0;
271
272 if (state == NULL)
273 return SSH_ERR_INVALID_ARGUMENT;
274 if ((cipher = cipher_by_name(ciphername)) == NULL)
275 return SSH_ERR_INTERNAL_ERROR;
276 if ((state->enc_ciphername = strdup(ciphername)) == NULL)
277 return SSH_ERR_ALLOC_FAIL;
278 keylen = cipher_keylen(cipher);
279 ivlen = cipher_ivlen(cipher);
280 state->enc_keyiv_len = keylen + ivlen;
281 if ((state->enc_keyiv = calloc(state->enc_keyiv_len, 1)) == NULL) {
282 free(state->enc_ciphername);
283 state->enc_ciphername = NULL;
284 return SSH_ERR_ALLOC_FAIL;
285 }
286 arc4random_buf(state->enc_keyiv, state->enc_keyiv_len);
287 return 0;
288}
289
290int
291sshkey_xmss_serialize_enc_key(const struct sshkey *k, struct sshbuf *b)
292{
293 struct ssh_xmss_state *state = k->xmss_state;
294 int r;
295
296 if (state == NULL || state->enc_keyiv == NULL ||
297 state->enc_ciphername == NULL)
298 return SSH_ERR_INVALID_ARGUMENT;
299 if ((r = sshbuf_put_cstring(b, state->enc_ciphername)) != 0 ||
300 (r = sshbuf_put_string(b, state->enc_keyiv,
301 state->enc_keyiv_len)) != 0)
302 return r;
303 return 0;
304}
305
306int
307sshkey_xmss_deserialize_enc_key(struct sshkey *k, struct sshbuf *b)
308{
309 struct ssh_xmss_state *state = k->xmss_state;
310 size_t len;
311 int r;
312
313 if (state == NULL)
314 return SSH_ERR_INVALID_ARGUMENT;
315 if ((r = sshbuf_get_cstring(b, &state->enc_ciphername, NULL)) != 0 ||
316 (r = sshbuf_get_string(b, &state->enc_keyiv, &len)) != 0)
317 return r;
318 state->enc_keyiv_len = len;
319 return 0;
320}
321
322int
323sshkey_xmss_serialize_pk_info(const struct sshkey *k, struct sshbuf *b,
324 enum sshkey_serialize_rep opts)
325{
326 struct ssh_xmss_state *state = k->xmss_state;
327 u_char have_info = 1;
328 u_int32_t idx;
329 int r;
330
331 if (state == NULL)
332 return SSH_ERR_INVALID_ARGUMENT;
333 if (opts != SSHKEY_SERIALIZE_INFO)
334 return 0;
335 idx = k->xmss_sk ? PEEK_U32(k->xmss_sk) : state->idx;
336 if ((r = sshbuf_put_u8(b, have_info)) != 0 ||
337 (r = sshbuf_put_u32(b, idx)) != 0 ||
338 (r = sshbuf_put_u32(b, state->maxidx)) != 0)
339 return r;
340 return 0;
341}
342
343int
344sshkey_xmss_deserialize_pk_info(struct sshkey *k, struct sshbuf *b)
345{
346 struct ssh_xmss_state *state = k->xmss_state;
347 u_char have_info;
348 int r;
349
350 if (state == NULL)
351 return SSH_ERR_INVALID_ARGUMENT;
352 /* optional */
353 if (sshbuf_len(b) == 0)
354 return 0;
355 if ((r = sshbuf_get_u8(b, &have_info)) != 0)
356 return r;
357 if (have_info != 1)
358 return SSH_ERR_INVALID_ARGUMENT;
359 if ((r = sshbuf_get_u32(b, &state->idx)) != 0 ||
360 (r = sshbuf_get_u32(b, &state->maxidx)) != 0)
361 return r;
362 return 0;
363}
364
365int
366sshkey_xmss_generate_private_key(struct sshkey *k, u_int bits)
367{
368 int r;
369 const char *name;
370
371 if (bits == 10) {
372 name = XMSS_SHA2_256_W16_H10_NAME;
373 } else if (bits == 16) {
374 name = XMSS_SHA2_256_W16_H16_NAME;
375 } else if (bits == 20) {
376 name = XMSS_SHA2_256_W16_H20_NAME;
377 } else {
378 name = XMSS_DEFAULT_NAME;
379 }
380 if ((r = sshkey_xmss_init(k, name)) != 0 ||
381 (r = sshkey_xmss_init_bds_state(k)) != 0 ||
382 (r = sshkey_xmss_init_enc_key(k, XMSS_CIPHERNAME)) != 0)
383 return r;
384 if ((k->xmss_pk = malloc(sshkey_xmss_pklen(k))) == NULL ||
385 (k->xmss_sk = malloc(sshkey_xmss_sklen(k))) == NULL) {
386 return SSH_ERR_ALLOC_FAIL;
387 }
388 xmss_keypair(k->xmss_pk, k->xmss_sk, sshkey_xmss_bds_state(k),
389 sshkey_xmss_params(k));
390 return 0;
391}
392
393int
394sshkey_xmss_get_state_from_file(struct sshkey *k, const char *filename,
395 int *have_file, sshkey_printfn *pr)
396{
397 struct sshbuf *b = NULL, *enc = NULL;
398 int ret = SSH_ERR_SYSTEM_ERROR, r, fd = -1;
399 u_int32_t len;
400 unsigned char buf[4], *data = NULL;
401
402 *have_file = 0;
403 if ((fd = open(filename, O_RDONLY)) >= 0) {
404 *have_file = 1;
405 if (atomicio(read, fd, buf, sizeof(buf)) != sizeof(buf)) {
406 PRINT("%s: corrupt state file: %s", __func__, filename);
407 goto done;
408 }
409 len = PEEK_U32(buf);
410 if ((data = calloc(len, 1)) == NULL) {
411 ret = SSH_ERR_ALLOC_FAIL;
412 goto done;
413 }
414 if (atomicio(read, fd, data, len) != len) {
415 PRINT("%s: cannot read blob: %s", __func__, filename);
416 goto done;
417 }
418 if ((enc = sshbuf_from(data, len)) == NULL) {
419 ret = SSH_ERR_ALLOC_FAIL;
420 goto done;
421 }
422 sshkey_xmss_free_bds(k);
423 if ((r = sshkey_xmss_decrypt_state(k, enc, &b)) != 0) {
424 ret = r;
425 goto done;
426 }
427 if ((r = sshkey_xmss_deserialize_state(k, b)) != 0) {
428 ret = r;
429 goto done;
430 }
431 ret = 0;
432 }
433done:
434 if (fd != -1)
435 close(fd);
436 free(data);
437 sshbuf_free(enc);
438 sshbuf_free(b);
439 return ret;
440}
441
442int
443sshkey_xmss_get_state(const struct sshkey *k, sshkey_printfn *pr)
444{
445 struct ssh_xmss_state *state = k->xmss_state;
446 u_int32_t idx = 0;
447 char *filename = NULL;
448 char *statefile = NULL, *ostatefile = NULL, *lockfile = NULL;
449 int lockfd = -1, have_state = 0, have_ostate, tries = 0;
450 int ret = SSH_ERR_INVALID_ARGUMENT, r;
451
452 if (state == NULL)
453 goto done;
454 /*
455 * If maxidx is set, then we are allowed a limited number
456 * of signatures, but don't need to access the disk.
457 * Otherwise we need to deal with the on-disk state.
458 */
459 if (state->maxidx) {
460 /* xmss_sk always contains the current state */
461 idx = PEEK_U32(k->xmss_sk);
462 if (idx < state->maxidx) {
463 state->allow_update = 1;
464 return 0;
465 }
466 return SSH_ERR_INVALID_ARGUMENT;
467 }
468 if ((filename = k->xmss_filename) == NULL)
469 goto done;
deraadt@openbsd.org5cdbaa72019-06-27 18:03:37 +0000470 if (asprintf(&lockfile, "%s.lock", filename) == -1 ||
471 asprintf(&statefile, "%s.state", filename) == -1 ||
472 asprintf(&ostatefile, "%s.ostate", filename) == -1) {
markus@openbsd.org1b11ea72018-02-23 15:58:37 +0000473 ret = SSH_ERR_ALLOC_FAIL;
474 goto done;
475 }
deraadt@openbsd.org4d28fa72019-06-28 13:35:04 +0000476 if ((lockfd = open(lockfile, O_CREAT|O_RDONLY, 0600)) == -1) {
markus@openbsd.org1b11ea72018-02-23 15:58:37 +0000477 ret = SSH_ERR_SYSTEM_ERROR;
478 PRINT("%s: cannot open/create: %s", __func__, lockfile);
479 goto done;
480 }
deraadt@openbsd.org4d28fa72019-06-28 13:35:04 +0000481 while (flock(lockfd, LOCK_EX|LOCK_NB) == -1) {
markus@openbsd.org1b11ea72018-02-23 15:58:37 +0000482 if (errno != EWOULDBLOCK) {
483 ret = SSH_ERR_SYSTEM_ERROR;
484 PRINT("%s: cannot lock: %s", __func__, lockfile);
485 goto done;
486 }
487 if (++tries > 10) {
488 ret = SSH_ERR_SYSTEM_ERROR;
489 PRINT("%s: giving up on: %s", __func__, lockfile);
490 goto done;
491 }
492 usleep(1000*100*tries);
493 }
494 /* XXX no longer const */
495 if ((r = sshkey_xmss_get_state_from_file((struct sshkey *)k,
496 statefile, &have_state, pr)) != 0) {
497 if ((r = sshkey_xmss_get_state_from_file((struct sshkey *)k,
498 ostatefile, &have_ostate, pr)) == 0) {
499 state->allow_update = 1;
500 r = sshkey_xmss_forward_state(k, 1);
501 state->idx = PEEK_U32(k->xmss_sk);
502 state->allow_update = 0;
503 }
504 }
505 if (!have_state && !have_ostate) {
506 /* check that bds state is initialized */
507 if (state->bds.auth == NULL)
508 goto done;
509 PRINT("%s: start from scratch idx 0: %u", __func__, state->idx);
510 } else if (r != 0) {
511 ret = r;
512 goto done;
513 }
514 if (state->idx + 1 < state->idx) {
515 PRINT("%s: state wrap: %u", __func__, state->idx);
516 goto done;
517 }
518 state->have_state = have_state;
519 state->lockfd = lockfd;
520 state->allow_update = 1;
521 lockfd = -1;
522 ret = 0;
523done:
524 if (lockfd != -1)
525 close(lockfd);
526 free(lockfile);
527 free(statefile);
528 free(ostatefile);
529 return ret;
530}
531
532int
533sshkey_xmss_forward_state(const struct sshkey *k, u_int32_t reserve)
534{
535 struct ssh_xmss_state *state = k->xmss_state;
536 u_char *sig = NULL;
537 size_t required_siglen;
538 unsigned long long smlen;
539 u_char data;
540 int ret, r;
541
542 if (state == NULL || !state->allow_update)
543 return SSH_ERR_INVALID_ARGUMENT;
544 if (reserve == 0)
545 return SSH_ERR_INVALID_ARGUMENT;
546 if (state->idx + reserve <= state->idx)
547 return SSH_ERR_INVALID_ARGUMENT;
548 if ((r = sshkey_xmss_siglen(k, &required_siglen)) != 0)
549 return r;
550 if ((sig = malloc(required_siglen)) == NULL)
551 return SSH_ERR_ALLOC_FAIL;
552 while (reserve-- > 0) {
553 state->idx = PEEK_U32(k->xmss_sk);
554 smlen = required_siglen;
555 if ((ret = xmss_sign(k->xmss_sk, sshkey_xmss_bds_state(k),
556 sig, &smlen, &data, 0, sshkey_xmss_params(k))) != 0) {
557 r = SSH_ERR_INVALID_ARGUMENT;
558 break;
559 }
560 }
561 free(sig);
562 return r;
563}
564
565int
566sshkey_xmss_update_state(const struct sshkey *k, sshkey_printfn *pr)
567{
568 struct ssh_xmss_state *state = k->xmss_state;
569 struct sshbuf *b = NULL, *enc = NULL;
570 u_int32_t idx = 0;
571 unsigned char buf[4];
572 char *filename = NULL;
573 char *statefile = NULL, *ostatefile = NULL, *nstatefile = NULL;
574 int fd = -1;
575 int ret = SSH_ERR_INVALID_ARGUMENT;
576
577 if (state == NULL || !state->allow_update)
578 return ret;
579 if (state->maxidx) {
580 /* no update since the number of signatures is limited */
581 ret = 0;
582 goto done;
583 }
584 idx = PEEK_U32(k->xmss_sk);
585 if (idx == state->idx) {
djm@openbsd.org001aa552018-04-10 00:10:49 +0000586 /* no signature happened, no need to update */
markus@openbsd.org1b11ea72018-02-23 15:58:37 +0000587 ret = 0;
588 goto done;
589 } else if (idx != state->idx + 1) {
590 PRINT("%s: more than one signature happened: idx %u state %u",
591 __func__, idx, state->idx);
592 goto done;
593 }
594 state->idx = idx;
595 if ((filename = k->xmss_filename) == NULL)
596 goto done;
deraadt@openbsd.org5cdbaa72019-06-27 18:03:37 +0000597 if (asprintf(&statefile, "%s.state", filename) == -1 ||
598 asprintf(&ostatefile, "%s.ostate", filename) == -1 ||
599 asprintf(&nstatefile, "%s.nstate", filename) == -1) {
markus@openbsd.org1b11ea72018-02-23 15:58:37 +0000600 ret = SSH_ERR_ALLOC_FAIL;
601 goto done;
602 }
603 unlink(nstatefile);
604 if ((b = sshbuf_new()) == NULL) {
605 ret = SSH_ERR_ALLOC_FAIL;
606 goto done;
607 }
608 if ((ret = sshkey_xmss_serialize_state(k, b)) != 0) {
609 PRINT("%s: SERLIALIZE FAILED: %d", __func__, ret);
610 goto done;
611 }
612 if ((ret = sshkey_xmss_encrypt_state(k, b, &enc)) != 0) {
613 PRINT("%s: ENCRYPT FAILED: %d", __func__, ret);
614 goto done;
615 }
deraadt@openbsd.org4d28fa72019-06-28 13:35:04 +0000616 if ((fd = open(nstatefile, O_CREAT|O_WRONLY|O_EXCL, 0600)) == -1) {
markus@openbsd.org1b11ea72018-02-23 15:58:37 +0000617 ret = SSH_ERR_SYSTEM_ERROR;
618 PRINT("%s: open new state file: %s", __func__, nstatefile);
619 goto done;
620 }
621 POKE_U32(buf, sshbuf_len(enc));
622 if (atomicio(vwrite, fd, buf, sizeof(buf)) != sizeof(buf)) {
623 ret = SSH_ERR_SYSTEM_ERROR;
624 PRINT("%s: write new state file hdr: %s", __func__, nstatefile);
625 close(fd);
626 goto done;
627 }
markus@openbsd.org49f47e62018-07-09 21:59:10 +0000628 if (atomicio(vwrite, fd, sshbuf_mutable_ptr(enc), sshbuf_len(enc)) !=
markus@openbsd.org1b11ea72018-02-23 15:58:37 +0000629 sshbuf_len(enc)) {
630 ret = SSH_ERR_SYSTEM_ERROR;
631 PRINT("%s: write new state file data: %s", __func__, nstatefile);
632 close(fd);
633 goto done;
634 }
deraadt@openbsd.org4d28fa72019-06-28 13:35:04 +0000635 if (fsync(fd) == -1) {
markus@openbsd.org1b11ea72018-02-23 15:58:37 +0000636 ret = SSH_ERR_SYSTEM_ERROR;
637 PRINT("%s: sync new state file: %s", __func__, nstatefile);
638 close(fd);
639 goto done;
640 }
deraadt@openbsd.org4d28fa72019-06-28 13:35:04 +0000641 if (close(fd) == -1) {
markus@openbsd.org1b11ea72018-02-23 15:58:37 +0000642 ret = SSH_ERR_SYSTEM_ERROR;
643 PRINT("%s: close new state file: %s", __func__, nstatefile);
644 goto done;
645 }
646 if (state->have_state) {
647 unlink(ostatefile);
648 if (link(statefile, ostatefile)) {
649 ret = SSH_ERR_SYSTEM_ERROR;
650 PRINT("%s: backup state %s to %s", __func__, statefile,
651 ostatefile);
652 goto done;
653 }
654 }
deraadt@openbsd.org4d28fa72019-06-28 13:35:04 +0000655 if (rename(nstatefile, statefile) == -1) {
markus@openbsd.org1b11ea72018-02-23 15:58:37 +0000656 ret = SSH_ERR_SYSTEM_ERROR;
657 PRINT("%s: rename %s to %s", __func__, nstatefile, statefile);
658 goto done;
659 }
660 ret = 0;
661done:
662 if (state->lockfd != -1) {
663 close(state->lockfd);
664 state->lockfd = -1;
665 }
666 if (nstatefile)
667 unlink(nstatefile);
668 free(statefile);
669 free(ostatefile);
670 free(nstatefile);
671 sshbuf_free(b);
672 sshbuf_free(enc);
673 return ret;
674}
675
676int
677sshkey_xmss_serialize_state(const struct sshkey *k, struct sshbuf *b)
678{
679 struct ssh_xmss_state *state = k->xmss_state;
680 treehash_inst *th;
681 u_int32_t i, node;
682 int r;
683
684 if (state == NULL)
685 return SSH_ERR_INVALID_ARGUMENT;
686 if (state->stack == NULL)
687 return SSH_ERR_INVALID_ARGUMENT;
688 state->stackoffset = state->bds.stackoffset; /* copy back */
689 if ((r = sshbuf_put_cstring(b, SSH_XMSS_K2_MAGIC)) != 0 ||
690 (r = sshbuf_put_u32(b, state->idx)) != 0 ||
691 (r = sshbuf_put_string(b, state->stack, num_stack(state))) != 0 ||
692 (r = sshbuf_put_u32(b, state->stackoffset)) != 0 ||
693 (r = sshbuf_put_string(b, state->stacklevels, num_stacklevels(state))) != 0 ||
694 (r = sshbuf_put_string(b, state->auth, num_auth(state))) != 0 ||
695 (r = sshbuf_put_string(b, state->keep, num_keep(state))) != 0 ||
696 (r = sshbuf_put_string(b, state->th_nodes, num_th_nodes(state))) != 0 ||
697 (r = sshbuf_put_string(b, state->retain, num_retain(state))) != 0 ||
698 (r = sshbuf_put_u32(b, num_treehash(state))) != 0)
699 return r;
700 for (i = 0; i < num_treehash(state); i++) {
701 th = &state->treehash[i];
702 node = th->node - state->th_nodes;
703 if ((r = sshbuf_put_u32(b, th->h)) != 0 ||
704 (r = sshbuf_put_u32(b, th->next_idx)) != 0 ||
705 (r = sshbuf_put_u32(b, th->stackusage)) != 0 ||
706 (r = sshbuf_put_u8(b, th->completed)) != 0 ||
707 (r = sshbuf_put_u32(b, node)) != 0)
708 return r;
709 }
710 return 0;
711}
712
713int
714sshkey_xmss_serialize_state_opt(const struct sshkey *k, struct sshbuf *b,
715 enum sshkey_serialize_rep opts)
716{
717 struct ssh_xmss_state *state = k->xmss_state;
718 int r = SSH_ERR_INVALID_ARGUMENT;
markus@openbsd.orgbf219922019-11-13 07:53:10 +0000719 u_char have_stack, have_filename, have_enc;
markus@openbsd.org1b11ea72018-02-23 15:58:37 +0000720
721 if (state == NULL)
722 return SSH_ERR_INVALID_ARGUMENT;
723 if ((r = sshbuf_put_u8(b, opts)) != 0)
724 return r;
725 switch (opts) {
726 case SSHKEY_SERIALIZE_STATE:
727 r = sshkey_xmss_serialize_state(k, b);
728 break;
729 case SSHKEY_SERIALIZE_FULL:
730 if ((r = sshkey_xmss_serialize_enc_key(k, b)) != 0)
markus@openbsd.orgbf219922019-11-13 07:53:10 +0000731 return r;
markus@openbsd.org1b11ea72018-02-23 15:58:37 +0000732 r = sshkey_xmss_serialize_state(k, b);
733 break;
markus@openbsd.orgbf219922019-11-13 07:53:10 +0000734 case SSHKEY_SERIALIZE_SHIELD:
735 /* all of stack/filename/enc are optional */
736 have_stack = state->stack != NULL;
737 if ((r = sshbuf_put_u8(b, have_stack)) != 0)
738 return r;
739 if (have_stack) {
740 state->idx = PEEK_U32(k->xmss_sk); /* update */
741 if ((r = sshkey_xmss_serialize_state(k, b)) != 0)
742 return r;
743 }
744 have_filename = k->xmss_filename != NULL;
745 if ((r = sshbuf_put_u8(b, have_filename)) != 0)
746 return r;
747 if (have_filename &&
748 (r = sshbuf_put_cstring(b, k->xmss_filename)) != 0)
749 return r;
750 have_enc = state->enc_keyiv != NULL;
751 if ((r = sshbuf_put_u8(b, have_enc)) != 0)
752 return r;
753 if (have_enc &&
754 (r = sshkey_xmss_serialize_enc_key(k, b)) != 0)
755 return r;
756 if ((r = sshbuf_put_u32(b, state->maxidx)) != 0 ||
757 (r = sshbuf_put_u8(b, state->allow_update)) != 0)
758 return r;
759 break;
markus@openbsd.org1b11ea72018-02-23 15:58:37 +0000760 case SSHKEY_SERIALIZE_DEFAULT:
761 r = 0;
762 break;
763 default:
764 r = SSH_ERR_INVALID_ARGUMENT;
765 break;
766 }
767 return r;
768}
769
770int
771sshkey_xmss_deserialize_state(struct sshkey *k, struct sshbuf *b)
772{
773 struct ssh_xmss_state *state = k->xmss_state;
774 treehash_inst *th;
775 u_int32_t i, lh, node;
776 size_t ls, lsl, la, lk, ln, lr;
777 char *magic;
djm@openbsd.orgd7d116b2019-10-14 06:00:02 +0000778 int r = SSH_ERR_INTERNAL_ERROR;
markus@openbsd.org1b11ea72018-02-23 15:58:37 +0000779
780 if (state == NULL)
781 return SSH_ERR_INVALID_ARGUMENT;
782 if (k->xmss_sk == NULL)
783 return SSH_ERR_INVALID_ARGUMENT;
784 if ((state->treehash = calloc(num_treehash(state),
785 sizeof(treehash_inst))) == NULL)
786 return SSH_ERR_ALLOC_FAIL;
787 if ((r = sshbuf_get_cstring(b, &magic, NULL)) != 0 ||
788 (r = sshbuf_get_u32(b, &state->idx)) != 0 ||
789 (r = sshbuf_get_string(b, &state->stack, &ls)) != 0 ||
790 (r = sshbuf_get_u32(b, &state->stackoffset)) != 0 ||
791 (r = sshbuf_get_string(b, &state->stacklevels, &lsl)) != 0 ||
792 (r = sshbuf_get_string(b, &state->auth, &la)) != 0 ||
793 (r = sshbuf_get_string(b, &state->keep, &lk)) != 0 ||
794 (r = sshbuf_get_string(b, &state->th_nodes, &ln)) != 0 ||
795 (r = sshbuf_get_string(b, &state->retain, &lr)) != 0 ||
796 (r = sshbuf_get_u32(b, &lh)) != 0)
djm@openbsd.orgd7d116b2019-10-14 06:00:02 +0000797 goto out;
798 if (strcmp(magic, SSH_XMSS_K2_MAGIC) != 0) {
799 r = SSH_ERR_INVALID_ARGUMENT;
800 goto out;
801 }
markus@openbsd.org1b11ea72018-02-23 15:58:37 +0000802 /* XXX check stackoffset */
803 if (ls != num_stack(state) ||
804 lsl != num_stacklevels(state) ||
805 la != num_auth(state) ||
806 lk != num_keep(state) ||
807 ln != num_th_nodes(state) ||
808 lr != num_retain(state) ||
djm@openbsd.orgd7d116b2019-10-14 06:00:02 +0000809 lh != num_treehash(state)) {
810 r = SSH_ERR_INVALID_ARGUMENT;
811 goto out;
812 }
markus@openbsd.org1b11ea72018-02-23 15:58:37 +0000813 for (i = 0; i < num_treehash(state); i++) {
814 th = &state->treehash[i];
815 if ((r = sshbuf_get_u32(b, &th->h)) != 0 ||
816 (r = sshbuf_get_u32(b, &th->next_idx)) != 0 ||
817 (r = sshbuf_get_u32(b, &th->stackusage)) != 0 ||
818 (r = sshbuf_get_u8(b, &th->completed)) != 0 ||
819 (r = sshbuf_get_u32(b, &node)) != 0)
djm@openbsd.orgd7d116b2019-10-14 06:00:02 +0000820 goto out;
markus@openbsd.org1b11ea72018-02-23 15:58:37 +0000821 if (node < num_th_nodes(state))
822 th->node = &state->th_nodes[node];
823 }
824 POKE_U32(k->xmss_sk, state->idx);
825 xmss_set_bds_state(&state->bds, state->stack, state->stackoffset,
826 state->stacklevels, state->auth, state->keep, state->treehash,
827 state->retain, 0);
djm@openbsd.orgd7d116b2019-10-14 06:00:02 +0000828 /* success */
829 r = 0;
830 out:
831 free(magic);
832 return r;
markus@openbsd.org1b11ea72018-02-23 15:58:37 +0000833}
834
835int
836sshkey_xmss_deserialize_state_opt(struct sshkey *k, struct sshbuf *b)
837{
markus@openbsd.orgbf219922019-11-13 07:53:10 +0000838 struct ssh_xmss_state *state = k->xmss_state;
markus@openbsd.org1b11ea72018-02-23 15:58:37 +0000839 enum sshkey_serialize_rep opts;
markus@openbsd.orgbf219922019-11-13 07:53:10 +0000840 u_char have_state, have_stack, have_filename, have_enc;
markus@openbsd.org1b11ea72018-02-23 15:58:37 +0000841 int r;
842
843 if ((r = sshbuf_get_u8(b, &have_state)) != 0)
844 return r;
845
846 opts = have_state;
847 switch (opts) {
848 case SSHKEY_SERIALIZE_DEFAULT:
849 r = 0;
850 break;
markus@openbsd.orgbf219922019-11-13 07:53:10 +0000851 case SSHKEY_SERIALIZE_SHIELD:
852 if ((r = sshbuf_get_u8(b, &have_stack)) != 0)
853 return r;
854 if (have_stack &&
855 (r = sshkey_xmss_deserialize_state(k, b)) != 0)
856 return r;
857 if ((r = sshbuf_get_u8(b, &have_filename)) != 0)
858 return r;
859 if (have_filename &&
860 (r = sshbuf_get_cstring(b, &k->xmss_filename, NULL)) != 0)
861 return r;
862 if ((r = sshbuf_get_u8(b, &have_enc)) != 0)
863 return r;
864 if (have_enc &&
865 (r = sshkey_xmss_deserialize_enc_key(k, b)) != 0)
866 return r;
867 if ((r = sshbuf_get_u32(b, &state->maxidx)) != 0 ||
868 (r = sshbuf_get_u8(b, &state->allow_update)) != 0)
869 return r;
870 break;
markus@openbsd.org1b11ea72018-02-23 15:58:37 +0000871 case SSHKEY_SERIALIZE_STATE:
872 if ((r = sshkey_xmss_deserialize_state(k, b)) != 0)
873 return r;
874 break;
875 case SSHKEY_SERIALIZE_FULL:
876 if ((r = sshkey_xmss_deserialize_enc_key(k, b)) != 0 ||
877 (r = sshkey_xmss_deserialize_state(k, b)) != 0)
878 return r;
879 break;
880 default:
881 r = SSH_ERR_INVALID_FORMAT;
882 break;
883 }
884 return r;
885}
886
887int
888sshkey_xmss_encrypt_state(const struct sshkey *k, struct sshbuf *b,
889 struct sshbuf **retp)
890{
891 struct ssh_xmss_state *state = k->xmss_state;
892 struct sshbuf *encrypted = NULL, *encoded = NULL, *padded = NULL;
893 struct sshcipher_ctx *ciphercontext = NULL;
894 const struct sshcipher *cipher;
895 u_char *cp, *key, *iv = NULL;
896 size_t i, keylen, ivlen, blocksize, authlen, encrypted_len, aadlen;
897 int r = SSH_ERR_INTERNAL_ERROR;
898
899 if (retp != NULL)
900 *retp = NULL;
901 if (state == NULL ||
902 state->enc_keyiv == NULL ||
903 state->enc_ciphername == NULL)
904 return SSH_ERR_INTERNAL_ERROR;
905 if ((cipher = cipher_by_name(state->enc_ciphername)) == NULL) {
906 r = SSH_ERR_INTERNAL_ERROR;
907 goto out;
908 }
909 blocksize = cipher_blocksize(cipher);
910 keylen = cipher_keylen(cipher);
911 ivlen = cipher_ivlen(cipher);
912 authlen = cipher_authlen(cipher);
913 if (state->enc_keyiv_len != keylen + ivlen) {
914 r = SSH_ERR_INVALID_FORMAT;
915 goto out;
916 }
917 key = state->enc_keyiv;
918 if ((encrypted = sshbuf_new()) == NULL ||
919 (encoded = sshbuf_new()) == NULL ||
920 (padded = sshbuf_new()) == NULL ||
921 (iv = malloc(ivlen)) == NULL) {
922 r = SSH_ERR_ALLOC_FAIL;
923 goto out;
924 }
925
926 /* replace first 4 bytes of IV with index to ensure uniqueness */
927 memcpy(iv, key + keylen, ivlen);
928 POKE_U32(iv, state->idx);
929
930 if ((r = sshbuf_put(encoded, XMSS_MAGIC, sizeof(XMSS_MAGIC))) != 0 ||
931 (r = sshbuf_put_u32(encoded, state->idx)) != 0)
932 goto out;
933
934 /* padded state will be encrypted */
935 if ((r = sshbuf_putb(padded, b)) != 0)
936 goto out;
937 i = 0;
938 while (sshbuf_len(padded) % blocksize) {
939 if ((r = sshbuf_put_u8(padded, ++i & 0xff)) != 0)
940 goto out;
941 }
942 encrypted_len = sshbuf_len(padded);
943
944 /* header including the length of state is used as AAD */
945 if ((r = sshbuf_put_u32(encoded, encrypted_len)) != 0)
946 goto out;
947 aadlen = sshbuf_len(encoded);
948
949 /* concat header and state */
950 if ((r = sshbuf_putb(encoded, padded)) != 0)
951 goto out;
952
953 /* reserve space for encryption of encoded data plus auth tag */
954 /* encrypt at offset addlen */
955 if ((r = sshbuf_reserve(encrypted,
956 encrypted_len + aadlen + authlen, &cp)) != 0 ||
957 (r = cipher_init(&ciphercontext, cipher, key, keylen,
958 iv, ivlen, 1)) != 0 ||
959 (r = cipher_crypt(ciphercontext, 0, cp, sshbuf_ptr(encoded),
960 encrypted_len, aadlen, authlen)) != 0)
961 goto out;
962
963 /* success */
964 r = 0;
965 out:
966 if (retp != NULL) {
967 *retp = encrypted;
968 encrypted = NULL;
969 }
970 sshbuf_free(padded);
971 sshbuf_free(encoded);
972 sshbuf_free(encrypted);
973 cipher_free(ciphercontext);
974 free(iv);
975 return r;
976}
977
978int
979sshkey_xmss_decrypt_state(const struct sshkey *k, struct sshbuf *encoded,
980 struct sshbuf **retp)
981{
982 struct ssh_xmss_state *state = k->xmss_state;
983 struct sshbuf *copy = NULL, *decrypted = NULL;
984 struct sshcipher_ctx *ciphercontext = NULL;
985 const struct sshcipher *cipher = NULL;
986 u_char *key, *iv = NULL, *dp;
987 size_t keylen, ivlen, authlen, aadlen;
988 u_int blocksize, encrypted_len, index;
989 int r = SSH_ERR_INTERNAL_ERROR;
990
991 if (retp != NULL)
992 *retp = NULL;
993 if (state == NULL ||
994 state->enc_keyiv == NULL ||
995 state->enc_ciphername == NULL)
996 return SSH_ERR_INTERNAL_ERROR;
997 if ((cipher = cipher_by_name(state->enc_ciphername)) == NULL) {
998 r = SSH_ERR_INVALID_FORMAT;
999 goto out;
1000 }
1001 blocksize = cipher_blocksize(cipher);
1002 keylen = cipher_keylen(cipher);
1003 ivlen = cipher_ivlen(cipher);
1004 authlen = cipher_authlen(cipher);
1005 if (state->enc_keyiv_len != keylen + ivlen) {
1006 r = SSH_ERR_INTERNAL_ERROR;
1007 goto out;
1008 }
1009 key = state->enc_keyiv;
1010
1011 if ((copy = sshbuf_fromb(encoded)) == NULL ||
1012 (decrypted = sshbuf_new()) == NULL ||
1013 (iv = malloc(ivlen)) == NULL) {
1014 r = SSH_ERR_ALLOC_FAIL;
1015 goto out;
1016 }
1017
1018 /* check magic */
1019 if (sshbuf_len(encoded) < sizeof(XMSS_MAGIC) ||
1020 memcmp(sshbuf_ptr(encoded), XMSS_MAGIC, sizeof(XMSS_MAGIC))) {
1021 r = SSH_ERR_INVALID_FORMAT;
1022 goto out;
1023 }
1024 /* parse public portion */
1025 if ((r = sshbuf_consume(encoded, sizeof(XMSS_MAGIC))) != 0 ||
1026 (r = sshbuf_get_u32(encoded, &index)) != 0 ||
1027 (r = sshbuf_get_u32(encoded, &encrypted_len)) != 0)
1028 goto out;
1029
1030 /* check size of encrypted key blob */
1031 if (encrypted_len < blocksize || (encrypted_len % blocksize) != 0) {
1032 r = SSH_ERR_INVALID_FORMAT;
1033 goto out;
1034 }
1035 /* check that an appropriate amount of auth data is present */
djm@openbsd.orga546b172019-10-09 00:02:57 +00001036 if (sshbuf_len(encoded) < authlen ||
1037 sshbuf_len(encoded) - authlen < encrypted_len) {
markus@openbsd.org1b11ea72018-02-23 15:58:37 +00001038 r = SSH_ERR_INVALID_FORMAT;
1039 goto out;
1040 }
1041
1042 aadlen = sshbuf_len(copy) - sshbuf_len(encoded);
1043
1044 /* replace first 4 bytes of IV with index to ensure uniqueness */
1045 memcpy(iv, key + keylen, ivlen);
1046 POKE_U32(iv, index);
1047
1048 /* decrypt private state of key */
1049 if ((r = sshbuf_reserve(decrypted, aadlen + encrypted_len, &dp)) != 0 ||
1050 (r = cipher_init(&ciphercontext, cipher, key, keylen,
1051 iv, ivlen, 0)) != 0 ||
1052 (r = cipher_crypt(ciphercontext, 0, dp, sshbuf_ptr(copy),
1053 encrypted_len, aadlen, authlen)) != 0)
1054 goto out;
1055
1056 /* there should be no trailing data */
1057 if ((r = sshbuf_consume(encoded, encrypted_len + authlen)) != 0)
1058 goto out;
1059 if (sshbuf_len(encoded) != 0) {
1060 r = SSH_ERR_INVALID_FORMAT;
1061 goto out;
1062 }
1063
1064 /* remove AAD */
1065 if ((r = sshbuf_consume(decrypted, aadlen)) != 0)
1066 goto out;
1067 /* XXX encrypted includes unchecked padding */
1068
1069 /* success */
1070 r = 0;
1071 if (retp != NULL) {
1072 *retp = decrypted;
1073 decrypted = NULL;
1074 }
1075 out:
1076 cipher_free(ciphercontext);
1077 sshbuf_free(copy);
1078 sshbuf_free(decrypted);
1079 free(iv);
1080 return r;
1081}
1082
1083u_int32_t
1084sshkey_xmss_signatures_left(const struct sshkey *k)
1085{
1086 struct ssh_xmss_state *state = k->xmss_state;
1087 u_int32_t idx;
1088
1089 if (sshkey_type_plain(k->type) == KEY_XMSS && state &&
1090 state->maxidx) {
1091 idx = k->xmss_sk ? PEEK_U32(k->xmss_sk) : state->idx;
1092 if (idx < state->maxidx)
1093 return state->maxidx - idx;
1094 }
1095 return 0;
1096}
1097
1098int
1099sshkey_xmss_enable_maxsign(struct sshkey *k, u_int32_t maxsign)
1100{
1101 struct ssh_xmss_state *state = k->xmss_state;
1102
1103 if (sshkey_type_plain(k->type) != KEY_XMSS)
1104 return SSH_ERR_INVALID_ARGUMENT;
1105 if (maxsign == 0)
1106 return 0;
1107 if (state->idx + maxsign < state->idx)
1108 return SSH_ERR_INVALID_ARGUMENT;
1109 state->maxidx = state->idx + maxsign;
1110 return 0;
1111}
Darren Tuckera10d8552018-02-27 14:45:17 +11001112#endif /* WITH_XMSS */