]> git.proxmox.com Git - libgit2.git/blame - src/util/hash/win32.c
Merge https://salsa.debian.org/debian/libgit2 into proxmox/bullseye
[libgit2.git] / src / util / hash / win32.c
CommitLineData
ad5611d8
TR
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 "runtime.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_SHA1_TYPE L"SHA1"
19#define GIT_HASH_CNG_SHA256_TYPE L"SHA256"
20
21/* BCRYPT_OBJECT_LENGTH */
22#define GIT_HASH_CNG_HASH_OBJECT_LEN L"ObjectLength"
23
24/* BCRYPT_HASH_REUSEABLE_FLAGS */
25#define GIT_HASH_CNG_HASH_REUSABLE 0x00000020
26
27/* Definitions */
28
29/* CryptoAPI is available for hashing on Windows XP and newer. */
30struct cryptoapi_provider {
31 HCRYPTPROV handle;
32};
33
34/*
35 * CNG (bcrypt.dll) is significantly more performant than CryptoAPI and is
36 * preferred, however it is only available on Windows 2008 and newer and
37 * must therefore be dynamically loaded, and we must inline constants that
38 * would not exist when building in pre-Windows 2008 environments.
39 */
40
41/* Function declarations for CNG */
42typedef NTSTATUS (WINAPI *cng_open_algorithm_provider_fn)(
43 HANDLE /* BCRYPT_ALG_HANDLE */ *phAlgorithm,
44 LPCWSTR pszAlgId,
45 LPCWSTR pszImplementation,
46 DWORD dwFlags);
47
48typedef NTSTATUS (WINAPI *cng_get_property_fn)(
49 HANDLE /* BCRYPT_HANDLE */ hObject,
50 LPCWSTR pszProperty,
51 PUCHAR pbOutput,
52 ULONG cbOutput,
53 ULONG *pcbResult,
54 ULONG dwFlags);
55
56typedef NTSTATUS (WINAPI *cng_create_hash_fn)(
57 HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm,
58 HANDLE /* BCRYPT_HASH_HANDLE */ *phHash,
59 PUCHAR pbHashObject, ULONG cbHashObject,
60 PUCHAR pbSecret,
61 ULONG cbSecret,
62 ULONG dwFlags);
63
64typedef NTSTATUS (WINAPI *cng_finish_hash_fn)(
65 HANDLE /* BCRYPT_HASH_HANDLE */ hHash,
66 PUCHAR pbOutput,
67 ULONG cbOutput,
68 ULONG dwFlags);
69
70typedef NTSTATUS (WINAPI *cng_hash_data_fn)(
71 HANDLE /* BCRYPT_HASH_HANDLE */ hHash,
72 PUCHAR pbInput,
73 ULONG cbInput,
74 ULONG dwFlags);
75
76typedef NTSTATUS (WINAPI *cng_destroy_hash_fn)(
77 HANDLE /* BCRYPT_HASH_HANDLE */ hHash);
78
79typedef NTSTATUS (WINAPI *cng_close_algorithm_provider_fn)(
80 HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm,
81 ULONG dwFlags);
82
83struct cng_provider {
84 /* DLL for CNG */
85 HINSTANCE dll;
86
87 /* Function pointers for CNG */
88 cng_open_algorithm_provider_fn open_algorithm_provider;
89 cng_get_property_fn get_property;
90 cng_create_hash_fn create_hash;
91 cng_finish_hash_fn finish_hash;
92 cng_hash_data_fn hash_data;
93 cng_destroy_hash_fn destroy_hash;
94 cng_close_algorithm_provider_fn close_algorithm_provider;
95
96 HANDLE /* BCRYPT_ALG_HANDLE */ sha1_handle;
97 DWORD sha1_object_size;
98
99 HANDLE /* BCRYPT_ALG_HANDLE */ sha256_handle;
100 DWORD sha256_object_size;
101};
102
103typedef struct {
104 git_hash_win32_provider_t type;
105
106 union {
107 struct cryptoapi_provider cryptoapi;
108 struct cng_provider cng;
109 } provider;
110} hash_win32_provider;
111
112/* Hash provider definition */
113
114static hash_win32_provider hash_provider = {0};
115
116/* Hash initialization */
117
118/* Initialize CNG, if available */
119GIT_INLINE(int) cng_provider_init(void)
120{
121 char dll_path[MAX_PATH];
122 DWORD dll_path_len, size_len;
123
124 /* Only use CNG on Windows 2008 / Vista SP1 or better (Windows 6.0 SP1) */
125 if (!git_has_win32_version(6, 0, 1)) {
126 git_error_set(GIT_ERROR_SHA, "CryptoNG is not supported on this platform");
127 return -1;
128 }
129
130 /* Load bcrypt.dll explicitly from the system directory */
131 if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 ||
132 dll_path_len > MAX_PATH ||
133 StringCchCat(dll_path, MAX_PATH, "\\") < 0 ||
134 StringCchCat(dll_path, MAX_PATH, GIT_HASH_CNG_DLL_NAME) < 0 ||
135 (hash_provider.provider.cng.dll = LoadLibrary(dll_path)) == NULL) {
136 git_error_set(GIT_ERROR_SHA, "CryptoNG library could not be loaded");
137 return -1;
138 }
139
140 /* Load the function addresses */
141 if ((hash_provider.provider.cng.open_algorithm_provider = (cng_open_algorithm_provider_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptOpenAlgorithmProvider"))) == NULL ||
142 (hash_provider.provider.cng.get_property = (cng_get_property_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptGetProperty"))) == NULL ||
143 (hash_provider.provider.cng.create_hash = (cng_create_hash_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptCreateHash"))) == NULL ||
144 (hash_provider.provider.cng.finish_hash = (cng_finish_hash_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptFinishHash"))) == NULL ||
145 (hash_provider.provider.cng.hash_data = (cng_hash_data_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptHashData"))) == NULL ||
146 (hash_provider.provider.cng.destroy_hash = (cng_destroy_hash_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptDestroyHash"))) == NULL ||
147 (hash_provider.provider.cng.close_algorithm_provider = (cng_close_algorithm_provider_fn)((void *)GetProcAddress(hash_provider.provider.cng.dll, "BCryptCloseAlgorithmProvider"))) == NULL) {
148 FreeLibrary(hash_provider.provider.cng.dll);
149
150 git_error_set(GIT_ERROR_OS, "CryptoNG functions could not be loaded");
151 return -1;
152 }
153
154 /* Load the SHA1 algorithm */
155 if (hash_provider.provider.cng.open_algorithm_provider(&hash_provider.provider.cng.sha1_handle, GIT_HASH_CNG_SHA1_TYPE, NULL, GIT_HASH_CNG_HASH_REUSABLE) < 0 ||
156 hash_provider.provider.cng.get_property(hash_provider.provider.cng.sha1_handle, GIT_HASH_CNG_HASH_OBJECT_LEN, (PBYTE)&hash_provider.provider.cng.sha1_object_size, sizeof(DWORD), &size_len, 0) < 0) {
157 git_error_set(GIT_ERROR_OS, "algorithm provider could not be initialized");
158 goto on_error;
159 }
160
161 /* Load the SHA256 algorithm */
162 if (hash_provider.provider.cng.open_algorithm_provider(&hash_provider.provider.cng.sha256_handle, GIT_HASH_CNG_SHA256_TYPE, NULL, GIT_HASH_CNG_HASH_REUSABLE) < 0 ||
163 hash_provider.provider.cng.get_property(hash_provider.provider.cng.sha256_handle, GIT_HASH_CNG_HASH_OBJECT_LEN, (PBYTE)&hash_provider.provider.cng.sha256_object_size, sizeof(DWORD), &size_len, 0) < 0) {
164 git_error_set(GIT_ERROR_OS, "algorithm provider could not be initialized");
165 goto on_error;
166 }
167
168 hash_provider.type = GIT_HASH_WIN32_CNG;
169 return 0;
170
171on_error:
172 if (hash_provider.provider.cng.sha1_handle)
173 hash_provider.provider.cng.close_algorithm_provider(hash_provider.provider.cng.sha1_handle, 0);
174
175 if (hash_provider.provider.cng.sha256_handle)
176 hash_provider.provider.cng.close_algorithm_provider(hash_provider.provider.cng.sha256_handle, 0);
177
178 if (hash_provider.provider.cng.dll)
179 FreeLibrary(hash_provider.provider.cng.dll);
180
181 return -1;
182}
183
184GIT_INLINE(void) cng_provider_shutdown(void)
185{
186 if (hash_provider.type == GIT_HASH_WIN32_INVALID)
187 return;
188
189 hash_provider.provider.cng.close_algorithm_provider(hash_provider.provider.cng.sha1_handle, 0);
190 hash_provider.provider.cng.close_algorithm_provider(hash_provider.provider.cng.sha256_handle, 0);
191 FreeLibrary(hash_provider.provider.cng.dll);
192
193 hash_provider.type = GIT_HASH_WIN32_INVALID;
194}
195
196/* Initialize CryptoAPI */
197GIT_INLINE(int) cryptoapi_provider_init(void)
198{
199 if (!CryptAcquireContext(&hash_provider.provider.cryptoapi.handle, NULL, 0, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) {
200 git_error_set(GIT_ERROR_OS, "legacy hash context could not be started");
201 return -1;
202 }
203
204 hash_provider.type = GIT_HASH_WIN32_CRYPTOAPI;
205 return 0;
206}
207
208GIT_INLINE(void) cryptoapi_provider_shutdown(void)
209{
210 if (hash_provider.type == GIT_HASH_WIN32_INVALID)
211 return;
212
213 CryptReleaseContext(hash_provider.provider.cryptoapi.handle, 0);
214
215 hash_provider.type = GIT_HASH_WIN32_INVALID;
216}
217
218static void hash_provider_shutdown(void)
219{
220 if (hash_provider.type == GIT_HASH_WIN32_CNG)
221 cng_provider_shutdown();
222 else if (hash_provider.type == GIT_HASH_WIN32_CRYPTOAPI)
223 cryptoapi_provider_shutdown();
224}
225
226static int hash_provider_init(void)
227{
228 int error = 0;
229
230 if (hash_provider.type != GIT_HASH_WIN32_INVALID)
231 return 0;
232
233 if ((error = cng_provider_init()) < 0)
234 error = cryptoapi_provider_init();
235
236 if (!error)
237 error = git_runtime_shutdown_register(hash_provider_shutdown);
238
239 return error;
240}
241
242git_hash_win32_provider_t git_hash_win32_provider(void)
243{
244 return hash_provider.type;
245}
246
247int git_hash_win32_set_provider(git_hash_win32_provider_t provider)
248{
249 if (provider == hash_provider.type)
250 return 0;
251
252 hash_provider_shutdown();
253
254 if (provider == GIT_HASH_WIN32_CNG)
255 return cng_provider_init();
256 else if (provider == GIT_HASH_WIN32_CRYPTOAPI)
257 return cryptoapi_provider_init();
258
259 git_error_set(GIT_ERROR_SHA, "unsupported win32 provider");
260 return -1;
261}
262
263/* CryptoAPI: available in Windows XP and newer */
264
265GIT_INLINE(int) hash_cryptoapi_init(git_hash_win32_ctx *ctx)
266{
267 if (ctx->ctx.cryptoapi.valid)
268 CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
269
270 if (!CryptCreateHash(hash_provider.provider.cryptoapi.handle, ctx->algorithm, 0, 0, &ctx->ctx.cryptoapi.hash_handle)) {
271 ctx->ctx.cryptoapi.valid = 0;
272 git_error_set(GIT_ERROR_OS, "legacy hash implementation could not be created");
273 return -1;
274 }
275
276 ctx->ctx.cryptoapi.valid = 1;
277 return 0;
278}
279
280GIT_INLINE(int) hash_cryptoapi_update(git_hash_win32_ctx *ctx, const void *_data, size_t len)
281{
282 const BYTE *data = (BYTE *)_data;
283
284 GIT_ASSERT(ctx->ctx.cryptoapi.valid);
285
286 while (len > 0) {
287 DWORD chunk = (len > MAXDWORD) ? MAXDWORD : (DWORD)len;
288
289 if (!CryptHashData(ctx->ctx.cryptoapi.hash_handle, data, chunk, 0)) {
290 git_error_set(GIT_ERROR_OS, "legacy hash data could not be updated");
291 return -1;
292 }
293
294 data += chunk;
295 len -= chunk;
296 }
297
298 return 0;
299}
300
301GIT_INLINE(int) hash_cryptoapi_final(unsigned char *out, git_hash_win32_ctx *ctx)
302{
303 DWORD len = ctx->algorithm == CALG_SHA_256 ? GIT_HASH_SHA256_SIZE : GIT_HASH_SHA1_SIZE;
304 int error = 0;
305
306 GIT_ASSERT(ctx->ctx.cryptoapi.valid);
307
308 if (!CryptGetHashParam(ctx->ctx.cryptoapi.hash_handle, HP_HASHVAL, out, &len, 0)) {
309 git_error_set(GIT_ERROR_OS, "legacy hash data could not be finished");
310 error = -1;
311 }
312
313 CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
314 ctx->ctx.cryptoapi.valid = 0;
315
316 return error;
317}
318
319GIT_INLINE(void) hash_ctx_cryptoapi_cleanup(git_hash_win32_ctx *ctx)
320{
321 if (ctx->ctx.cryptoapi.valid)
322 CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
323}
324
325GIT_INLINE(int) hash_sha1_cryptoapi_ctx_init_init(git_hash_win32_ctx *ctx)
326{
327 ctx->algorithm = CALG_SHA1;
328 return hash_cryptoapi_init(ctx);
329}
330
331GIT_INLINE(int) hash_sha256_cryptoapi_ctx_init(git_hash_win32_ctx *ctx)
332{
333 ctx->algorithm = CALG_SHA_256;
334 return hash_cryptoapi_init(ctx);
335}
336
337/* CNG: Available in Windows Server 2008 and newer */
338
339GIT_INLINE(int) hash_sha1_cng_ctx_init(git_hash_win32_ctx *ctx)
340{
341 if ((ctx->ctx.cng.hash_object = git__malloc(hash_provider.provider.cng.sha1_object_size)) == NULL)
342 return -1;
343
344 if (hash_provider.provider.cng.create_hash(hash_provider.provider.cng.sha1_handle, &ctx->ctx.cng.hash_handle, ctx->ctx.cng.hash_object, hash_provider.provider.cng.sha1_object_size, NULL, 0, 0) < 0) {
345 git__free(ctx->ctx.cng.hash_object);
346
347 git_error_set(GIT_ERROR_OS, "sha1 implementation could not be created");
348 return -1;
349 }
350
351 ctx->algorithm = CALG_SHA1;
352 return 0;
353}
354
355GIT_INLINE(int) hash_sha256_cng_ctx_init(git_hash_win32_ctx *ctx)
356{
357 if ((ctx->ctx.cng.hash_object = git__malloc(hash_provider.provider.cng.sha256_object_size)) == NULL)
358 return -1;
359
360 if (hash_provider.provider.cng.create_hash(hash_provider.provider.cng.sha256_handle, &ctx->ctx.cng.hash_handle, ctx->ctx.cng.hash_object, hash_provider.provider.cng.sha256_object_size, NULL, 0, 0) < 0) {
361 git__free(ctx->ctx.cng.hash_object);
362
363 git_error_set(GIT_ERROR_OS, "sha256 implementation could not be created");
364 return -1;
365 }
366
367 ctx->algorithm = CALG_SHA_256;
368 return 0;
369}
370
371GIT_INLINE(int) hash_cng_init(git_hash_win32_ctx *ctx)
372{
373 BYTE hash[GIT_HASH_SHA256_SIZE];
374 ULONG size = ctx->algorithm == CALG_SHA_256 ? GIT_HASH_SHA256_SIZE : GIT_HASH_SHA1_SIZE;
375
376 if (!ctx->ctx.cng.updated)
377 return 0;
378
379 /* CNG needs to be finished to restart */
380 if (hash_provider.provider.cng.finish_hash(ctx->ctx.cng.hash_handle, hash, size, 0) < 0) {
381 git_error_set(GIT_ERROR_OS, "hash implementation could not be finished");
382 return -1;
383 }
384
385 ctx->ctx.cng.updated = 0;
386
387 return 0;
388}
389
390GIT_INLINE(int) hash_cng_update(git_hash_win32_ctx *ctx, const void *_data, size_t len)
391{
392 PBYTE data = (PBYTE)_data;
393
394 while (len > 0) {
395 ULONG chunk = (len > ULONG_MAX) ? ULONG_MAX : (ULONG)len;
396
397 if (hash_provider.provider.cng.hash_data(ctx->ctx.cng.hash_handle, data, chunk, 0) < 0) {
398 git_error_set(GIT_ERROR_OS, "hash could not be updated");
399 return -1;
400 }
401
402 data += chunk;
403 len -= chunk;
404 }
405
406 return 0;
407}
408
409GIT_INLINE(int) hash_cng_final(unsigned char *out, git_hash_win32_ctx *ctx)
410{
411 ULONG size = ctx->algorithm == CALG_SHA_256 ? GIT_HASH_SHA256_SIZE : GIT_HASH_SHA1_SIZE;
412
413 if (hash_provider.provider.cng.finish_hash(ctx->ctx.cng.hash_handle, out, size, 0) < 0) {
414 git_error_set(GIT_ERROR_OS, "hash could not be finished");
415 return -1;
416 }
417
418 ctx->ctx.cng.updated = 0;
419
420 return 0;
421}
422
423GIT_INLINE(void) hash_ctx_cng_cleanup(git_hash_win32_ctx *ctx)
424{
425 hash_provider.provider.cng.destroy_hash(ctx->ctx.cng.hash_handle);
426 git__free(ctx->ctx.cng.hash_object);
427}
428
429/* Indirection between CryptoAPI and CNG */
430
431GIT_INLINE(int) hash_sha1_win32_ctx_init(git_hash_win32_ctx *ctx)
432{
433 GIT_ASSERT_ARG(hash_provider.type);
434
435 memset(ctx, 0x0, sizeof(git_hash_win32_ctx));
436 return (hash_provider.type == GIT_HASH_WIN32_CNG) ? hash_sha1_cng_ctx_init(ctx) : hash_sha1_cryptoapi_ctx_init_init(ctx);
437}
438
439GIT_INLINE(int) hash_sha256_win32_ctx_init(git_hash_win32_ctx *ctx)
440{
441 GIT_ASSERT_ARG(hash_provider.type);
442
443 memset(ctx, 0x0, sizeof(git_hash_win32_ctx));
444 return (hash_provider.type == GIT_HASH_WIN32_CNG) ? hash_sha256_cng_ctx_init(ctx) : hash_sha256_cryptoapi_ctx_init(ctx);
445}
446
447GIT_INLINE(int) hash_win32_init(git_hash_win32_ctx *ctx)
448{
449 return (hash_provider.type == GIT_HASH_WIN32_CNG) ? hash_cng_init(ctx) : hash_cryptoapi_init(ctx);
450}
451
452GIT_INLINE(int) hash_win32_update(git_hash_win32_ctx *ctx, const void *data, size_t len)
453{
454 return (hash_provider.type == GIT_HASH_WIN32_CNG) ? hash_cng_update(ctx, data, len) : hash_cryptoapi_update(ctx, data, len);
455}
456
457GIT_INLINE(int) hash_win32_final(unsigned char *out, git_hash_win32_ctx *ctx)
458{
459 GIT_ASSERT_ARG(ctx);
460 return (hash_provider.type == GIT_HASH_WIN32_CNG) ? hash_cng_final(out, ctx) : hash_cryptoapi_final(out, ctx);
461}
462
463GIT_INLINE(void) hash_win32_cleanup(git_hash_win32_ctx *ctx)
464{
465 if (hash_provider.type == GIT_HASH_WIN32_CNG)
466 hash_ctx_cng_cleanup(ctx);
467 else if(hash_provider.type == GIT_HASH_WIN32_CRYPTOAPI)
468 hash_ctx_cryptoapi_cleanup(ctx);
469}
470
471#ifdef GIT_SHA1_WIN32
472
473int git_hash_sha1_global_init(void)
474{
475 return hash_provider_init();
476}
477
478int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx)
479{
480 GIT_ASSERT_ARG(ctx);
481 return hash_sha1_win32_ctx_init(&ctx->win32);
482}
483
484int git_hash_sha1_init(git_hash_sha1_ctx *ctx)
485{
486 GIT_ASSERT_ARG(ctx);
487 return hash_win32_init(&ctx->win32);
488}
489
490int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len)
491{
492 GIT_ASSERT_ARG(ctx);
493 return hash_win32_update(&ctx->win32, data, len);
494}
495
496int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx)
497{
498 GIT_ASSERT_ARG(ctx);
499 return hash_win32_final(out, &ctx->win32);
500}
501
502void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx)
503{
504 if (!ctx)
505 return;
506 hash_win32_cleanup(&ctx->win32);
507}
508
509#endif
510
511#ifdef GIT_SHA256_WIN32
512
513int git_hash_sha256_global_init(void)
514{
515 return hash_provider_init();
516}
517
518int git_hash_sha256_ctx_init(git_hash_sha256_ctx *ctx)
519{
520 GIT_ASSERT_ARG(ctx);
521 return hash_sha256_win32_ctx_init(&ctx->win32);
522}
523
524int git_hash_sha256_init(git_hash_sha256_ctx *ctx)
525{
526 GIT_ASSERT_ARG(ctx);
527 return hash_win32_init(&ctx->win32);
528}
529
530int git_hash_sha256_update(git_hash_sha256_ctx *ctx, const void *data, size_t len)
531{
532 GIT_ASSERT_ARG(ctx);
533 return hash_win32_update(&ctx->win32, data, len);
534}
535
536int git_hash_sha256_final(unsigned char *out, git_hash_sha256_ctx *ctx)
537{
538 GIT_ASSERT_ARG(ctx);
539 return hash_win32_final(out, &ctx->win32);
540}
541
542void git_hash_sha256_ctx_cleanup(git_hash_sha256_ctx *ctx)
543{
544 if (!ctx)
545 return;
546 hash_win32_cleanup(&ctx->win32);
547}
548
549#endif