indentation
[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 /**
40  * Set this to 0 if you compile this code outside of GNUnet.
41  */
42 #define GNUNET_BUILD 1
43
44 /**
45  * Enable debugging.
46  */
47 #define DEBUG_HKDF 0
48
49
50 #if GNUNET_BUILD
51 #include "platform.h"
52 #include "gnunet_crypto_lib.h"
53 #else
54 #define GNUNET_NO 0
55 #define GNUNET_YES 1
56 #define GNUNET_SYSERR -1
57 #include <stdlib.h>
58 #endif
59
60 #include <gcrypt.h>
61
62
63 /**
64  * @brief Compute the HMAC
65  * @todo use chunked buffers
66  * @param mac gcrypt MAC handle
67  * @param key HMAC key
68  * @param key_len length of key
69  * @param buf message to be processed
70  * @param buf_len length of buf
71  * @return HMAC, freed by caller via gcry_md_close/_reset
72  */
73 static const void *
74 doHMAC (gcry_md_hd_t mac,
75         const void *key, size_t key_len, const void *buf, size_t buf_len)
76 {
77   gcry_md_setkey (mac, key, key_len);
78   gcry_md_write (mac, buf, buf_len);
79
80   return (const void *) gcry_md_read (mac, 0);
81 }
82
83 /**
84  * @brief Generate pseudo-random key
85  * @param mac gcrypt HMAC handle
86  * @param xts salt
87  * @param xts_len length of the salt
88  * @param skm source key material
89  * @param skm_len length of skm
90  * @param prk result buffer (allocated by caller; at least gcry_md_dlen() bytes)
91  * @return GNUNET_YES on success
92  */
93 static int
94 getPRK (gcry_md_hd_t mac,
95         const void *xts, size_t xts_len,
96         const void *skm, size_t skm_len, void *prk)
97 {
98   const void *ret;
99
100   ret = doHMAC (mac, xts, xts_len, skm, skm_len);
101   if (ret == NULL)
102     return GNUNET_SYSERR;
103   memcpy (prk, ret, gcry_md_get_algo_dlen (gcry_md_get_algo (mac)));
104
105   return GNUNET_YES;
106 }
107
108
109 #if DEBUG_HKDF
110 static void
111 dump (const char *src, const void *p, unsigned int l)
112 {
113   unsigned int i;
114
115   printf ("\n%s: ", src);
116   for (i = 0; i < l; i++)
117   {
118     printf ("%2x", (int) ((const unsigned char *) p)[i]);
119   }
120   printf ("\n");
121 }
122 #endif
123
124
125 /**
126  * @brief Derive key
127  * @param result buffer for the derived key, allocated by caller
128  * @param out_len desired length of the derived key
129  * @param xtr_algo hash algorithm for the extraction phase, GCRY_MD_...
130  * @param prf_algo hash algorithm for the expansion phase, GCRY_MD_...
131  * @param xts salt
132  * @param xts_len length of xts
133  * @param skm source key material
134  * @param skm_len length of skm
135  * @param argp va_list of void * & size_t pairs for context chunks
136  * @return GNUNET_YES on success
137  */
138 int
139 GNUNET_CRYPTO_hkdf_v (void *result, size_t out_len,
140                       int xtr_algo, int prf_algo,
141                       const void *xts, size_t xts_len,
142                       const void *skm, 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,
284                     int xtr_algo, int prf_algo,
285                     const void *xts, size_t xts_len,
286                     const void *skm, size_t skm_len, ...)
287 {
288   va_list argp;
289   int ret;
290
291   va_start (argp, skm_len);
292   ret = GNUNET_CRYPTO_hkdf_v (result, out_len, xtr_algo, prf_algo, xts,
293                               xts_len, skm, skm_len, argp);
294   va_end (argp);
295
296   return ret;
297 }
298
299 /* end of crypto_hkdf.c */