|
|
@@ -0,0 +1,234 @@
|
|
|
+/*
|
|
|
+ * Implementation of HMAC (RFC 2104) for PuTTY, in a general form that
|
|
|
+ * can wrap any underlying hash function.
|
|
|
+ */
|
|
|
+
|
|
|
+#include "ssh.h"
|
|
|
+
|
|
|
+struct hmac {
|
|
|
+ const ssh_hashalg *hashalg;
|
|
|
+ ssh_hash *h_outer, *h_inner, *h_live;
|
|
|
+ bool keyed;
|
|
|
+ uint8_t *digest;
|
|
|
+ strbuf *text_name;
|
|
|
+ ssh2_mac mac;
|
|
|
+};
|
|
|
+
|
|
|
+struct hmac_extra {
|
|
|
+ const ssh_hashalg *hashalg_base;
|
|
|
+ const char *suffix;
|
|
|
+};
|
|
|
+
|
|
|
+static ssh2_mac *hmac_new(const ssh2_macalg *alg, ssh_cipher *cipher)
|
|
|
+{
|
|
|
+ struct hmac *ctx = snew(struct hmac);
|
|
|
+ const struct hmac_extra *extra = (const struct hmac_extra *)alg->extra;
|
|
|
+
|
|
|
+ ctx->h_outer = ssh_hash_new(extra->hashalg_base);
|
|
|
+ /* In case that hashalg was a selector vtable, we'll now switch to
|
|
|
+ * using whatever real one it selected, for all future purposes. */
|
|
|
+ ctx->hashalg = ssh_hash_alg(ctx->h_outer);
|
|
|
+ ctx->h_inner = ssh_hash_new(ctx->hashalg);
|
|
|
+ ctx->h_live = ssh_hash_new(ctx->hashalg);
|
|
|
+ ctx->keyed = false;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * HMAC is not well defined as a wrapper on an absolutely general
|
|
|
+ * hash function; it expects that the function it's wrapping will
|
|
|
+ * consume data in fixed-size blocks, and it's partially defined
|
|
|
+ * in terms of that block size. So we insist that the hash we're
|
|
|
+ * given must have defined a meaningful block size.
|
|
|
+ */
|
|
|
+ assert(ctx->hashalg->blocklen);
|
|
|
+
|
|
|
+ ctx->digest = snewn(ctx->hashalg->hlen, uint8_t);
|
|
|
+
|
|
|
+ ctx->text_name = strbuf_new();
|
|
|
+ strbuf_catf(ctx->text_name, "HMAC-%s%s",
|
|
|
+ ctx->hashalg->text_name, extra->suffix);
|
|
|
+
|
|
|
+ ctx->mac.vt = alg;
|
|
|
+ BinarySink_DELEGATE_INIT(&ctx->mac, ctx->h_live);
|
|
|
+
|
|
|
+ return &ctx->mac;
|
|
|
+}
|
|
|
+
|
|
|
+static void hmac_free(ssh2_mac *mac)
|
|
|
+{
|
|
|
+ struct hmac *ctx = container_of(mac, struct hmac, mac);
|
|
|
+ const struct hmac_extra *extra = (const struct hmac_extra *)mac->vt->extra;
|
|
|
+
|
|
|
+ ssh_hash_free(ctx->h_outer);
|
|
|
+ ssh_hash_free(ctx->h_inner);
|
|
|
+ ssh_hash_free(ctx->h_live);
|
|
|
+ smemclr(ctx->digest, ctx->hashalg->hlen);
|
|
|
+ sfree(ctx->digest);
|
|
|
+ strbuf_free(ctx->text_name);
|
|
|
+
|
|
|
+ smemclr(ctx, sizeof(*ctx));
|
|
|
+ sfree(ctx);
|
|
|
+}
|
|
|
+
|
|
|
+#define PAD_OUTER 0x5C
|
|
|
+#define PAD_INNER 0x36
|
|
|
+
|
|
|
+static void hmac_key(ssh2_mac *mac, ptrlen key)
|
|
|
+{
|
|
|
+ struct hmac *ctx = container_of(mac, struct hmac, mac);
|
|
|
+ const struct hmac_extra *extra = (const struct hmac_extra *)mac->vt->extra;
|
|
|
+
|
|
|
+ const uint8_t *kp;
|
|
|
+ size_t klen;
|
|
|
+ strbuf *sb = NULL;
|
|
|
+
|
|
|
+ if (ctx->keyed) {
|
|
|
+ /*
|
|
|
+ * If we've already been keyed, throw away the existing hash
|
|
|
+ * objects and make a fresh pair to put the new key in.
|
|
|
+ */
|
|
|
+ ssh_hash_free(ctx->h_outer);
|
|
|
+ ssh_hash_free(ctx->h_inner);
|
|
|
+ ctx->h_outer = ssh_hash_new(ctx->hashalg);
|
|
|
+ ctx->h_inner = ssh_hash_new(ctx->hashalg);
|
|
|
+ }
|
|
|
+ ctx->keyed = true;
|
|
|
+
|
|
|
+ if (key.len > ctx->hashalg->blocklen) {
|
|
|
+ /*
|
|
|
+ * RFC 2104 section 2: if the key exceeds the block length of
|
|
|
+ * the underlying hash, then we start by hashing the key, and
|
|
|
+ * use that hash as the 'true' key for the HMAC construction.
|
|
|
+ */
|
|
|
+ sb = strbuf_new();
|
|
|
+ strbuf_append(sb, ctx->hashalg->hlen);
|
|
|
+
|
|
|
+ ssh_hash *htmp = ssh_hash_new(ctx->hashalg);
|
|
|
+ put_datapl(htmp, key);
|
|
|
+ ssh_hash_final(htmp, sb->u);
|
|
|
+
|
|
|
+ kp = sb->u;
|
|
|
+ klen = sb->len;
|
|
|
+ } else {
|
|
|
+ /*
|
|
|
+ * A short enough key is used as is.
|
|
|
+ */
|
|
|
+ kp = (const uint8_t *)key.ptr;
|
|
|
+ klen = key.len;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ctx->h_outer)
|
|
|
+ ssh_hash_free(ctx->h_outer);
|
|
|
+ if (ctx->h_inner)
|
|
|
+ ssh_hash_free(ctx->h_inner);
|
|
|
+
|
|
|
+ ctx->h_outer = ssh_hash_new(ctx->hashalg);
|
|
|
+ for (size_t i = 0; i < klen; i++)
|
|
|
+ put_byte(ctx->h_outer, PAD_OUTER ^ kp[i]);
|
|
|
+ for (size_t i = klen; i < ctx->hashalg->blocklen; i++)
|
|
|
+ put_byte(ctx->h_outer, PAD_OUTER);
|
|
|
+
|
|
|
+ ctx->h_inner = ssh_hash_new(ctx->hashalg);
|
|
|
+ for (size_t i = 0; i < klen; i++)
|
|
|
+ put_byte(ctx->h_inner, PAD_INNER ^ kp[i]);
|
|
|
+ for (size_t i = klen; i < ctx->hashalg->blocklen; i++)
|
|
|
+ put_byte(ctx->h_inner, PAD_INNER);
|
|
|
+
|
|
|
+ if (sb)
|
|
|
+ strbuf_free(sb);
|
|
|
+}
|
|
|
+
|
|
|
+static void hmac_start(ssh2_mac *mac)
|
|
|
+{
|
|
|
+ struct hmac *ctx = container_of(mac, struct hmac, mac);
|
|
|
+
|
|
|
+ ssh_hash_free(ctx->h_live);
|
|
|
+ ctx->h_live = ssh_hash_copy(ctx->h_inner);
|
|
|
+ BinarySink_DELEGATE_INIT(&ctx->mac, ctx->h_live);
|
|
|
+}
|
|
|
+
|
|
|
+static void hmac_genresult(ssh2_mac *mac, unsigned char *output)
|
|
|
+{
|
|
|
+ struct hmac *ctx = container_of(mac, struct hmac, mac);
|
|
|
+ const struct hmac_extra *extra = (const struct hmac_extra *)mac->vt->extra;
|
|
|
+ ssh_hash *htmp;
|
|
|
+
|
|
|
+ /* Leave h_live in place, so that the SSH-2 BPP can continue
|
|
|
+ * regenerating test results from different-length prefixes of the
|
|
|
+ * packet */
|
|
|
+ htmp = ssh_hash_copy(ctx->h_live);
|
|
|
+ ssh_hash_final(htmp, ctx->digest);
|
|
|
+
|
|
|
+ htmp = ssh_hash_copy(ctx->h_outer);
|
|
|
+ put_data(htmp, ctx->digest, ctx->hashalg->hlen);
|
|
|
+ ssh_hash_final(htmp, ctx->digest);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Some instances of HMAC truncate the output hash, so instead of
|
|
|
+ * writing it directly to 'output' we wrote it to our own
|
|
|
+ * full-length buffer, and now we copy the required amount.
|
|
|
+ */
|
|
|
+ memcpy(output, ctx->digest, mac->vt->len);
|
|
|
+ smemclr(ctx->digest, ctx->hashalg->hlen);
|
|
|
+}
|
|
|
+
|
|
|
+static const char *hmac_text_name(ssh2_mac *mac)
|
|
|
+{
|
|
|
+ struct hmac *ctx = container_of(mac, struct hmac, mac);
|
|
|
+ return ctx->text_name->s;
|
|
|
+}
|
|
|
+
|
|
|
+const struct hmac_extra ssh_hmac_sha256_extra = { &ssh_sha256, "" };
|
|
|
+const ssh2_macalg ssh_hmac_sha256 = {
|
|
|
+ hmac_new, hmac_free, hmac_key,
|
|
|
+ hmac_start, hmac_genresult, hmac_text_name,
|
|
|
+ "hmac-sha2-256", "[email protected]",
|
|
|
+ 32, 32, &ssh_hmac_sha256_extra,
|
|
|
+};
|
|
|
+
|
|
|
+const struct hmac_extra ssh_hmac_md5_extra = { &ssh_md5, "" };
|
|
|
+const ssh2_macalg ssh_hmac_md5 = {
|
|
|
+ hmac_new, hmac_free, hmac_key,
|
|
|
+ hmac_start, hmac_genresult, hmac_text_name,
|
|
|
+ "hmac-md5", "[email protected]",
|
|
|
+ 16, 16, &ssh_hmac_md5_extra,
|
|
|
+};
|
|
|
+
|
|
|
+const struct hmac_extra ssh_hmac_sha1_extra = { &ssh_sha1, "" };
|
|
|
+
|
|
|
+const ssh2_macalg ssh_hmac_sha1 = {
|
|
|
+ hmac_new, hmac_free, hmac_key,
|
|
|
+ hmac_start, hmac_genresult, hmac_text_name,
|
|
|
+ "hmac-sha1", "[email protected]",
|
|
|
+ 20, 20, &ssh_hmac_sha1_extra,
|
|
|
+};
|
|
|
+
|
|
|
+const struct hmac_extra ssh_hmac_sha1_96_extra = { &ssh_sha1, "-96" };
|
|
|
+
|
|
|
+const ssh2_macalg ssh_hmac_sha1_96 = {
|
|
|
+ hmac_new, hmac_free, hmac_key,
|
|
|
+ hmac_start, hmac_genresult, hmac_text_name,
|
|
|
+ "hmac-sha1-96", "[email protected]",
|
|
|
+ 12, 20, &ssh_hmac_sha1_96_extra,
|
|
|
+};
|
|
|
+
|
|
|
+const struct hmac_extra ssh_hmac_sha1_buggy_extra = {
|
|
|
+ &ssh_sha1, " (bug-compatible)"
|
|
|
+};
|
|
|
+
|
|
|
+const ssh2_macalg ssh_hmac_sha1_buggy = {
|
|
|
+ hmac_new, hmac_free, hmac_key,
|
|
|
+ hmac_start, hmac_genresult, hmac_text_name,
|
|
|
+ "hmac-sha1", NULL,
|
|
|
+ 20, 16, &ssh_hmac_sha1_buggy_extra,
|
|
|
+};
|
|
|
+
|
|
|
+const struct hmac_extra ssh_hmac_sha1_96_buggy_extra = {
|
|
|
+ &ssh_sha1, "-96 (bug-compatible)"
|
|
|
+};
|
|
|
+
|
|
|
+const ssh2_macalg ssh_hmac_sha1_96_buggy = {
|
|
|
+ hmac_new, hmac_free, hmac_key,
|
|
|
+ hmac_start, hmac_genresult, hmac_text_name,
|
|
|
+ "hmac-sha1-96", NULL,
|
|
|
+ 12, 16, &ssh_hmac_sha1_96_buggy_extra,
|
|
|
+};
|