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