]>
Commit | Line | Data |
---|---|---|
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 | ||
26 | static git_hash_prov hash_prov = {0}; | |
7ebefd22 ET |
27 | |
28 | /* Hash initialization */ | |
29 | ||
d6fb0924 | 30 | /* Initialize CNG, if available */ |
7ebefd22 | 31 | GIT_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 |
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 | ||
d6fb0924 | 95 | /* Initialize CryptoAPI */ |
7ebefd22 | 96 | GIT_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 |
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 | ||
22a2d3d5 | 114 | static 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 | 122 | int 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 | 140 | GIT_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 | 148 | GIT_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 | 163 | GIT_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 | 184 | GIT_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 | 202 | GIT_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 | 210 | GIT_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 | 228 | GIT_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 | 246 | GIT_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 | 265 | GIT_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 | 277 | GIT_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 | 285 | int 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 | 304 | int 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 | 311 | int 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 | 318 | int 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 | 325 | void 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 | } |