cf102d2bbcd188b6609ca3cc7c328a6bfab8b83c
[collectd.git] / src / libcollectdclient / win_hmac.c
1 /**
2  * Copyright (c) 2010-2014  Florian Forster
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 #include "win_hmac.h"
24
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include <assert.h>
28
29 /*
30  * Creating a HCRYPTKEY from a plain text password is probably the most tricky
31  * part when calculating an RFC2104 HMAC using Microsoft CryptoAPI. The
32  * examples provided at the MSDN website encourage you to use "CryptDeriveKey",
33  * which doesn't do the conditional hashing required by HMAC. (That is, it
34  * might. The documentation on any of the functions is so vague that it's well
35  * possible the elegant solution is just not documented nor demonstrated.
36  */
37 static HCRYPTKEY create_hmac_key_exact (HCRYPTPROV hProv,
38                                                                                 BYTE *pbKey, DWORD dwKeySize)
39 {
40         /* Layout of the memory expected when importing a plain text blob is
41          * documented at the "CryptImportKey" under "Remarks". The four bytes
42          * following the PUBLICKEYSTRUC structure hold the size of the key, then
43          * follows the key itself. */
44         struct plain_text_data_s
45         {
46                 PUBLICKEYSTRUC blob;
47                 DWORD key_size;
48         } *data;
49         DWORD data_size;
50         HCRYPTKEY ret_key = 0;
51         BOOL status;
52
53         data_size = sizeof (*data) + dwKeySize;
54         data = (struct plain_text_data_s *) malloc (data_size);
55         if (data == NULL)
56                 return (0);
57         memset (data, 0, data_size);
58
59         /* The key is not encrypted. */
60         data->blob.bType = PLAINTEXTKEYBLOB;
61         data->blob.bVersion = CUR_BLOB_VERSION;
62         data->blob.reserved = 0;
63         /* The "CryptImportKey" page explicitly states that "RC2" is to be used for
64          * HMAC keys. What anyone might have been thinking, I don't know. */
65         data->blob.aiKeyAlg = CALG_RC2;
66
67         /* Copy the key to the memory right behind the struct. "memcpy" should only
68          * add memory protection crap *after* the region written to, so the blob
69          * shouldn't be destroyed. We play it save, though, and set the key size
70          * last. Should problems arise, we should switch to a loop just to be sure. */
71         memcpy (data + 1, pbKey, dwKeySize);
72         data->key_size = dwKeySize;
73
74         /* Actually convert our memory region to this mysterious key structure.
75          * The "CRYPT_IPSEC_HMAC_KEY" is required to allow RC2 keys longer than
76          * 16 byte. Again, this is documented on the "CryptImportKey" page as a
77          * side note. */
78         status = CryptImportKey (hProv, (BYTE *) data, sizeof (data),
79                 /* public key = */ 0,
80                 /* flags = */ CRYPT_IPSEC_HMAC_KEY,
81                 &ret_key);
82         if (!status)
83         {
84                 free (data);
85                 return (0);
86         }
87
88         free (data);
89         return (ret_key);
90 } /* HCRYPTKEY create_hmac_key_exact */
91
92 /* If the key of the HMAC is larger than the hash size, use the hash of the
93  * key instead of using the key directly. */
94 static HCRYPTKEY create_hmac_key_hashed (HCRYPTPROV hProv,
95                                                                                  BYTE *pbKey, DWORD dwKeySize,
96                                                                                  DWORD dwHashSize, HCRYPTHASH hHash)
97 {
98         BOOL status;
99         BYTE *hash_data;
100         DWORD hash_data_size;
101         HCRYPTKEY key;
102
103         assert (dwKeySize > dwHashSize);
104
105         status = CryptHashData (hHash, pbKey, dwKeySize, /* dwFlags = */ 0);
106         if (!status)
107                 return (0);
108
109         hash_data = (BYTE *) malloc (dwHashSize);
110         if (hash_data == NULL)
111                 return (0);
112         memset (hash_data, 0, dwHashSize);
113         hash_data_size = dwHashSize;
114
115         status = CryptGetHashParam (hHash, HP_HASHVAL,
116                 hash_data, &hash_data_size, /* flags = */ 0);
117         if (!status)
118         {
119                 free (hash_data);
120                 return (0);
121         }
122
123         assert (hash_data_size == dwHashSize);
124
125         key = create_hmac_key_exact (hProv, hash_data, hash_data_size);
126
127         free (hash_data);
128         return (key);
129 } /* HCRYPTKEY create_hmac_key_hashed */
130
131 /* If the key is short enough, it is (right-)padded with zeros and otherwise
132  * used as-is. */
133 static HCRYPTKEY create_hmac_key_padded (HCRYPTPROV hProv,
134                                                                                  BYTE *pbKey, DWORD dwKeySize,
135                                                                                  DWORD dwHashSize)
136 {
137         BYTE *padded_key;
138         HCRYPTKEY key;
139         DWORD i;
140
141         assert (dwKeySize <= dwHashSize);
142
143         if (dwKeySize == dwHashSize)
144                 return (create_hmac_key_exact (hProv, pbKey, dwKeySize));
145
146         padded_key = (BYTE *) malloc (dwHashSize);
147         if (padded_key == NULL)
148                 return (0);
149
150         /* Copy the key and right-pad with zeros. Don't use "memcpy" here because
151          * the fucked up version of VS will corrupt memory. */
152         for (i = 0; i < dwHashSize; i++)
153                 padded_key[i] = (i < dwKeySize) ? pbKey[i] : 0;
154
155         key = create_hmac_key_exact (hProv, padded_key, dwHashSize);
156
157         free (padded_key);
158         return (key);
159 } /* HCRYPTKEY create_hmac_key_padded */
160
161 static HCRYPTKEY create_hmac_key (HCRYPTPROV hProv,
162                                                                   ALG_ID Algid,
163                                                                   BYTE *pbKey, DWORD dwKeySize)
164 {
165         HCRYPTHASH hash = 0;
166         HCRYPTKEY key;
167         DWORD hash_size = 0;
168         DWORD param_size;
169         BOOL status;
170
171         /* Allocate a hash object to determine the hash size. */
172         status = CryptCreateHash (hProv, Algid,
173                 /* hKey = */ 0,
174                 /* dwFlags = */ 0,
175                 /* out phHash = */ &hash);
176         if (!status)
177                 return (0);
178
179         param_size = (DWORD) sizeof (hash_size);
180         status = CryptGetHashParam (hash, HP_HASHSIZE,
181                 (BYTE *) &hash_size, &param_size,
182                 /* flags = */ 0);
183         if (!status)
184         {
185                 CryptDestroyHash (hash);
186                 return (0);
187         }
188
189         /* Determine whether we need to calculate the hash of the key or if
190          * padding is sufficient. */
191         if (dwKeySize > hash_size)
192                 key = create_hmac_key_hashed (hProv, pbKey, dwKeySize, hash_size, hash);
193         else
194                 key = create_hmac_key_padded (hProv, pbKey, dwKeySize, hash_size);
195
196         CryptDestroyHash (hash);
197         return (key);
198 } /* HCRYPTKEY create_hmac_key */
199
200 BOOL CreateHMAC (HCRYPTPROV hProv,
201                                  ALG_ID Algid,
202                                  BYTE *pbKey, DWORD dwKeySize,
203                                  DWORD dwFlags,
204                                  HCRYPTHASH *phHash,
205                                  HCRYPTKEY *phKey)
206 {
207         HCRYPTKEY hmac_key;
208         HCRYPTHASH hash = 0;
209         HMAC_INFO hmac_info;
210         BOOL status;
211
212         hmac_key = create_hmac_key (hProv, Algid, pbKey, dwKeySize);
213         if (hmac_key == 0)
214                 return (FALSE);
215
216         status = CryptCreateHash (hProv, CALG_HMAC, hmac_key,
217                 /* flags = */ dwFlags,
218                 &hash);
219         if (!status)
220         {
221                 CryptDestroyKey (hmac_key);
222                 return (status);
223         }
224
225         memset (&hmac_info, 0, sizeof (hmac_info));
226         hmac_info.HashAlgid = Algid;
227         hmac_info.pbInnerString = NULL;
228         hmac_info.cbInnerString = 0;
229         hmac_info.pbOuterString = NULL;
230         hmac_info.cbOuterString = 0;
231
232         status = CryptSetHashParam (hash, HP_HMAC_INFO, (BYTE *) &hmac_info,
233                 /* flags = */ 0);
234         if (!status)
235         {
236                 CryptDestroyHash (hash);
237                 CryptDestroyKey (hmac_key);
238                 return (status);
239         }
240
241         *phHash = hash;
242         *phKey = hmac_key;
243         return (TRUE);
244 } /* BOOL CreateHMAC */
245
246 BOOL DestroyHMAC (HCRYPTHASH hHash, HCRYPTKEY hKey)
247 {
248         if (hHash != 0)
249                 CryptDestroyHash (hHash);
250
251         if (hKey != 0)
252                 CryptDestroyKey (hKey);
253
254         return (TRUE);
255 } /* BOOL DestroyHMAC */
256
257 /* vim: set ts=4 sw=4 noet : */