int mac_compute(Mac *mac, u_int32_t seqno, u_char *data, int datalen, u_char *digest, size_t dlen) { static u_char m[MAC_DIGEST_LEN_MAX]; u_char b[4], nonce[8]; if (mac->mac_len > sizeof(m)) return SSH_ERR_INTERNAL_ERROR; switch (mac->type) { case SSH_EVP: POKE_U32(b, seqno); /* reset HMAC context */ if (HMAC_Init(&mac->evp_ctx, NULL, 0, NULL) != 1 || HMAC_Update(&mac->evp_ctx, b, sizeof(b)) != 1 || HMAC_Update(&mac->evp_ctx, data, datalen) != 1 || HMAC_Final(&mac->evp_ctx, m, NULL) != 1) return SSH_ERR_LIBCRYPTO_ERROR; break; case SSH_UMAC: POKE_U64(nonce, seqno); umac_update(mac->umac_ctx, data, datalen); umac_final(mac->umac_ctx, m, nonce); break; default: return SSH_ERR_INVALID_ARGUMENT; } if (digest != NULL) { if (dlen > mac->mac_len) dlen = mac->mac_len; memcpy(digest, m, dlen); } return 0; }
int sshbuf_put_bignum2_bytes(struct sshbuf *buf, const void *v, size_t len) { u_char *d; const u_char *s = (const u_char *)v; int r, prepend; if (len > SSHBUF_SIZE_MAX - 5) { SSHBUF_DBG(("SSH_ERR_NO_BUFFER_SPACE")); return SSH_ERR_NO_BUFFER_SPACE; } /* Skip leading zero bytes */ for (; len > 0 && *s == 0; len--, s++) ; /* * If most significant bit is set then prepend a zero byte to * avoid interpretation as a negative number. */ prepend = len > 0 && (s[0] & 0x80) != 0; if ((r = sshbuf_reserve(buf, len + 4 + prepend, &d)) < 0) return r; POKE_U32(d, len + prepend); if (prepend) d[4] = 0; if (len != 0) memcpy(d + 4 + prepend, s, len); return 0; }
int sshkey_xmss_deserialize_state(struct sshkey *k, struct sshbuf *b) { struct ssh_xmss_state *state = k->xmss_state; treehash_inst *th; u_int32_t i, lh, node; size_t ls, lsl, la, lk, ln, lr; char *magic; int r; if (state == NULL) return SSH_ERR_INVALID_ARGUMENT; if (k->xmss_sk == NULL) return SSH_ERR_INVALID_ARGUMENT; if ((state->treehash = calloc(num_treehash(state), sizeof(treehash_inst))) == NULL) return SSH_ERR_ALLOC_FAIL; if ((r = sshbuf_get_cstring(b, &magic, NULL)) != 0 || (r = sshbuf_get_u32(b, &state->idx)) != 0 || (r = sshbuf_get_string(b, &state->stack, &ls)) != 0 || (r = sshbuf_get_u32(b, &state->stackoffset)) != 0 || (r = sshbuf_get_string(b, &state->stacklevels, &lsl)) != 0 || (r = sshbuf_get_string(b, &state->auth, &la)) != 0 || (r = sshbuf_get_string(b, &state->keep, &lk)) != 0 || (r = sshbuf_get_string(b, &state->th_nodes, &ln)) != 0 || (r = sshbuf_get_string(b, &state->retain, &lr)) != 0 || (r = sshbuf_get_u32(b, &lh)) != 0) return r; if (strcmp(magic, SSH_XMSS_K2_MAGIC) != 0) return SSH_ERR_INVALID_ARGUMENT; /* XXX check stackoffset */ if (ls != num_stack(state) || lsl != num_stacklevels(state) || la != num_auth(state) || lk != num_keep(state) || ln != num_th_nodes(state) || lr != num_retain(state) || lh != num_treehash(state)) return SSH_ERR_INVALID_ARGUMENT; for (i = 0; i < num_treehash(state); i++) { th = &state->treehash[i]; if ((r = sshbuf_get_u32(b, &th->h)) != 0 || (r = sshbuf_get_u32(b, &th->next_idx)) != 0 || (r = sshbuf_get_u32(b, &th->stackusage)) != 0 || (r = sshbuf_get_u8(b, &th->completed)) != 0 || (r = sshbuf_get_u32(b, &node)) != 0) return r; if (node < num_th_nodes(state)) th->node = &state->th_nodes[node]; } POKE_U32(k->xmss_sk, state->idx); xmss_set_bds_state(&state->bds, state->stack, state->stackoffset, state->stacklevels, state->auth, state->keep, state->treehash, state->retain, 0); return 0; }
int sshbuf_put_u32(struct sshbuf *buf, u_int32_t val) { u_char *p; int r; if ((r = sshbuf_reserve(buf, 4, &p)) < 0) return r; POKE_U32(p, val); return 0; }
static void send_msg(struct sshbuf *m) { u_char buf[4]; size_t mlen = sshbuf_len(m); int r; POKE_U32(buf, mlen); if (atomicio(vwrite, fd, buf, 4) != 4 || atomicio(vwrite, fd, (u_char *)sshbuf_ptr(m), sshbuf_len(m)) != sshbuf_len(m)) error("write to helper failed"); if ((r = sshbuf_consume(m, mlen)) != 0) fatal("%s: buffer error: %s", __func__, ssh_err(r)); }
int sshbuf_put_string(struct sshbuf *buf, const void *v, size_t len) { u_char *d; int r; if (len > 0xFFFFFFFF - 4) { SSHBUF_DBG(("SSH_ERR_NO_BUFFER_SPACE")); return SSH_ERR_NO_BUFFER_SPACE; } if ((r = sshbuf_reserve(buf, len + 4, &d)) < 0) return r; POKE_U32(d, len); memcpy(d + 4, v, len); return 0; }
int sshkey_xmss_decrypt_state(const struct sshkey *k, struct sshbuf *encoded, struct sshbuf **retp) { struct ssh_xmss_state *state = k->xmss_state; struct sshbuf *copy = NULL, *decrypted = NULL; struct sshcipher_ctx *ciphercontext = NULL; const struct sshcipher *cipher = NULL; u_char *key, *iv = NULL, *dp; size_t keylen, ivlen, authlen, aadlen; u_int blocksize, encrypted_len, index; int r = SSH_ERR_INTERNAL_ERROR; if (retp != NULL) *retp = NULL; if (state == NULL || state->enc_keyiv == NULL || state->enc_ciphername == NULL) return SSH_ERR_INTERNAL_ERROR; if ((cipher = cipher_by_name(state->enc_ciphername)) == NULL) { r = SSH_ERR_INVALID_FORMAT; goto out; } blocksize = cipher_blocksize(cipher); keylen = cipher_keylen(cipher); ivlen = cipher_ivlen(cipher); authlen = cipher_authlen(cipher); if (state->enc_keyiv_len != keylen + ivlen) { r = SSH_ERR_INTERNAL_ERROR; goto out; } key = state->enc_keyiv; if ((copy = sshbuf_fromb(encoded)) == NULL || (decrypted = sshbuf_new()) == NULL || (iv = malloc(ivlen)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } /* check magic */ if (sshbuf_len(encoded) < sizeof(XMSS_MAGIC) || memcmp(sshbuf_ptr(encoded), XMSS_MAGIC, sizeof(XMSS_MAGIC))) { r = SSH_ERR_INVALID_FORMAT; goto out; } /* parse public portion */ if ((r = sshbuf_consume(encoded, sizeof(XMSS_MAGIC))) != 0 || (r = sshbuf_get_u32(encoded, &index)) != 0 || (r = sshbuf_get_u32(encoded, &encrypted_len)) != 0) goto out; /* check size of encrypted key blob */ if (encrypted_len < blocksize || (encrypted_len % blocksize) != 0) { r = SSH_ERR_INVALID_FORMAT; goto out; } /* check that an appropriate amount of auth data is present */ if (sshbuf_len(encoded) < encrypted_len + authlen) { r = SSH_ERR_INVALID_FORMAT; goto out; } aadlen = sshbuf_len(copy) - sshbuf_len(encoded); /* replace first 4 bytes of IV with index to ensure uniqueness */ memcpy(iv, key + keylen, ivlen); POKE_U32(iv, index); /* decrypt private state of key */ if ((r = sshbuf_reserve(decrypted, aadlen + encrypted_len, &dp)) != 0 || (r = cipher_init(&ciphercontext, cipher, key, keylen, iv, ivlen, 0)) != 0 || (r = cipher_crypt(ciphercontext, 0, dp, sshbuf_ptr(copy), encrypted_len, aadlen, authlen)) != 0) goto out; /* there should be no trailing data */ if ((r = sshbuf_consume(encoded, encrypted_len + authlen)) != 0) goto out; if (sshbuf_len(encoded) != 0) { r = SSH_ERR_INVALID_FORMAT; goto out; } /* remove AAD */ if ((r = sshbuf_consume(decrypted, aadlen)) != 0) goto out; /* XXX encrypted includes unchecked padding */ /* success */ r = 0; if (retp != NULL) { *retp = decrypted; decrypted = NULL; } out: cipher_free(ciphercontext); sshbuf_free(copy); sshbuf_free(decrypted); free(iv); return r; }
int sshkey_xmss_encrypt_state(const struct sshkey *k, struct sshbuf *b, struct sshbuf **retp) { struct ssh_xmss_state *state = k->xmss_state; struct sshbuf *encrypted = NULL, *encoded = NULL, *padded = NULL; struct sshcipher_ctx *ciphercontext = NULL; const struct sshcipher *cipher; u_char *cp, *key, *iv = NULL; size_t i, keylen, ivlen, blocksize, authlen, encrypted_len, aadlen; int r = SSH_ERR_INTERNAL_ERROR; if (retp != NULL) *retp = NULL; if (state == NULL || state->enc_keyiv == NULL || state->enc_ciphername == NULL) return SSH_ERR_INTERNAL_ERROR; if ((cipher = cipher_by_name(state->enc_ciphername)) == NULL) { r = SSH_ERR_INTERNAL_ERROR; goto out; } blocksize = cipher_blocksize(cipher); keylen = cipher_keylen(cipher); ivlen = cipher_ivlen(cipher); authlen = cipher_authlen(cipher); if (state->enc_keyiv_len != keylen + ivlen) { r = SSH_ERR_INVALID_FORMAT; goto out; } key = state->enc_keyiv; if ((encrypted = sshbuf_new()) == NULL || (encoded = sshbuf_new()) == NULL || (padded = sshbuf_new()) == NULL || (iv = malloc(ivlen)) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } /* replace first 4 bytes of IV with index to ensure uniqueness */ memcpy(iv, key + keylen, ivlen); POKE_U32(iv, state->idx); if ((r = sshbuf_put(encoded, XMSS_MAGIC, sizeof(XMSS_MAGIC))) != 0 || (r = sshbuf_put_u32(encoded, state->idx)) != 0) goto out; /* padded state will be encrypted */ if ((r = sshbuf_putb(padded, b)) != 0) goto out; i = 0; while (sshbuf_len(padded) % blocksize) { if ((r = sshbuf_put_u8(padded, ++i & 0xff)) != 0) goto out; } encrypted_len = sshbuf_len(padded); /* header including the length of state is used as AAD */ if ((r = sshbuf_put_u32(encoded, encrypted_len)) != 0) goto out; aadlen = sshbuf_len(encoded); /* concat header and state */ if ((r = sshbuf_putb(encoded, padded)) != 0) goto out; /* reserve space for encryption of encoded data plus auth tag */ /* encrypt at offset addlen */ if ((r = sshbuf_reserve(encrypted, encrypted_len + aadlen + authlen, &cp)) != 0 || (r = cipher_init(&ciphercontext, cipher, key, keylen, iv, ivlen, 1)) != 0 || (r = cipher_crypt(ciphercontext, 0, cp, sshbuf_ptr(encoded), encrypted_len, aadlen, authlen)) != 0) goto out; /* success */ r = 0; out: if (retp != NULL) { *retp = encrypted; encrypted = NULL; } sshbuf_free(padded); sshbuf_free(encoded); sshbuf_free(encrypted); cipher_free(ciphercontext); free(iv); return r; }
int sshkey_xmss_update_state(const struct sshkey *k, sshkey_printfn *pr) { struct ssh_xmss_state *state = k->xmss_state; struct sshbuf *b = NULL, *enc = NULL; u_int32_t idx = 0; unsigned char buf[4]; char *filename = NULL; char *statefile = NULL, *ostatefile = NULL, *nstatefile = NULL; int fd = -1; int ret = SSH_ERR_INVALID_ARGUMENT; if (state == NULL || !state->allow_update) return ret; if (state->maxidx) { /* no update since the number of signatures is limited */ ret = 0; goto done; } idx = PEEK_U32(k->xmss_sk); if (idx == state->idx) { /* no signature happened, no need to update */ ret = 0; goto done; } else if (idx != state->idx + 1) { PRINT("%s: more than one signature happened: idx %u state %u", __func__, idx, state->idx); goto done; } state->idx = idx; if ((filename = k->xmss_filename) == NULL) goto done; if (asprintf(&statefile, "%s.state", filename) < 0 || asprintf(&ostatefile, "%s.ostate", filename) < 0 || asprintf(&nstatefile, "%s.nstate", filename) < 0) { ret = SSH_ERR_ALLOC_FAIL; goto done; } unlink(nstatefile); if ((b = sshbuf_new()) == NULL) { ret = SSH_ERR_ALLOC_FAIL; goto done; } if ((ret = sshkey_xmss_serialize_state(k, b)) != 0) { PRINT("%s: SERLIALIZE FAILED: %d", __func__, ret); goto done; } if ((ret = sshkey_xmss_encrypt_state(k, b, &enc)) != 0) { PRINT("%s: ENCRYPT FAILED: %d", __func__, ret); goto done; } if ((fd = open(nstatefile, O_CREAT|O_WRONLY|O_EXCL, 0600)) < 0) { ret = SSH_ERR_SYSTEM_ERROR; PRINT("%s: open new state file: %s", __func__, nstatefile); goto done; } POKE_U32(buf, sshbuf_len(enc)); if (atomicio(vwrite, fd, buf, sizeof(buf)) != sizeof(buf)) { ret = SSH_ERR_SYSTEM_ERROR; PRINT("%s: write new state file hdr: %s", __func__, nstatefile); close(fd); goto done; } if (atomicio(vwrite, fd, sshbuf_mutable_ptr(enc), sshbuf_len(enc)) != sshbuf_len(enc)) { ret = SSH_ERR_SYSTEM_ERROR; PRINT("%s: write new state file data: %s", __func__, nstatefile); close(fd); goto done; } if (fsync(fd) < 0) { ret = SSH_ERR_SYSTEM_ERROR; PRINT("%s: sync new state file: %s", __func__, nstatefile); close(fd); goto done; } if (close(fd) < 0) { ret = SSH_ERR_SYSTEM_ERROR; PRINT("%s: close new state file: %s", __func__, nstatefile); goto done; } if (state->have_state) { unlink(ostatefile); if (link(statefile, ostatefile)) { ret = SSH_ERR_SYSTEM_ERROR; PRINT("%s: backup state %s to %s", __func__, statefile, ostatefile); goto done; } } if (rename(nstatefile, statefile) < 0) { ret = SSH_ERR_SYSTEM_ERROR; PRINT("%s: rename %s to %s", __func__, nstatefile, statefile); goto done; } ret = 0; done: if (state->lockfd != -1) { close(state->lockfd); state->lockfd = -1; } if (nstatefile) unlink(nstatefile); free(statefile); free(ostatefile); free(nstatefile); sshbuf_free(b); sshbuf_free(enc); return ret; }