Fix the dwDataLen parameter of CryptImportKey().
[create_hmac.git] / create_hmac / create_hmac.c
1 /**\r
2  * Copyright (c) 2010-2014 Florian Forster\r
3  *\r
4  * Permission is hereby granted, free of charge, to any person obtaining a copy\r
5  * of this software and associated documentation files (the "Software"), to deal\r
6  * in the Software without restriction, including without limitation the rights\r
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
8  * copies of the Software, and to permit persons to whom the Software is\r
9  * furnished to do so, subject to the following conditions:\r
10  *\r
11  * The above copyright notice and this permission notice shall be included in\r
12  * all copies or substantial portions of the Software.\r
13  *\r
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r
20  * THE SOFTWARE.\r
21  **/\r
22 \r
23 #include "create_hmac.h"\r
24 \r
25 #include <stdlib.h>\r
26 #include <stdio.h>\r
27 #include <assert.h>\r
28 \r
29 /*\r
30  * Creating a HCRYPTKEY from a plain text password is probably the most tricky\r
31  * part when calculating an RFC2104 HMAC using Microsoft CryptoAPI. The\r
32  * examples provided at the MSDN website encourage you to use "CryptDeriveKey",\r
33  * which doesn't do the conditional hashing required by HMAC. (That is, it\r
34  * might. The documentation on any of the functions is so vague that it's well\r
35  * possible the elegant solution is just not documented nor demonstrated.\r
36  */\r
37 static HCRYPTKEY create_hmac_key_exact (HCRYPTPROV hProv,\r
38                                                                                 BYTE *pbKey, DWORD dwKeySize)\r
39 {\r
40         /* Layout of the memory expected when importing a plain text blob is\r
41          * documented at the "CryptImportKey" under "Remarks". The four bytes\r
42          * following the PUBLICKEYSTRUC structure hold the size of the key, then\r
43          * follows the key itself. */\r
44         struct plain_text_data_s\r
45         {\r
46                 PUBLICKEYSTRUC blob;\r
47                 DWORD key_size;\r
48         } *data;\r
49         DWORD data_size;\r
50         HCRYPTKEY ret_key = 0;\r
51         BOOL status;\r
52 \r
53         data_size = sizeof (*data) + dwKeySize;\r
54         data = (struct plain_text_data_s *) malloc (data_size);\r
55         if (data == NULL)\r
56                 return (0);\r
57         memset (data, 0, data_size);\r
58 \r
59         /* The key is not encrypted. */\r
60         data->blob.bType = PLAINTEXTKEYBLOB;\r
61         data->blob.bVersion = CUR_BLOB_VERSION;\r
62         data->blob.reserved = 0;\r
63         /* The "CryptImportKey" page explicitly states that "RC2" is to be used for\r
64          * HMAC keys. What anyone might have been thinking, I don't know. */\r
65         data->blob.aiKeyAlg = CALG_RC2;\r
66 \r
67         /* Copy the key to the memory right behind the struct. "memcpy" should only\r
68          * add memory protection crap *after* the region written to, so the blob\r
69          * shouldn't be destroyed. We play it save, though, and set the key size\r
70          * last. Should problems arise, we should switch to a loop just to be sure. */\r
71         memcpy (data + 1, pbKey, dwKeySize);\r
72         data->key_size = dwKeySize;\r
73 \r
74         /* Actually convert our memory region to this mysterious key structure.\r
75          * The "CRYPT_IPSEC_HMAC_KEY" is required to allow RC2 keys longer than\r
76          * 16 byte. Again, this is documented on the "CryptImportKey" page as a\r
77          * side note. */\r
78         status = CryptImportKey (hProv,\r
79                         /* pbData    = */ (void *) data,\r
80                         /* dwDataLen = */ data_size,\r
81                         /* hPubKey   = */ 0,\r
82                         /* dwFlags   = */ CRYPT_IPSEC_HMAC_KEY,\r
83                         /* phKey     = */ &ret_key);\r
84         if (!status)\r
85         {\r
86                 free (data);\r
87                 return (0);\r
88         }\r
89 \r
90         free (data);\r
91         return (ret_key);\r
92 } /* HCRYPTKEY create_hmac_key_exact */\r
93 \r
94 /* If the key of the HMAC is larger than the hash size, use the hash of the\r
95  * key instead of using the key directly. */\r
96 static HCRYPTKEY create_hmac_key_hashed (HCRYPTPROV hProv,\r
97                                                                                  BYTE *pbKey, DWORD dwKeySize,\r
98                                                                                  DWORD dwHashSize, HCRYPTHASH hHash)\r
99 {\r
100         BOOL status;\r
101         BYTE *hash_data;\r
102         DWORD hash_data_size;\r
103         HCRYPTKEY key;\r
104 \r
105         assert (dwKeySize > dwHashSize);\r
106 \r
107         status = CryptHashData (hHash, pbKey, dwKeySize, /* dwFlags = */ 0);\r
108         if (!status)\r
109                 return (0);\r
110 \r
111         hash_data = (BYTE *) malloc (dwHashSize);\r
112         if (hash_data == NULL)\r
113                 return (0);\r
114         memset (hash_data, 0, dwHashSize);\r
115         hash_data_size = dwHashSize;\r
116 \r
117         status = CryptGetHashParam (hHash, HP_HASHVAL,\r
118                 hash_data, &hash_data_size, /* flags = */ 0);\r
119         if (!status)\r
120         {\r
121                 free (hash_data);\r
122                 return (0);\r
123         }\r
124 \r
125         assert (hash_data_size == dwHashSize);\r
126 \r
127         key = create_hmac_key_exact (hProv, hash_data, hash_data_size);\r
128 \r
129         free (hash_data);\r
130         return (key);\r
131 } /* HCRYPTKEY create_hmac_key_hashed */\r
132 \r
133 /* If the key is short enough, it is (right-)padded with zeros and otherwise\r
134  * used as-is. */\r
135 static HCRYPTKEY create_hmac_key_padded (HCRYPTPROV hProv,\r
136                                                                                  BYTE *pbKey, DWORD dwKeySize,\r
137                                                                                  DWORD dwHashSize)\r
138 {\r
139         BYTE *padded_key;\r
140         HCRYPTKEY key;\r
141         DWORD i;\r
142 \r
143         assert (dwKeySize <= dwHashSize);\r
144 \r
145         if (dwKeySize == dwHashSize)\r
146                 return (create_hmac_key_exact (hProv, pbKey, dwKeySize));\r
147 \r
148         padded_key = (BYTE *) malloc (dwHashSize);\r
149         if (padded_key == NULL)\r
150                 return (0);\r
151 \r
152         /* Copy the key and right-pad with zeros. Don't use "memcpy" here because\r
153          * the fucked up version of VS will corrupt memory. */\r
154         for (i = 0; i < dwHashSize; i++)\r
155                 padded_key[i] = (i < dwKeySize) ? pbKey[i] : 0;\r
156 \r
157         key = create_hmac_key_exact (hProv, padded_key, dwHashSize);\r
158 \r
159         free (padded_key);\r
160         return (key);\r
161 } /* HCRYPTKEY create_hmac_key_padded */\r
162 \r
163 static HCRYPTKEY create_hmac_key (HCRYPTPROV hProv,\r
164                                                                   ALG_ID Algid,\r
165                                                                   BYTE *pbKey, DWORD dwKeySize)\r
166 {\r
167         HCRYPTHASH hash = 0;\r
168         HCRYPTKEY key;\r
169         DWORD hash_size = 0;\r
170         DWORD param_size;\r
171         BOOL status;\r
172 \r
173         /* Allocate a hash object to determine the hash size. */\r
174         status = CryptCreateHash (hProv, Algid,\r
175                 /* hKey = */ 0,\r
176                 /* dwFlags = */ 0,\r
177                 /* out phHash = */ &hash);\r
178         if (!status)\r
179                 return (0);\r
180 \r
181         param_size = (DWORD) sizeof (hash_size);\r
182         status = CryptGetHashParam (hash, HP_HASHSIZE,\r
183                 (BYTE *) &hash_size, &param_size,\r
184                 /* flags = */ 0);\r
185         if (!status)\r
186         {\r
187                 CryptDestroyHash (hash);\r
188                 return (0);\r
189         }\r
190 \r
191         /* Determine whether we need to calculate the hash of the key or if\r
192          * padding is sufficient. */\r
193         if (dwKeySize > hash_size)\r
194                 key = create_hmac_key_hashed (hProv, pbKey, dwKeySize, hash_size, hash);\r
195         else\r
196                 key = create_hmac_key_padded (hProv, pbKey, dwKeySize, hash_size);\r
197 \r
198         CryptDestroyHash (hash);\r
199         return (key);\r
200 } /* HCRYPTKEY create_hmac_key */\r
201 \r
202 BOOL CreateHMAC (HCRYPTPROV hProv,\r
203                                  ALG_ID Algid,\r
204                                  BYTE *pbKey, DWORD dwKeySize,\r
205                                  DWORD dwFlags,\r
206                                  HCRYPTHASH *phHash,\r
207                                  HCRYPTKEY *phKey)\r
208 {\r
209         HCRYPTKEY hmac_key;\r
210         HCRYPTHASH hash = 0;\r
211         HMAC_INFO hmac_info;\r
212         BOOL status;\r
213 \r
214         hmac_key = create_hmac_key (hProv, Algid, pbKey, dwKeySize);\r
215         if (hmac_key == 0)\r
216                 return (FALSE);\r
217 \r
218         status = CryptCreateHash (hProv, CALG_HMAC, hmac_key,\r
219                 /* flags = */ dwFlags,\r
220                 &hash);\r
221         if (!status)\r
222         {\r
223                 CryptDestroyKey (hmac_key);\r
224                 return (status);\r
225         }\r
226 \r
227         memset (&hmac_info, 0, sizeof (hmac_info));\r
228         hmac_info.HashAlgid = Algid;\r
229         hmac_info.pbInnerString = NULL;\r
230         hmac_info.cbInnerString = 0;\r
231         hmac_info.pbOuterString = NULL;\r
232         hmac_info.cbOuterString = 0;\r
233 \r
234         status = CryptSetHashParam (hash, HP_HMAC_INFO, (BYTE *) &hmac_info,\r
235                 /* flags = */ 0);\r
236         if (!status)\r
237         {\r
238                 CryptDestroyHash (hash);\r
239                 CryptDestroyKey (hmac_key);\r
240                 return (status);\r
241         }\r
242 \r
243         *phHash = hash;\r
244         *phKey = hmac_key;\r
245         return (TRUE);\r
246 } /* BOOL CreateHMAC */\r
247 \r
248 BOOL DestroyHMAC (HCRYPTHASH hHash, HCRYPTKEY hKey)\r
249 {\r
250         if (hHash != 0)\r
251                 CryptDestroyHash (hHash);\r
252 \r
253         if (hKey != 0)\r
254                 CryptDestroyKey (hKey);\r
255 \r
256         return (TRUE);\r
257 } /* BOOL DestroyHMAC */\r
258 \r
259 /* vim: set ts=4 sw=4 noet : */\r