]>
Commit | Line | Data |
---|---|---|
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_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 | if (!error) | |
133 | error = git_runtime_shutdown_register(sha1_shutdown); | |
134 | ||
135 | return error; | |
136 | } | |
137 | ||
138 | /* CryptoAPI: available in Windows XP and newer */ | |
139 | ||
140 | GIT_INLINE(int) hash_ctx_cryptoapi_init(git_hash_sha1_ctx *ctx) | |
141 | { | |
142 | ctx->type = CRYPTOAPI; | |
143 | ctx->prov = &hash_prov; | |
144 | ||
145 | return git_hash_sha1_init(ctx); | |
146 | } | |
147 | ||
148 | GIT_INLINE(int) hash_cryptoapi_init(git_hash_sha1_ctx *ctx) | |
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; | |
155 | git_error_set(GIT_ERROR_OS, "legacy hash implementation could not be created"); | |
156 | return -1; | |
157 | } | |
158 | ||
159 | ctx->ctx.cryptoapi.valid = 1; | |
160 | return 0; | |
161 | } | |
162 | ||
163 | GIT_INLINE(int) hash_cryptoapi_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len) | |
164 | { | |
165 | const BYTE *data = (BYTE *)_data; | |
166 | ||
167 | GIT_ASSERT(ctx->ctx.cryptoapi.valid); | |
168 | ||
169 | while (len > 0) { | |
170 | DWORD chunk = (len > MAXDWORD) ? MAXDWORD : (DWORD)len; | |
171 | ||
172 | if (!CryptHashData(ctx->ctx.cryptoapi.hash_handle, data, chunk, 0)) { | |
173 | git_error_set(GIT_ERROR_OS, "legacy hash data could not be updated"); | |
174 | return -1; | |
175 | } | |
176 | ||
177 | data += chunk; | |
178 | len -= chunk; | |
179 | } | |
180 | ||
181 | return 0; | |
182 | } | |
183 | ||
184 | GIT_INLINE(int) hash_cryptoapi_final(unsigned char *out, git_hash_sha1_ctx *ctx) | |
185 | { | |
186 | DWORD len = GIT_HASH_SHA1_SIZE; | |
187 | int error = 0; | |
188 | ||
189 | GIT_ASSERT(ctx->ctx.cryptoapi.valid); | |
190 | ||
191 | if (!CryptGetHashParam(ctx->ctx.cryptoapi.hash_handle, HP_HASHVAL, out, &len, 0)) { | |
192 | git_error_set(GIT_ERROR_OS, "legacy hash data could not be finished"); | |
193 | error = -1; | |
194 | } | |
195 | ||
196 | CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); | |
197 | ctx->ctx.cryptoapi.valid = 0; | |
198 | ||
199 | return error; | |
200 | } | |
201 | ||
202 | GIT_INLINE(void) hash_ctx_cryptoapi_cleanup(git_hash_sha1_ctx *ctx) | |
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 | ||
210 | GIT_INLINE(int) hash_ctx_cng_init(git_hash_sha1_ctx *ctx) | |
211 | { | |
212 | if ((ctx->ctx.cng.hash_object = git__malloc(hash_prov.prov.cng.hash_object_size)) == NULL) | |
213 | return -1; | |
214 | ||
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) { | |
216 | git__free(ctx->ctx.cng.hash_object); | |
217 | ||
218 | git_error_set(GIT_ERROR_OS, "hash implementation could not be created"); | |
219 | return -1; | |
220 | } | |
221 | ||
222 | ctx->type = CNG; | |
223 | ctx->prov = &hash_prov; | |
224 | ||
225 | return 0; | |
226 | } | |
227 | ||
228 | GIT_INLINE(int) hash_cng_init(git_hash_sha1_ctx *ctx) | |
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 */ | |
236 | if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, hash, GIT_OID_RAWSZ, 0) < 0) { | |
237 | git_error_set(GIT_ERROR_OS, "hash implementation could not be finished"); | |
238 | return -1; | |
239 | } | |
240 | ||
241 | ctx->ctx.cng.updated = 0; | |
242 | ||
243 | return 0; | |
244 | } | |
245 | ||
246 | GIT_INLINE(int) hash_cng_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len) | |
247 | { | |
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) { | |
254 | git_error_set(GIT_ERROR_OS, "hash could not be updated"); | |
255 | return -1; | |
256 | } | |
257 | ||
258 | data += chunk; | |
259 | len -= chunk; | |
260 | } | |
261 | ||
262 | return 0; | |
263 | } | |
264 | ||
265 | GIT_INLINE(int) hash_cng_final(unsigned char *out, git_hash_sha1_ctx *ctx) | |
266 | { | |
267 | if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, out, GIT_HASH_SHA1_SIZE, 0) < 0) { | |
268 | git_error_set(GIT_ERROR_OS, "hash could not be finished"); | |
269 | return -1; | |
270 | } | |
271 | ||
272 | ctx->ctx.cng.updated = 0; | |
273 | ||
274 | return 0; | |
275 | } | |
276 | ||
277 | GIT_INLINE(void) hash_ctx_cng_cleanup(git_hash_sha1_ctx *ctx) | |
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 | ||
285 | int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx) | |
286 | { | |
287 | int error = 0; | |
288 | ||
289 | GIT_ASSERT_ARG(ctx); | |
290 | ||
291 | /* | |
292 | * When compiled with GIT_THREADS, the global hash_prov data is | |
293 | * initialized with git_libgit2_init. Otherwise, it must be initialized | |
294 | * at first use. | |
295 | */ | |
296 | if (hash_prov.type == INVALID && (error = git_hash_sha1_global_init()) < 0) | |
297 | return error; | |
298 | ||
299 | memset(ctx, 0x0, sizeof(git_hash_sha1_ctx)); | |
300 | ||
301 | return (hash_prov.type == CNG) ? hash_ctx_cng_init(ctx) : hash_ctx_cryptoapi_init(ctx); | |
302 | } | |
303 | ||
304 | int git_hash_sha1_init(git_hash_sha1_ctx *ctx) | |
305 | { | |
306 | GIT_ASSERT_ARG(ctx); | |
307 | GIT_ASSERT_ARG(ctx->type); | |
308 | return (ctx->type == CNG) ? hash_cng_init(ctx) : hash_cryptoapi_init(ctx); | |
309 | } | |
310 | ||
311 | int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len) | |
312 | { | |
313 | GIT_ASSERT_ARG(ctx); | |
314 | GIT_ASSERT_ARG(ctx->type); | |
315 | return (ctx->type == CNG) ? hash_cng_update(ctx, data, len) : hash_cryptoapi_update(ctx, data, len); | |
316 | } | |
317 | ||
318 | int git_hash_sha1_final(unsigned char *out, git_hash_sha1_ctx *ctx) | |
319 | { | |
320 | GIT_ASSERT_ARG(ctx); | |
321 | GIT_ASSERT_ARG(ctx->type); | |
322 | return (ctx->type == CNG) ? hash_cng_final(out, ctx) : hash_cryptoapi_final(out, ctx); | |
323 | } | |
324 | ||
325 | void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx) | |
326 | { | |
327 | if (!ctx) | |
328 | return; | |
329 | else if (ctx->type == CNG) | |
330 | hash_ctx_cng_cleanup(ctx); | |
331 | else if(ctx->type == CRYPTOAPI) | |
332 | hash_ctx_cryptoapi_cleanup(ctx); | |
333 | } |