]> git.proxmox.com Git - libgit2.git/blame - src/hash/sha1/win32.c
New upstream version 1.4.3+dfsg.1
[libgit2.git] / src / hash / sha1 / win32.c
CommitLineData
d6fb0924 1/*
359fc2d2 2 * Copyright (C) the libgit2 contributors. All rights reserved.
d6fb0924
ET
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
22a2d3d5 8#include "win32.h"
eae0bfdc 9
c25aa7cd 10#include "runtime.h"
d6fb0924
ET
11
12#include <wincrypt.h>
13#include <strsafe.h>
14
22a2d3d5
UG
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
26static git_hash_prov hash_prov = {0};
7ebefd22
ET
27
28/* Hash initialization */
29
d6fb0924 30/* Initialize CNG, if available */
7ebefd22 31GIT_INLINE(int) hash_cng_prov_init(void)
d6fb0924 32{
d6fb0924
ET
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) */
eae0bfdc 37 if (!git_has_win32_version(6, 0, 1)) {
ac3d33df 38 git_error_set(GIT_ERROR_SHA1, "CryptoNG is not supported on this platform");
d6fb0924 39 return -1;
eae0bfdc 40 }
d6fb0924
ET
41
42 /* Load bcrypt.dll explicitly from the system directory */
43095341
RB
43 if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 ||
44 dll_path_len > MAX_PATH ||
d6fb0924
ET
45 StringCchCat(dll_path, MAX_PATH, "\\") < 0 ||
46 StringCchCat(dll_path, MAX_PATH, GIT_HASH_CNG_DLL_NAME) < 0 ||
eae0bfdc 47 (hash_prov.prov.cng.dll = LoadLibrary(dll_path)) == NULL) {
ac3d33df 48 git_error_set(GIT_ERROR_SHA1, "CryptoNG library could not be loaded");
d6fb0924 49 return -1;
eae0bfdc 50 }
d6fb0924
ET
51
52 /* Load the function addresses */
7ebefd22
ET
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);
eae0bfdc 61
ac3d33df 62 git_error_set(GIT_ERROR_OS, "CryptoNG functions could not be loaded");
d6fb0924
ET
63 return -1;
64 }
65
66 /* Load the SHA1 algorithm */
7ebefd22
ET
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);
eae0bfdc 69
ac3d33df 70 git_error_set(GIT_ERROR_OS, "algorithm provider could not be initialized");
d6fb0924
ET
71 return -1;
72 }
73
74 /* Get storage space for the hash object */
7ebefd22
ET
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);
eae0bfdc 78
ac3d33df 79 git_error_set(GIT_ERROR_OS, "algorithm handle could not be found");
d6fb0924
ET
80 return -1;
81 }
82
7ebefd22 83 hash_prov.type = CNG;
d6fb0924
ET
84 return 0;
85}
86
a8527429
ET
87GIT_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
d6fb0924 95/* Initialize CryptoAPI */
7ebefd22 96GIT_INLINE(int) hash_cryptoapi_prov_init()
d6fb0924 97{
eae0bfdc 98 if (!CryptAcquireContext(&hash_prov.prov.cryptoapi.handle, NULL, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
ac3d33df 99 git_error_set(GIT_ERROR_OS, "legacy hash context could not be started");
d6fb0924 100 return -1;
eae0bfdc 101 }
d6fb0924 102
7ebefd22 103 hash_prov.type = CRYPTOAPI;
d6fb0924
ET
104 return 0;
105}
106
a8527429
ET
107GIT_INLINE(void) hash_cryptoapi_prov_shutdown(void)
108{
109 CryptReleaseContext(hash_prov.prov.cryptoapi.handle, 0);
110
111 hash_prov.type = INVALID;
112}
113
22a2d3d5 114static void sha1_shutdown(void)
a3aa5f4d
RB
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
22a2d3d5 122int git_hash_sha1_global_init(void)
d6fb0924
ET
123{
124 int error = 0;
125
7ebefd22
ET
126 if (hash_prov.type != INVALID)
127 return 0;
d6fb0924 128
7ebefd22
ET
129 if ((error = hash_cng_prov_init()) < 0)
130 error = hash_cryptoapi_prov_init();
d6fb0924 131
c25aa7cd
PP
132 if (!error)
133 error = git_runtime_shutdown_register(sha1_shutdown);
d6fb0924 134
a3aa5f4d 135 return error;
a8527429
ET
136}
137
d6fb0924
ET
138/* CryptoAPI: available in Windows XP and newer */
139
22a2d3d5 140GIT_INLINE(int) hash_ctx_cryptoapi_init(git_hash_sha1_ctx *ctx)
d6fb0924 141{
d6fb0924 142 ctx->type = CRYPTOAPI;
7ebefd22 143 ctx->prov = &hash_prov;
d6fb0924 144
22a2d3d5 145 return git_hash_sha1_init(ctx);
d6fb0924
ET
146}
147
22a2d3d5 148GIT_INLINE(int) hash_cryptoapi_init(git_hash_sha1_ctx *ctx)
d6fb0924
ET
149{
150 if (ctx->ctx.cryptoapi.valid)
151 CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
152
153 if (!CryptCreateHash(ctx->prov->prov.cryptoapi.handle, CALG_SHA1, 0, 0, &ctx->ctx.cryptoapi.hash_handle)) {
154 ctx->ctx.cryptoapi.valid = 0;
ac3d33df 155 git_error_set(GIT_ERROR_OS, "legacy hash implementation could not be created");
d6fb0924
ET
156 return -1;
157 }
158
159 ctx->ctx.cryptoapi.valid = 1;
160 return 0;
161}
162
22a2d3d5 163GIT_INLINE(int) hash_cryptoapi_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len)
d6fb0924 164{
eae0bfdc
PP
165 const BYTE *data = (BYTE *)_data;
166
c25aa7cd 167 GIT_ASSERT(ctx->ctx.cryptoapi.valid);
d6fb0924 168
eae0bfdc
PP
169 while (len > 0) {
170 DWORD chunk = (len > MAXDWORD) ? MAXDWORD : (DWORD)len;
171
172 if (!CryptHashData(ctx->ctx.cryptoapi.hash_handle, data, chunk, 0)) {
ac3d33df 173 git_error_set(GIT_ERROR_OS, "legacy hash data could not be updated");
eae0bfdc
PP
174 return -1;
175 }
176
177 data += chunk;
178 len -= chunk;
179 }
d6fb0924
ET
180
181 return 0;
182}
183
e579e0f7 184GIT_INLINE(int) hash_cryptoapi_final(unsigned char *out, git_hash_sha1_ctx *ctx)
d6fb0924 185{
e579e0f7 186 DWORD len = GIT_HASH_SHA1_SIZE;
d6fb0924
ET
187 int error = 0;
188
c25aa7cd 189 GIT_ASSERT(ctx->ctx.cryptoapi.valid);
d6fb0924 190
e579e0f7 191 if (!CryptGetHashParam(ctx->ctx.cryptoapi.hash_handle, HP_HASHVAL, out, &len, 0)) {
ac3d33df 192 git_error_set(GIT_ERROR_OS, "legacy hash data could not be finished");
d6fb0924 193 error = -1;
eae0bfdc 194 }
d6fb0924
ET
195
196 CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
197 ctx->ctx.cryptoapi.valid = 0;
198
199 return error;
200}
201
22a2d3d5 202GIT_INLINE(void) hash_ctx_cryptoapi_cleanup(git_hash_sha1_ctx *ctx)
d6fb0924
ET
203{
204 if (ctx->ctx.cryptoapi.valid)
205 CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
206}
207
208/* CNG: Available in Windows Server 2008 and newer */
209
22a2d3d5 210GIT_INLINE(int) hash_ctx_cng_init(git_hash_sha1_ctx *ctx)
d6fb0924 211{
7ebefd22 212 if ((ctx->ctx.cng.hash_object = git__malloc(hash_prov.prov.cng.hash_object_size)) == NULL)
603bee07 213 return -1;
d6fb0924 214
7ebefd22 215 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) {
d6fb0924 216 git__free(ctx->ctx.cng.hash_object);
eae0bfdc 217
ac3d33df 218 git_error_set(GIT_ERROR_OS, "hash implementation could not be created");
603bee07 219 return -1;
d6fb0924
ET
220 }
221
222 ctx->type = CNG;
7ebefd22 223 ctx->prov = &hash_prov;
d6fb0924 224
603bee07 225 return 0;
d6fb0924
ET
226}
227
22a2d3d5 228GIT_INLINE(int) hash_cng_init(git_hash_sha1_ctx *ctx)
d6fb0924
ET
229{
230 BYTE hash[GIT_OID_RAWSZ];
231
232 if (!ctx->ctx.cng.updated)
233 return 0;
234
235 /* CNG needs to be finished to restart */
eae0bfdc 236 if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, hash, GIT_OID_RAWSZ, 0) < 0) {
ac3d33df 237 git_error_set(GIT_ERROR_OS, "hash implementation could not be finished");
d6fb0924 238 return -1;
eae0bfdc 239 }
d6fb0924
ET
240
241 ctx->ctx.cng.updated = 0;
242
243 return 0;
244}
245
22a2d3d5 246GIT_INLINE(int) hash_cng_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len)
d6fb0924 247{
eae0bfdc
PP
248 PBYTE data = (PBYTE)_data;
249
250 while (len > 0) {
251 ULONG chunk = (len > ULONG_MAX) ? ULONG_MAX : (ULONG)len;
252
253 if (ctx->prov->prov.cng.hash_data(ctx->ctx.cng.hash_handle, data, chunk, 0) < 0) {
ac3d33df 254 git_error_set(GIT_ERROR_OS, "hash could not be updated");
eae0bfdc
PP
255 return -1;
256 }
257
258 data += chunk;
259 len -= chunk;
260 }
d6fb0924
ET
261
262 return 0;
263}
264
e579e0f7 265GIT_INLINE(int) hash_cng_final(unsigned char *out, git_hash_sha1_ctx *ctx)
d6fb0924 266{
e579e0f7 267 if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, out, GIT_HASH_SHA1_SIZE, 0) < 0) {
ac3d33df 268 git_error_set(GIT_ERROR_OS, "hash could not be finished");
d6fb0924 269 return -1;
eae0bfdc 270 }
d6fb0924
ET
271
272 ctx->ctx.cng.updated = 0;
273
274 return 0;
275}
276
22a2d3d5 277GIT_INLINE(void) hash_ctx_cng_cleanup(git_hash_sha1_ctx *ctx)
d6fb0924
ET
278{
279 ctx->prov->prov.cng.destroy_hash(ctx->ctx.cng.hash_handle);
280 git__free(ctx->ctx.cng.hash_object);
281}
282
283/* Indirection between CryptoAPI and CNG */
284
22a2d3d5 285int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx)
d6fb0924 286{
7ebefd22 287 int error = 0;
d6fb0924 288
c25aa7cd 289 GIT_ASSERT_ARG(ctx);
d6fb0924 290
7ebefd22
ET
291 /*
292 * When compiled with GIT_THREADS, the global hash_prov data is
799e22ea 293 * initialized with git_libgit2_init. Otherwise, it must be initialized
7ebefd22
ET
294 * at first use.
295 */
22a2d3d5 296 if (hash_prov.type == INVALID && (error = git_hash_sha1_global_init()) < 0)
7ebefd22 297 return error;
d6fb0924 298
22a2d3d5 299 memset(ctx, 0x0, sizeof(git_hash_sha1_ctx));
d6fb0924 300
7ebefd22 301 return (hash_prov.type == CNG) ? hash_ctx_cng_init(ctx) : hash_ctx_cryptoapi_init(ctx);
d6fb0924
ET
302}
303
22a2d3d5 304int git_hash_sha1_init(git_hash_sha1_ctx *ctx)
d6fb0924 305{
c25aa7cd
PP
306 GIT_ASSERT_ARG(ctx);
307 GIT_ASSERT_ARG(ctx->type);
d6fb0924
ET
308 return (ctx->type == CNG) ? hash_cng_init(ctx) : hash_cryptoapi_init(ctx);
309}
310
22a2d3d5 311int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len)
d6fb0924 312{
c25aa7cd
PP
313 GIT_ASSERT_ARG(ctx);
314 GIT_ASSERT_ARG(ctx->type);
d6fb0924
ET
315 return (ctx->type == CNG) ? hash_cng_update(ctx, data, len) : hash_cryptoapi_update(ctx, data, len);
316}
317
e579e0f7 318int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx)
d6fb0924 319{
c25aa7cd
PP
320 GIT_ASSERT_ARG(ctx);
321 GIT_ASSERT_ARG(ctx->type);
d6fb0924
ET
322 return (ctx->type == CNG) ? hash_cng_final(out, ctx) : hash_cryptoapi_final(out, ctx);
323}
324
22a2d3d5 325void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx)
d6fb0924 326{
c25aa7cd
PP
327 if (!ctx)
328 return;
329 else if (ctx->type == CNG)
603bee07
ET
330 hash_ctx_cng_cleanup(ctx);
331 else if(ctx->type == CRYPTOAPI)
332 hash_ctx_cryptoapi_cleanup(ctx);
d6fb0924 333}