Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mRemoteNG/PuTTYNG.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'crypto/hmac.c')
-rw-r--r--crypto/hmac.c263
1 files changed, 263 insertions, 0 deletions
diff --git a/crypto/hmac.c b/crypto/hmac.c
new file mode 100644
index 00000000..adeccd29
--- /dev/null
+++ b/crypto/hmac.c
@@ -0,0 +1,263 @@
+/*
+ * 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;
+ uint8_t *digest;
+ strbuf *text_name;
+ ssh2_mac mac;
+};
+
+struct hmac_extra {
+ const ssh_hashalg *hashalg_base;
+ const char *suffix, *annotation;
+};
+
+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);
+
+ /*
+ * 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();
+ put_fmt(ctx->text_name, "HMAC-%s%s",
+ ctx->hashalg->text_basename, extra->suffix);
+ if (extra->annotation || ctx->hashalg->annotation) {
+ put_fmt(ctx->text_name, " (");
+ const char *sep = "";
+ if (extra->annotation) {
+ put_fmt(ctx->text_name, "%s%s", sep, extra->annotation);
+ sep = ", ";
+ }
+ if (ctx->hashalg->annotation) {
+ put_fmt(ctx->text_name, "%s%s", sep, ctx->hashalg->annotation);
+ sep = ", ";
+ }
+ put_fmt(ctx->text_name, ")");
+ }
+
+ 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);
+
+ 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 uint8_t *kp;
+ size_t klen;
+ strbuf *sb = NULL;
+
+ 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_nm();
+ strbuf_append(sb, ctx->hashalg->hlen);
+ hash_simple(ctx->hashalg, key, 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;
+ }
+
+ ssh_hash_reset(ctx->h_outer);
+ 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);
+
+ ssh_hash_reset(ctx->h_inner);
+ 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_copyfrom(ctx->h_live, ctx->h_inner);
+}
+
+static void hmac_genresult(ssh2_mac *mac, unsigned char *output)
+{
+ struct hmac *ctx = container_of(mac, struct hmac, mac);
+ ssh_hash *htmp;
+
+ /* Leave h_live and h_outer in place, so that the SSH-2 BPP can
+ * continue regenerating test results from different-length
+ * prefixes of the packet */
+ ssh_hash_digest_nondestructive(ctx->h_live, 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;
+}
+
+static const struct hmac_extra ssh_hmac_sha256_extra = { &ssh_sha256, "" };
+const ssh2_macalg ssh_hmac_sha256 = {
+ .new = hmac_new,
+ .free = hmac_free,
+ .setkey = hmac_key,
+ .start = hmac_start,
+ .genresult = hmac_genresult,
+ .next_message = nullmac_next_message,
+ .text_name = hmac_text_name,
+ .name = "hmac-sha2-256",
+ .etm_name = "hmac-sha2-256-etm@openssh.com",
+ .len = 32,
+ .keylen = 32,
+ .extra = &ssh_hmac_sha256_extra,
+};
+
+static const struct hmac_extra ssh_hmac_md5_extra = { &ssh_md5, "" };
+const ssh2_macalg ssh_hmac_md5 = {
+ .new = hmac_new,
+ .free = hmac_free,
+ .setkey = hmac_key,
+ .start = hmac_start,
+ .genresult = hmac_genresult,
+ .next_message = nullmac_next_message,
+ .text_name = hmac_text_name,
+ .name = "hmac-md5",
+ .etm_name = "hmac-md5-etm@openssh.com",
+ .len = 16,
+ .keylen = 16,
+ .extra = &ssh_hmac_md5_extra,
+};
+
+static const struct hmac_extra ssh_hmac_sha1_extra = { &ssh_sha1, "" };
+
+const ssh2_macalg ssh_hmac_sha1 = {
+ .new = hmac_new,
+ .free = hmac_free,
+ .setkey = hmac_key,
+ .start = hmac_start,
+ .genresult = hmac_genresult,
+ .next_message = nullmac_next_message,
+ .text_name = hmac_text_name,
+ .name = "hmac-sha1",
+ .etm_name = "hmac-sha1-etm@openssh.com",
+ .len = 20,
+ .keylen = 20,
+ .extra = &ssh_hmac_sha1_extra,
+};
+
+static const struct hmac_extra ssh_hmac_sha1_96_extra = { &ssh_sha1, "-96" };
+
+const ssh2_macalg ssh_hmac_sha1_96 = {
+ .new = hmac_new,
+ .free = hmac_free,
+ .setkey = hmac_key,
+ .start = hmac_start,
+ .genresult = hmac_genresult,
+ .next_message = nullmac_next_message,
+ .text_name = hmac_text_name,
+ .name = "hmac-sha1-96",
+ .etm_name = "hmac-sha1-96-etm@openssh.com",
+ .len = 12,
+ .keylen = 20,
+ .extra = &ssh_hmac_sha1_96_extra,
+};
+
+static const struct hmac_extra ssh_hmac_sha1_buggy_extra = {
+ &ssh_sha1, "", "bug-compatible"
+};
+
+const ssh2_macalg ssh_hmac_sha1_buggy = {
+ .new = hmac_new,
+ .free = hmac_free,
+ .setkey = hmac_key,
+ .start = hmac_start,
+ .genresult = hmac_genresult,
+ .next_message = nullmac_next_message,
+ .text_name = hmac_text_name,
+ .name = "hmac-sha1",
+ .len = 20,
+ .keylen = 16,
+ .extra = &ssh_hmac_sha1_buggy_extra,
+};
+
+static const struct hmac_extra ssh_hmac_sha1_96_buggy_extra = {
+ &ssh_sha1, "-96", "bug-compatible"
+};
+
+const ssh2_macalg ssh_hmac_sha1_96_buggy = {
+ .new = hmac_new,
+ .free = hmac_free,
+ .setkey = hmac_key,
+ .start = hmac_start,
+ .genresult = hmac_genresult,
+ .next_message = nullmac_next_message,
+ .text_name = hmac_text_name,
+ .name = "hmac-sha1-96",
+ .len = 12,
+ .keylen = 16,
+ .extra = &ssh_hmac_sha1_96_buggy_extra,
+};