static u_char * derive_key(Kex *kex, int id, u_int need, u_char *hash, u_int hashlen, const u_char *shared_secret, u_int slen) { Buffer b; struct ssh_digest_ctx *hashctx; char c = id; u_int have; size_t mdsz; u_char *digest; if ((mdsz = ssh_digest_bytes(kex->hash_alg)) == 0) fatal("bad kex md size %zu", mdsz); digest = xmalloc(roundup(need, mdsz)); buffer_init(&b); buffer_append(&b, shared_secret, slen); /* K1 = HASH(K || H || "A" || session_id) */ if ((hashctx = ssh_digest_start(kex->hash_alg)) == NULL) fatal("%s: ssh_digest_start failed", __func__); if (ssh_digest_update_buffer(hashctx, &b) != 0 || ssh_digest_update(hashctx, hash, hashlen) != 0 || ssh_digest_update(hashctx, &c, 1) != 0 || ssh_digest_update(hashctx, kex->session_id, kex->session_id_len) != 0) fatal("%s: ssh_digest_update failed", __func__); if (ssh_digest_final(hashctx, digest, mdsz) != 0) fatal("%s: ssh_digest_final failed", __func__); ssh_digest_free(hashctx); /* * expand key: * Kn = HASH(K || H || K1 || K2 || ... || Kn-1) * Key = K1 || K2 || ... || Kn */ for (have = mdsz; need > have; have += mdsz) { if ((hashctx = ssh_digest_start(kex->hash_alg)) == NULL) fatal("%s: ssh_digest_start failed", __func__); if (ssh_digest_update_buffer(hashctx, &b) != 0 || ssh_digest_update(hashctx, hash, hashlen) != 0 || ssh_digest_update(hashctx, digest, have) != 0) fatal("%s: ssh_digest_update failed", __func__); if (ssh_digest_final(hashctx, digest + have, mdsz) != 0) fatal("%s: ssh_digest_final failed", __func__); ssh_digest_free(hashctx); } buffer_free(&b); #ifdef DEBUG_KEX fprintf(stderr, "key '%c'== ", c); dump_digest("key", digest, need); #endif return digest; }
int derive_ssh1_session_id(BIGNUM *host_modulus, BIGNUM *server_modulus, u_int8_t cookie[8], u_int8_t id[16]) { u_int8_t nbuf[2048], obuf[SSH_DIGEST_MAX_LENGTH]; struct ssh_digest_ctx *hashctx = NULL; size_t len; int r; len = BN_num_bytes(host_modulus); if (len < (512 / 8) || (u_int)len > sizeof(nbuf)) return SSH_ERR_KEY_BITS_MISMATCH; if (BN_bn2bin(host_modulus, nbuf) <= 0 || (hashctx = ssh_digest_start(SSH_DIGEST_MD5)) == NULL || ssh_digest_update(hashctx, nbuf, len) != 0 || ssh_digest_update(hashctx, cookie, 8) != 0 || ssh_digest_final(hashctx, obuf, sizeof(obuf)) != 0) { r = SSH_ERR_LIBCRYPTO_ERROR; goto out; } memcpy(id, obuf, ssh_digest_bytes(SSH_DIGEST_MD5)); r = 0; out: ssh_digest_free(hashctx); explicit_bzero(nbuf, sizeof(nbuf)); explicit_bzero(obuf, sizeof(obuf)); return r; }
int ssh_hmac_final(struct ssh_hmac_ctx *ctx, u_char *d, size_t dlen) { size_t len; len = ssh_digest_bytes(ctx->alg); if (dlen < len || ssh_digest_final(ctx->digest, ctx->buf, len)) return -1; /* switch to octx */ if (ssh_digest_copy_state(ctx->octx, ctx->digest) < 0 || ssh_digest_update(ctx->digest, ctx->buf, len) < 0 || ssh_digest_final(ctx->digest, d, dlen) < 0) return -1; return 0; }
void derive_ssh1_session_id(BIGNUM *host_modulus, BIGNUM *server_modulus, u_int8_t cookie[8], u_int8_t id[16]) { u_int8_t nbuf[2048], obuf[SSH_DIGEST_MAX_LENGTH]; int len; struct ssh_digest_ctx *hashctx; if ((hashctx = ssh_digest_start(SSH_DIGEST_MD5)) == NULL) fatal("%s: ssh_digest_start", __func__); len = BN_num_bytes(host_modulus); if (len < (512 / 8) || (u_int)len > sizeof(nbuf)) fatal("%s: bad host modulus (len %d)", __func__, len); BN_bn2bin(host_modulus, nbuf); if (ssh_digest_update(hashctx, nbuf, len) != 0) fatal("%s: ssh_digest_update failed", __func__); len = BN_num_bytes(server_modulus); if (len < (512 / 8) || (u_int)len > sizeof(nbuf)) fatal("%s: bad server modulus (len %d)", __func__, len); BN_bn2bin(server_modulus, nbuf); if (ssh_digest_update(hashctx, nbuf, len) != 0 || ssh_digest_update(hashctx, cookie, 8) != 0) fatal("%s: ssh_digest_update failed", __func__); if (ssh_digest_final(hashctx, obuf, sizeof(obuf)) != 0) fatal("%s: ssh_digest_final failed", __func__); memcpy(id, obuf, ssh_digest_bytes(SSH_DIGEST_MD5)); explicit_bzero(nbuf, sizeof(nbuf)); explicit_bzero(obuf, sizeof(obuf)); }
int ssh_digest_memory(int alg, const void *m, size_t mlen, u_char *d, size_t dlen) { struct ssh_digest_ctx *ctx = ssh_digest_start(alg); if (ctx == NULL) return SSH_ERR_INVALID_ARGUMENT; if (ssh_digest_update(ctx, m, mlen) != 0 || ssh_digest_final(ctx, d, dlen) != 0) return SSH_ERR_INVALID_ARGUMENT; ssh_digest_free(ctx); return 0; }
/* * Computes the proper response to a RSA challenge, and sends the response to * the server. */ static void respond_to_rsa_challenge(BIGNUM * challenge, RSA * prv) { u_char buf[32], response[16]; struct ssh_digest_ctx *md; int i, len; /* Decrypt the challenge using the private key. */ /* XXX think about Bleichenbacher, too */ if (rsa_private_decrypt(challenge, challenge, prv) != 0) packet_disconnect( "respond_to_rsa_challenge: rsa_private_decrypt failed"); /* Compute the response. */ /* The response is MD5 of decrypted challenge plus session id. */ len = BN_num_bytes(challenge); if (len <= 0 || (u_int)len > sizeof(buf)) packet_disconnect( "respond_to_rsa_challenge: bad challenge length %d", len); memset(buf, 0, sizeof(buf)); BN_bn2bin(challenge, buf + sizeof(buf) - len); if ((md = ssh_digest_start(SSH_DIGEST_MD5)) == NULL || ssh_digest_update(md, buf, 32) < 0 || ssh_digest_update(md, session_id, 16) < 0 || ssh_digest_final(md, response, sizeof(response)) < 0) fatal("%s: md5 failed", __func__); ssh_digest_free(md); debug("Sending response to host key RSA challenge."); /* Send the response back to the server. */ packet_start(SSH_CMSG_AUTH_RSA_RESPONSE); for (i = 0; i < 16; i++) packet_put_char(response[i]); packet_send(); packet_write_wait(); explicit_bzero(buf, sizeof(buf)); explicit_bzero(response, sizeof(response)); explicit_bzero(&md, sizeof(md)); }
static int derive_key(struct ssh *ssh, int id, u_int need, u_char *hash, u_int hashlen, const u_char *shared_secret, u_int slen, u_char **keyp) { struct kex *kex = ssh->kex; struct sshbuf *b = NULL; struct ssh_digest_ctx *hashctx = NULL; char c = id; u_int have; size_t mdsz; u_char *digest; int r; if ((mdsz = ssh_digest_bytes(kex->hash_alg)) == 0) return SSH_ERR_INVALID_ARGUMENT; if ((digest = calloc(1, roundup(need, mdsz))) == NULL || (b = sshbuf_new()) == NULL) { r = SSH_ERR_ALLOC_FAIL; goto out; } if ((r = sshbuf_put(b, shared_secret, slen)) < 0) goto out; /* K1 = HASH(K || H || "A" || session_id) */ if ((hashctx = ssh_digest_start(kex->hash_alg)) == NULL || ssh_digest_update_buffer(hashctx, b) != 0 || ssh_digest_update(hashctx, hash, hashlen) != 0 || ssh_digest_update(hashctx, &c, 1) != 0 || ssh_digest_update(hashctx, kex->session_id, kex->session_id_len) != 0 || ssh_digest_final(hashctx, digest, mdsz) != 0) { r = SSH_ERR_LIBCRYPTO_ERROR; goto out; } ssh_digest_free(hashctx); hashctx = NULL; /* * expand key: * Kn = HASH(K || H || K1 || K2 || ... || Kn-1) * Key = K1 || K2 || ... || Kn */ for (have = mdsz; need > have; have += mdsz) { if ((hashctx = ssh_digest_start(kex->hash_alg)) == NULL || ssh_digest_update_buffer(hashctx, b) != 0 || ssh_digest_update(hashctx, hash, hashlen) != 0 || ssh_digest_update(hashctx, digest, have) != 0 || ssh_digest_final(hashctx, digest + have, mdsz) != 0) { r = SSH_ERR_LIBCRYPTO_ERROR; goto out; } ssh_digest_free(hashctx); hashctx = NULL; } #ifdef DEBUG_KEX fprintf(stderr, "key '%c'== ", c); dump_digest("key", digest, need); #endif *keyp = digest; digest = NULL; r = 0; out: if (digest) free(digest); if (b) sshbuf_free(b); ssh_digest_free(hashctx); return r; }
// TODO: can we send multiple authrequests at the same time, so that we don’t // need multiple round-trips but still support multiple security keys static void input_userauth_u2f_info_response(int type, u_int32_t seq, void *ctxt) { int authenticated = 0; Authctxt *authctxt = ctxt; u_char digest[ssh_digest_bytes(SSH_DIGEST_SHA256)]; debug("input_userauth_u2f_info_response\n"); u_int len; char *clientdata; u_char *cdecoded; int cdecodedlen; char *resp = packet_get_string(&len); debug("u2f resp len (server): %d\n", len); debug("u2f resp (server): %s\n", resp); packet_check_eom(); char *sig = extract_json_string(resp, "signatureData"); if (sig == NULL) fatal("could not extract signature"); // TODO: free sig debug("signature is *%s*", sig); if (*sig == '\0') fatal("u2f authentication failed: empty signature. Probably the key is not registered (i.e. your key handle/pubkey do not exist on the key you are using)"); // TODO: is there a macro for this size? u_char decoded[strlen(sig) * 3 / 4]; int decodedlen = urlsafe_base64_decode(sig, decoded, sizeof(decoded)); // Ensure that the user presence byte, the counter and at least one byte of // signature are present. if (decodedlen <= (sizeof(u_char) + sizeof(u_int32_t))) fatal("decoded signature too short"); if ((decoded[0] & 0x01) != 0x01) fatal("user presence bit not set"); u_int32_t counter = ntohl(*((u_int32_t*)&decoded[1])); debug("usage counter = %d\n", counter); struct sha_digest_ctx *sha256ctx = ssh_digest_start(SSH_DIGEST_SHA256); u2f_sha256(digest, appid, strlen(appid)); ssh_digest_update(sha256ctx, digest, sizeof(digest)); ssh_digest_update(sha256ctx, decoded, sizeof(u_char)); ssh_digest_update(sha256ctx, decoded+1, 4 * sizeof(u_char)); if ((clientdata = extract_json_string(resp, "clientData")) == NULL) { fatal("U2F response JSON lacks the \"clientData\" key."); } cdecoded = xmalloc(strlen(clientdata) * 3 / 4); cdecodedlen = urlsafe_base64_decode(clientdata, cdecoded, strlen(clientdata) * 3 / 4); u2f_sha256(digest, cdecoded, cdecodedlen); ssh_digest_update(sha256ctx, digest, sizeof(digest)); ssh_digest_final(sha256ctx, digest, sizeof(digest)); debug("hashed sig"); authenticated = PRIVSEP(verify_u2f_user( authctxt->u2f_key, digest, sizeof(digest), decoded+5, decodedlen-5)); authctxt->postponed = 0; dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE, NULL); userauth_finish(authctxt, authenticated, "u2f", NULL); }
void u2f_sha256(u_char *dest, u_char *src, size_t srclen) { struct ssh_digest_ctx *ctx = ssh_digest_start(SSH_DIGEST_SHA256); ssh_digest_update(ctx, src, srclen); ssh_digest_final(ctx, dest, ssh_digest_bytes(SSH_DIGEST_SHA256)); }