]> git.proxmox.com Git - libgit2.git/blob - src/hash/sha1/win32.c
c7369566598c432629d9eadce4d5e6b7be18964e
[libgit2.git] / src / hash / sha1 / win32.c
1 /*
2 * Copyright (C) the libgit2 contributors. All rights reserved.
3 *
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
6 */
7
8 #include "win32.h"
9
10 #include "global.h"
11
12 #include <wincrypt.h>
13 #include <strsafe.h>
14
15 #define GIT_HASH_CNG_DLL_NAME "bcrypt.dll"
16
17 /* BCRYPT_SHA1_ALGORITHM */
18 #define GIT_HASH_CNG_HASH_TYPE L"SHA1"
19
20 /* BCRYPT_OBJECT_LENGTH */
21 #define GIT_HASH_CNG_HASH_OBJECT_LEN L"ObjectLength"
22
23 /* BCRYPT_HASH_REUSEABLE_FLAGS */
24 #define GIT_HASH_CNG_HASH_REUSABLE 0x00000020
25
26 static git_hash_prov hash_prov = {0};
27
28 /* Hash initialization */
29
30 /* Initialize CNG, if available */
31 GIT_INLINE(int) hash_cng_prov_init(void)
32 {
33 char dll_path[MAX_PATH];
34 DWORD dll_path_len, size_len;
35
36 /* Only use CNG on Windows 2008 / Vista SP1 or better (Windows 6.0 SP1) */
37 if (!git_has_win32_version(6, 0, 1)) {
38 git_error_set(GIT_ERROR_SHA1, "CryptoNG is not supported on this platform");
39 return -1;
40 }
41
42 /* Load bcrypt.dll explicitly from the system directory */
43 if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 ||
44 dll_path_len > MAX_PATH ||
45 StringCchCat(dll_path, MAX_PATH, "\\") < 0 ||
46 StringCchCat(dll_path, MAX_PATH, GIT_HASH_CNG_DLL_NAME) < 0 ||
47 (hash_prov.prov.cng.dll = LoadLibrary(dll_path)) == NULL) {
48 git_error_set(GIT_ERROR_SHA1, "CryptoNG library could not be loaded");
49 return -1;
50 }
51
52 /* Load the function addresses */
53 if ((hash_prov.prov.cng.open_algorithm_provider = (hash_win32_cng_open_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptOpenAlgorithmProvider")) == NULL ||
54 (hash_prov.prov.cng.get_property = (hash_win32_cng_get_property_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptGetProperty")) == NULL ||
55 (hash_prov.prov.cng.create_hash = (hash_win32_cng_create_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCreateHash")) == NULL ||
56 (hash_prov.prov.cng.finish_hash = (hash_win32_cng_finish_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptFinishHash")) == NULL ||
57 (hash_prov.prov.cng.hash_data = (hash_win32_cng_hash_data_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptHashData")) == NULL ||
58 (hash_prov.prov.cng.destroy_hash = (hash_win32_cng_destroy_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptDestroyHash")) == NULL ||
59 (hash_prov.prov.cng.close_algorithm_provider = (hash_win32_cng_close_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCloseAlgorithmProvider")) == NULL) {
60 FreeLibrary(hash_prov.prov.cng.dll);
61
62 git_error_set(GIT_ERROR_OS, "CryptoNG functions could not be loaded");
63 return -1;
64 }
65
66 /* Load the SHA1 algorithm */
67 if (hash_prov.prov.cng.open_algorithm_provider(&hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_TYPE, NULL, GIT_HASH_CNG_HASH_REUSABLE) < 0) {
68 FreeLibrary(hash_prov.prov.cng.dll);
69
70 git_error_set(GIT_ERROR_OS, "algorithm provider could not be initialized");
71 return -1;
72 }
73
74 /* Get storage space for the hash object */
75 if (hash_prov.prov.cng.get_property(hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_OBJECT_LEN, (PBYTE)&hash_prov.prov.cng.hash_object_size, sizeof(DWORD), &size_len, 0) < 0) {
76 hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0);
77 FreeLibrary(hash_prov.prov.cng.dll);
78
79 git_error_set(GIT_ERROR_OS, "algorithm handle could not be found");
80 return -1;
81 }
82
83 hash_prov.type = CNG;
84 return 0;
85 }
86
87 GIT_INLINE(void) hash_cng_prov_shutdown(void)
88 {
89 hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0);
90 FreeLibrary(hash_prov.prov.cng.dll);
91
92 hash_prov.type = INVALID;
93 }
94
95 /* Initialize CryptoAPI */
96 GIT_INLINE(int) hash_cryptoapi_prov_init()
97 {
98 if (!CryptAcquireContext(&hash_prov.prov.cryptoapi.handle, NULL, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
99 git_error_set(GIT_ERROR_OS, "legacy hash context could not be started");
100 return -1;
101 }
102
103 hash_prov.type = CRYPTOAPI;
104 return 0;
105 }
106
107 GIT_INLINE(void) hash_cryptoapi_prov_shutdown(void)
108 {
109 CryptReleaseContext(hash_prov.prov.cryptoapi.handle, 0);
110
111 hash_prov.type = INVALID;
112 }
113
114 static void sha1_shutdown(void)
115 {
116 if (hash_prov.type == CNG)
117 hash_cng_prov_shutdown();
118 else if(hash_prov.type == CRYPTOAPI)
119 hash_cryptoapi_prov_shutdown();
120 }
121
122 int git_hash_sha1_global_init(void)
123 {
124 int error = 0;
125
126 if (hash_prov.type != INVALID)
127 return 0;
128
129 if ((error = hash_cng_prov_init()) < 0)
130 error = hash_cryptoapi_prov_init();
131
132 git__on_shutdown(sha1_shutdown);
133
134 return error;
135 }
136
137 /* CryptoAPI: available in Windows XP and newer */
138
139 GIT_INLINE(int) hash_ctx_cryptoapi_init(git_hash_sha1_ctx *ctx)
140 {
141 ctx->type = CRYPTOAPI;
142 ctx->prov = &hash_prov;
143
144 return git_hash_sha1_init(ctx);
145 }
146
147 GIT_INLINE(int) hash_cryptoapi_init(git_hash_sha1_ctx *ctx)
148 {
149 if (ctx->ctx.cryptoapi.valid)
150 CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
151
152 if (!CryptCreateHash(ctx->prov->prov.cryptoapi.handle, CALG_SHA1, 0, 0, &ctx->ctx.cryptoapi.hash_handle)) {
153 ctx->ctx.cryptoapi.valid = 0;
154 git_error_set(GIT_ERROR_OS, "legacy hash implementation could not be created");
155 return -1;
156 }
157
158 ctx->ctx.cryptoapi.valid = 1;
159 return 0;
160 }
161
162 GIT_INLINE(int) hash_cryptoapi_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len)
163 {
164 const BYTE *data = (BYTE *)_data;
165
166 assert(ctx->ctx.cryptoapi.valid);
167
168 while (len > 0) {
169 DWORD chunk = (len > MAXDWORD) ? MAXDWORD : (DWORD)len;
170
171 if (!CryptHashData(ctx->ctx.cryptoapi.hash_handle, data, chunk, 0)) {
172 git_error_set(GIT_ERROR_OS, "legacy hash data could not be updated");
173 return -1;
174 }
175
176 data += chunk;
177 len -= chunk;
178 }
179
180 return 0;
181 }
182
183 GIT_INLINE(int) hash_cryptoapi_final(git_oid *out, git_hash_sha1_ctx *ctx)
184 {
185 DWORD len = 20;
186 int error = 0;
187
188 assert(ctx->ctx.cryptoapi.valid);
189
190 if (!CryptGetHashParam(ctx->ctx.cryptoapi.hash_handle, HP_HASHVAL, out->id, &len, 0)) {
191 git_error_set(GIT_ERROR_OS, "legacy hash data could not be finished");
192 error = -1;
193 }
194
195 CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
196 ctx->ctx.cryptoapi.valid = 0;
197
198 return error;
199 }
200
201 GIT_INLINE(void) hash_ctx_cryptoapi_cleanup(git_hash_sha1_ctx *ctx)
202 {
203 if (ctx->ctx.cryptoapi.valid)
204 CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
205 }
206
207 /* CNG: Available in Windows Server 2008 and newer */
208
209 GIT_INLINE(int) hash_ctx_cng_init(git_hash_sha1_ctx *ctx)
210 {
211 if ((ctx->ctx.cng.hash_object = git__malloc(hash_prov.prov.cng.hash_object_size)) == NULL)
212 return -1;
213
214 if (hash_prov.prov.cng.create_hash(hash_prov.prov.cng.handle, &ctx->ctx.cng.hash_handle, ctx->ctx.cng.hash_object, hash_prov.prov.cng.hash_object_size, NULL, 0, 0) < 0) {
215 git__free(ctx->ctx.cng.hash_object);
216
217 git_error_set(GIT_ERROR_OS, "hash implementation could not be created");
218 return -1;
219 }
220
221 ctx->type = CNG;
222 ctx->prov = &hash_prov;
223
224 return 0;
225 }
226
227 GIT_INLINE(int) hash_cng_init(git_hash_sha1_ctx *ctx)
228 {
229 BYTE hash[GIT_OID_RAWSZ];
230
231 if (!ctx->ctx.cng.updated)
232 return 0;
233
234 /* CNG needs to be finished to restart */
235 if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, hash, GIT_OID_RAWSZ, 0) < 0) {
236 git_error_set(GIT_ERROR_OS, "hash implementation could not be finished");
237 return -1;
238 }
239
240 ctx->ctx.cng.updated = 0;
241
242 return 0;
243 }
244
245 GIT_INLINE(int) hash_cng_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len)
246 {
247 PBYTE data = (PBYTE)_data;
248
249 while (len > 0) {
250 ULONG chunk = (len > ULONG_MAX) ? ULONG_MAX : (ULONG)len;
251
252 if (ctx->prov->prov.cng.hash_data(ctx->ctx.cng.hash_handle, data, chunk, 0) < 0) {
253 git_error_set(GIT_ERROR_OS, "hash could not be updated");
254 return -1;
255 }
256
257 data += chunk;
258 len -= chunk;
259 }
260
261 return 0;
262 }
263
264 GIT_INLINE(int) hash_cng_final(git_oid *out, git_hash_sha1_ctx *ctx)
265 {
266 if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, out->id, GIT_OID_RAWSZ, 0) < 0) {
267 git_error_set(GIT_ERROR_OS, "hash could not be finished");
268 return -1;
269 }
270
271 ctx->ctx.cng.updated = 0;
272
273 return 0;
274 }
275
276 GIT_INLINE(void) hash_ctx_cng_cleanup(git_hash_sha1_ctx *ctx)
277 {
278 ctx->prov->prov.cng.destroy_hash(ctx->ctx.cng.hash_handle);
279 git__free(ctx->ctx.cng.hash_object);
280 }
281
282 /* Indirection between CryptoAPI and CNG */
283
284 int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx)
285 {
286 int error = 0;
287
288 assert(ctx);
289
290 /*
291 * When compiled with GIT_THREADS, the global hash_prov data is
292 * initialized with git_libgit2_init. Otherwise, it must be initialized
293 * at first use.
294 */
295 if (hash_prov.type == INVALID && (error = git_hash_sha1_global_init()) < 0)
296 return error;
297
298 memset(ctx, 0x0, sizeof(git_hash_sha1_ctx));
299
300 return (hash_prov.type == CNG) ? hash_ctx_cng_init(ctx) : hash_ctx_cryptoapi_init(ctx);
301 }
302
303 int git_hash_sha1_init(git_hash_sha1_ctx *ctx)
304 {
305 assert(ctx && ctx->type);
306 return (ctx->type == CNG) ? hash_cng_init(ctx) : hash_cryptoapi_init(ctx);
307 }
308
309 int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len)
310 {
311 assert(ctx && ctx->type);
312 return (ctx->type == CNG) ? hash_cng_update(ctx, data, len) : hash_cryptoapi_update(ctx, data, len);
313 }
314
315 int git_hash_sha1_final(git_oid *out, git_hash_sha1_ctx *ctx)
316 {
317 assert(ctx && ctx->type);
318 return (ctx->type == CNG) ? hash_cng_final(out, ctx) : hash_cryptoapi_final(out, ctx);
319 }
320
321 void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx)
322 {
323 assert(ctx);
324
325 if (ctx->type == CNG)
326 hash_ctx_cng_cleanup(ctx);
327 else if(ctx->type == CRYPTOAPI)
328 hash_ctx_cryptoapi_cleanup(ctx);
329 }