1 /*
2  * Implementation of HMAC (RFC 2104) for PuTTY, in a general form that
3  * can wrap any underlying hash function.
4  */
5 
6 #include "ssh.h"
7 
8 struct hmac {
9     const ssh_hashalg *hashalg;
10     ssh_hash *h_outer, *h_inner, *h_live;
11     uint8_t *digest;
12     strbuf *text_name;
13     ssh2_mac mac;
14 };
15 
16 struct hmac_extra {
17     const ssh_hashalg *hashalg_base;
18     const char *suffix, *annotation;
19 };
20 
hmac_new(const ssh2_macalg * alg,ssh_cipher * cipher)21 static ssh2_mac *hmac_new(const ssh2_macalg *alg, ssh_cipher *cipher)
22 {
23     struct hmac *ctx = snew(struct hmac);
24     const struct hmac_extra *extra = (const struct hmac_extra *)alg->extra;
25 
26     ctx->h_outer = ssh_hash_new(extra->hashalg_base);
27     /* In case that hashalg was a selector vtable, we'll now switch to
28      * using whatever real one it selected, for all future purposes. */
29     ctx->hashalg = ssh_hash_alg(ctx->h_outer);
30     ctx->h_inner = ssh_hash_new(ctx->hashalg);
31     ctx->h_live = ssh_hash_new(ctx->hashalg);
32 
33     /*
34      * HMAC is not well defined as a wrapper on an absolutely general
35      * hash function; it expects that the function it's wrapping will
36      * consume data in fixed-size blocks, and it's partially defined
37      * in terms of that block size. So we insist that the hash we're
38      * given must have defined a meaningful block size.
39      */
40     assert(ctx->hashalg->blocklen);
41 
42     ctx->digest = snewn(ctx->hashalg->hlen, uint8_t);
43 
44     ctx->text_name = strbuf_new();
45     strbuf_catf(ctx->text_name, "HMAC-%s%s",
46                 ctx->hashalg->text_basename, extra->suffix);
47     if (extra->annotation || ctx->hashalg->annotation) {
48         strbuf_catf(ctx->text_name, " (");
49         const char *sep = "";
50         if (extra->annotation) {
51             strbuf_catf(ctx->text_name, "%s%s", sep, extra->annotation);
52             sep = ", ";
53         }
54         if (ctx->hashalg->annotation) {
55             strbuf_catf(ctx->text_name, "%s%s", sep, ctx->hashalg->annotation);
56             sep = ", ";
57         }
58         strbuf_catf(ctx->text_name, ")");
59     }
60 
61     ctx->mac.vt = alg;
62     BinarySink_DELEGATE_INIT(&ctx->mac, ctx->h_live);
63 
64     return &ctx->mac;
65 }
66 
hmac_free(ssh2_mac * mac)67 static void hmac_free(ssh2_mac *mac)
68 {
69     struct hmac *ctx = container_of(mac, struct hmac, mac);
70 
71     ssh_hash_free(ctx->h_outer);
72     ssh_hash_free(ctx->h_inner);
73     ssh_hash_free(ctx->h_live);
74     smemclr(ctx->digest, ctx->hashalg->hlen);
75     sfree(ctx->digest);
76     strbuf_free(ctx->text_name);
77 
78     smemclr(ctx, sizeof(*ctx));
79     sfree(ctx);
80 }
81 
82 #define PAD_OUTER 0x5C
83 #define PAD_INNER 0x36
84 
hmac_key(ssh2_mac * mac,ptrlen key)85 static void hmac_key(ssh2_mac *mac, ptrlen key)
86 {
87     struct hmac *ctx = container_of(mac, struct hmac, mac);
88 
89     const uint8_t *kp;
90     size_t klen;
91     strbuf *sb = NULL;
92 
93     if (key.len > ctx->hashalg->blocklen) {
94         /*
95          * RFC 2104 section 2: if the key exceeds the block length of
96          * the underlying hash, then we start by hashing the key, and
97          * use that hash as the 'true' key for the HMAC construction.
98          */
99         sb = strbuf_new_nm();
100         strbuf_append(sb, ctx->hashalg->hlen);
101         hash_simple(ctx->hashalg, key, sb->u);
102         kp = sb->u;
103         klen = sb->len;
104     } else {
105         /*
106          * A short enough key is used as is.
107          */
108         kp = (const uint8_t *)key.ptr;
109         klen = key.len;
110     }
111 
112     ssh_hash_reset(ctx->h_outer);
113     for (size_t i = 0; i < klen; i++)
114         put_byte(ctx->h_outer, PAD_OUTER ^ kp[i]);
115     for (size_t i = klen; i < ctx->hashalg->blocklen; i++)
116         put_byte(ctx->h_outer, PAD_OUTER);
117 
118     ssh_hash_reset(ctx->h_inner);
119     for (size_t i = 0; i < klen; i++)
120         put_byte(ctx->h_inner, PAD_INNER ^ kp[i]);
121     for (size_t i = klen; i < ctx->hashalg->blocklen; i++)
122         put_byte(ctx->h_inner, PAD_INNER);
123 
124     if (sb)
125         strbuf_free(sb);
126 }
127 
hmac_start(ssh2_mac * mac)128 static void hmac_start(ssh2_mac *mac)
129 {
130     struct hmac *ctx = container_of(mac, struct hmac, mac);
131     ssh_hash_copyfrom(ctx->h_live, ctx->h_inner);
132 }
133 
hmac_genresult(ssh2_mac * mac,unsigned char * output)134 static void hmac_genresult(ssh2_mac *mac, unsigned char *output)
135 {
136     struct hmac *ctx = container_of(mac, struct hmac, mac);
137     ssh_hash *htmp;
138 
139     /* Leave h_live and h_outer in place, so that the SSH-2 BPP can
140      * continue regenerating test results from different-length
141      * prefixes of the packet */
142     ssh_hash_digest_nondestructive(ctx->h_live, ctx->digest);
143 
144     htmp = ssh_hash_copy(ctx->h_outer);
145     put_data(htmp, ctx->digest, ctx->hashalg->hlen);
146     ssh_hash_final(htmp, ctx->digest);
147 
148     /*
149      * Some instances of HMAC truncate the output hash, so instead of
150      * writing it directly to 'output' we wrote it to our own
151      * full-length buffer, and now we copy the required amount.
152      */
153     memcpy(output, ctx->digest, mac->vt->len);
154     smemclr(ctx->digest, ctx->hashalg->hlen);
155 }
156 
hmac_text_name(ssh2_mac * mac)157 static const char *hmac_text_name(ssh2_mac *mac)
158 {
159     struct hmac *ctx = container_of(mac, struct hmac, mac);
160     return ctx->text_name->s;
161 }
162 
163 static const struct hmac_extra ssh_hmac_sha256_extra = { &ssh_sha256, "" };
164 const ssh2_macalg ssh_hmac_sha256 = {
165     .new = hmac_new,
166     .free = hmac_free,
167     .setkey = hmac_key,
168     .start = hmac_start,
169     .genresult = hmac_genresult,
170     .text_name = hmac_text_name,
171     .name = "hmac-sha2-256",
172     .etm_name = "hmac-sha2-256-etm@openssh.com",
173     .len = 32,
174     .keylen = 32,
175     .extra = &ssh_hmac_sha256_extra,
176 };
177 
178 static const struct hmac_extra ssh_hmac_md5_extra = { &ssh_md5, "" };
179 const ssh2_macalg ssh_hmac_md5 = {
180     .new = hmac_new,
181     .free = hmac_free,
182     .setkey = hmac_key,
183     .start = hmac_start,
184     .genresult = hmac_genresult,
185     .text_name = hmac_text_name,
186     .name = "hmac-md5",
187     .etm_name = "hmac-md5-etm@openssh.com",
188     .len = 16,
189     .keylen = 16,
190     .extra = &ssh_hmac_md5_extra,
191 };
192 
193 static const struct hmac_extra ssh_hmac_sha1_extra = { &ssh_sha1, "" };
194 
195 const ssh2_macalg ssh_hmac_sha1 = {
196     .new = hmac_new,
197     .free = hmac_free,
198     .setkey = hmac_key,
199     .start = hmac_start,
200     .genresult = hmac_genresult,
201     .text_name = hmac_text_name,
202     .name = "hmac-sha1",
203     .etm_name = "hmac-sha1-etm@openssh.com",
204     .len = 20,
205     .keylen = 20,
206     .extra = &ssh_hmac_sha1_extra,
207 };
208 
209 static const struct hmac_extra ssh_hmac_sha1_96_extra = { &ssh_sha1, "-96" };
210 
211 const ssh2_macalg ssh_hmac_sha1_96 = {
212     .new = hmac_new,
213     .free = hmac_free,
214     .setkey = hmac_key,
215     .start = hmac_start,
216     .genresult = hmac_genresult,
217     .text_name = hmac_text_name,
218     .name = "hmac-sha1-96",
219     .etm_name = "hmac-sha1-96-etm@openssh.com",
220     .len = 12,
221     .keylen = 20,
222     .extra = &ssh_hmac_sha1_96_extra,
223 };
224 
225 static const struct hmac_extra ssh_hmac_sha1_buggy_extra = {
226     &ssh_sha1, "", "bug-compatible"
227 };
228 
229 const ssh2_macalg ssh_hmac_sha1_buggy = {
230     .new = hmac_new,
231     .free = hmac_free,
232     .setkey = hmac_key,
233     .start = hmac_start,
234     .genresult = hmac_genresult,
235     .text_name = hmac_text_name,
236     .name = "hmac-sha1",
237     .len = 20,
238     .keylen = 16,
239     .extra = &ssh_hmac_sha1_buggy_extra,
240 };
241 
242 static const struct hmac_extra ssh_hmac_sha1_96_buggy_extra = {
243     &ssh_sha1, "-96", "bug-compatible"
244 };
245 
246 const ssh2_macalg ssh_hmac_sha1_96_buggy = {
247     .new = hmac_new,
248     .free = hmac_free,
249     .setkey = hmac_key,
250     .start = hmac_start,
251     .genresult = hmac_genresult,
252     .text_name = hmac_text_name,
253     .name = "hmac-sha1-96",
254     .len = 12,
255     .keylen = 16,
256     .extra = &ssh_hmac_sha1_96_buggy_extra,
257 };
258