-fixes
[oweals/gnunet.git] / src / util / crypto_hkdf.c
1 /*
2     Copyright (c) 2010 Nils Durner
3
4     Permission is hereby granted, free of charge, to any person obtaining a copy
5     of this software and associated documentation files (the "Software"), to deal
6     in the Software without restriction, including without limitation the rights
7     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8     copies of the Software, and to permit persons to whom the Software is
9     furnished to do so, subject to the following conditions:
10
11     The above copyright notice and this permission notice shall be included in
12     all copies or substantial portions of the Software.
13
14     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20     THE SOFTWARE.
21 */
22
23 /**
24  * @file src/util/crypto_hkdf.c
25  * @brief Hash-based KDF as defined in RFC 5869
26  * @see http://www.rfc-editor.org/rfc/rfc5869.txt
27  * @todo remove GNUNET references
28  * @author Nils Durner
29  *
30  * The following list of people have reviewed this code and considered
31  * it correct on the date given (if you reviewed it, please
32  * have your name added to the list):
33  *
34  * - Christian Grothoff (08.10.2010)
35  * - Nathan Evans (08.10.2010)
36  * - Matthias Wachs (08.10.2010)
37  */
38
39 #define LOG(kind,...) GNUNET_log_from (kind, "util", __VA_ARGS__)
40
41 /**
42  * Set this to 0 if you compile this code outside of GNUnet.
43  */
44 #define GNUNET_BUILD 1
45
46 /**
47  * Enable debugging.
48  */
49 #define DEBUG_HKDF 0
50
51
52 #if GNUNET_BUILD
53 #include "platform.h"
54 #include "gnunet_crypto_lib.h"
55 #else
56 #define GNUNET_NO 0
57 #define GNUNET_YES 1
58 #define GNUNET_SYSERR -1
59 #include <stdlib.h>
60 #endif
61
62 #include <gcrypt.h>
63
64
65 /**
66  * @brief Compute the HMAC
67  * @todo use chunked buffers
68  * @param mac gcrypt MAC handle
69  * @param key HMAC key
70  * @param key_len length of key
71  * @param buf message to be processed
72  * @param buf_len length of buf
73  * @return HMAC, freed by caller via gcry_md_close/_reset
74  */
75 static const void *
76 doHMAC (gcry_md_hd_t mac, const void *key, size_t key_len, const void *buf,
77         size_t buf_len)
78 {
79   gcry_md_setkey (mac, key, key_len);
80   gcry_md_write (mac, buf, buf_len);
81
82   return (const void *) gcry_md_read (mac, 0);
83 }
84
85 /**
86  * @brief Generate pseudo-random key
87  * @param mac gcrypt HMAC handle
88  * @param xts salt
89  * @param xts_len length of the salt
90  * @param skm source key material
91  * @param skm_len length of skm
92  * @param prk result buffer (allocated by caller; at least gcry_md_dlen() bytes)
93  * @return GNUNET_YES on success
94  */
95 static int
96 getPRK (gcry_md_hd_t mac, const void *xts, size_t xts_len, const void *skm,
97         size_t skm_len, void *prk)
98 {
99   const void *ret;
100
101   ret = doHMAC (mac, xts, xts_len, skm, skm_len);
102   if (ret == NULL)
103     return GNUNET_SYSERR;
104   memcpy (prk, ret, gcry_md_get_algo_dlen (gcry_md_get_algo (mac)));
105
106   return GNUNET_YES;
107 }
108
109
110 #if DEBUG_HKDF
111 static void
112 dump (const char *src, const void *p, unsigned int l)
113 {
114   unsigned int i;
115
116   printf ("\n%s: ", src);
117   for (i = 0; i < l; i++)
118   {
119     printf ("%2x", (int) ((const unsigned char *) p)[i]);
120   }
121   printf ("\n");
122 }
123 #endif
124
125
126 /**
127  * @brief Derive key
128  * @param result buffer for the derived key, allocated by caller
129  * @param out_len desired length of the derived key
130  * @param xtr_algo hash algorithm for the extraction phase, GCRY_MD_...
131  * @param prf_algo hash algorithm for the expansion phase, GCRY_MD_...
132  * @param xts salt
133  * @param xts_len length of xts
134  * @param skm source key material
135  * @param skm_len length of skm
136  * @param argp va_list of void * & size_t pairs for context chunks
137  * @return GNUNET_YES on success
138  */
139 int
140 GNUNET_CRYPTO_hkdf_v (void *result, size_t out_len, int xtr_algo, int prf_algo,
141                       const void *xts, size_t xts_len, const void *skm,
142                       size_t skm_len, va_list argp)
143 {
144   const void *hc;
145   unsigned long i, t, d;
146   unsigned int k = gcry_md_get_algo_dlen (prf_algo);
147   unsigned int xtr_len = gcry_md_get_algo_dlen (xtr_algo);
148   char prk[xtr_len];
149   int ret;
150   gcry_md_hd_t xtr, prf;
151   size_t ctx_len;
152   va_list args;
153
154   if (k == 0)
155     return GNUNET_SYSERR;
156
157   if (gcry_md_open (&xtr, xtr_algo, GCRY_MD_FLAG_HMAC) != GPG_ERR_NO_ERROR)
158     return GNUNET_SYSERR;
159
160   if (gcry_md_open (&prf, prf_algo, GCRY_MD_FLAG_HMAC) != GPG_ERR_NO_ERROR)
161   {
162     gcry_md_close (xtr);
163     return GNUNET_SYSERR;
164   }
165
166   va_copy (args, argp);
167
168   ctx_len = 0;
169   while (NULL != va_arg (args, void *))
170          ctx_len += va_arg (args, size_t);
171
172   va_end (args);
173
174   memset (result, 0, out_len);
175   if (getPRK (xtr, xts, xts_len, skm, skm_len, prk) != GNUNET_YES)
176     goto hkdf_error;
177 #if DEBUG_HKDF
178   dump ("PRK", prk, xtr_len);
179 #endif
180
181   t = out_len / k;
182   d = out_len % k;
183
184   /* K(1) */
185   {
186     size_t plain_len = k + ctx_len + 1;
187     char plain[plain_len];
188     const void *ctx;
189     char *dst;
190
191     dst = plain + k;
192     va_copy (args, argp);
193     while ((ctx = va_arg (args, void *)))
194     {
195       size_t len;
196
197       len = va_arg (args, size_t);
198       memcpy (dst, ctx, len);
199       dst += len;
200     }
201     va_end (args);
202
203     if (t > 0)
204     {
205       memset (plain + k + ctx_len, 1, 1);
206 #if DEBUG_HKDF
207       dump ("K(1)", plain, plain_len);
208 #endif
209       hc = doHMAC (prf, prk, xtr_len, &plain[k], ctx_len + 1);
210       if (hc == NULL)
211         goto hkdf_error;
212       memcpy (result, hc, k);
213       result += k;
214     }
215
216     /* K(i+1) */
217     for (i = 1; i < t; i++)
218     {
219       memcpy (plain, result - k, k);
220       memset (plain + k + ctx_len, i + 1, 1);
221       gcry_md_reset (prf);
222 #if DEBUG_HKDF
223       dump ("K(i+1)", plain, plain_len);
224 #endif
225       hc = doHMAC (prf, prk, xtr_len, plain, plain_len);
226       if (hc == NULL)
227         goto hkdf_error;
228       memcpy (result, hc, k);
229       result += k;
230     }
231
232     /* K(t):d */
233     if (d > 0)
234     {
235       if (t > 0)
236       {
237         memcpy (plain, result - k, k);
238         i++;
239       }
240       memset (plain + k + ctx_len, i, 1);
241       gcry_md_reset (prf);
242 #if DEBUG_HKDF
243       dump ("K(t):d", plain, plain_len);
244 #endif
245       if (t > 0)
246         hc = doHMAC (prf, prk, xtr_len, plain, plain_len);
247       else
248         hc = doHMAC (prf, prk, xtr_len, plain + k, plain_len - k);
249       if (hc == NULL)
250         goto hkdf_error;
251       memcpy (result, hc, d);
252     }
253 #if DEBUG_HKDF
254     dump ("result", result - k, out_len);
255 #endif
256
257     ret = GNUNET_YES;
258     goto hkdf_ok;
259   }
260 hkdf_error:
261   ret = GNUNET_SYSERR;
262 hkdf_ok:
263   gcry_md_close (prf);
264   gcry_md_close (xtr);
265
266   return ret;
267 }
268
269
270 /**
271  * @brief Derive key
272  * @param result buffer for the derived key, allocated by caller
273  * @param out_len desired length of the derived key
274  * @param xtr_algo hash algorithm for the extraction phase, GCRY_MD_...
275  * @param prf_algo hash algorithm for the expansion phase, GCRY_MD_...
276  * @param xts salt
277  * @param xts_len length of xts
278  * @param skm source key material
279  * @param skm_len length of skm
280  * @return GNUNET_YES on success
281  */
282 int
283 GNUNET_CRYPTO_hkdf (void *result, size_t out_len, int xtr_algo, int prf_algo,
284                     const void *xts, size_t xts_len, const void *skm,
285                     size_t skm_len, ...)
286 {
287   va_list argp;
288   int ret;
289
290   va_start (argp, skm_len);
291   ret =
292       GNUNET_CRYPTO_hkdf_v (result, out_len, xtr_algo, prf_algo, xts, xts_len,
293                             skm, skm_len, argp);
294   va_end (argp);
295
296   return ret;
297 }
298
299 /* end of crypto_hkdf.c */