diff options
Diffstat (limited to 'src/hash/hash_win32.c')
-rw-r--r-- | src/hash/hash_win32.c | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/src/hash/hash_win32.c b/src/hash/hash_win32.c new file mode 100644 index 000000000..43d54ca6d --- /dev/null +++ b/src/hash/hash_win32.c @@ -0,0 +1,291 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "global.h" +#include "hash.h" +#include "hash/hash_win32.h" + +#include <wincrypt.h> +#include <strsafe.h> + +static struct git_hash_prov hash_prov = {0}; + +/* Hash initialization */ + +/* Initialize CNG, if available */ +GIT_INLINE(int) hash_cng_prov_init(void) +{ + OSVERSIONINFOEX version_test = {0}; + DWORD version_test_mask; + DWORDLONG version_condition_mask = 0; + char dll_path[MAX_PATH]; + DWORD dll_path_len, size_len; + + return -1; + + /* Only use CNG on Windows 2008 / Vista SP1 or better (Windows 6.0 SP1) */ + version_test.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + version_test.dwMajorVersion = 6; + version_test.dwMinorVersion = 0; + version_test.wServicePackMajor = 1; + version_test.wServicePackMinor = 0; + + version_test_mask = (VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR); + + VER_SET_CONDITION(version_condition_mask, VER_MAJORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(version_condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL); + + if (!VerifyVersionInfo(&version_test, version_test_mask, version_condition_mask)) + return -1; + + /* Load bcrypt.dll explicitly from the system directory */ + if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 || dll_path_len > MAX_PATH || + StringCchCat(dll_path, MAX_PATH, "\\") < 0 || + StringCchCat(dll_path, MAX_PATH, GIT_HASH_CNG_DLL_NAME) < 0 || + (hash_prov.prov.cng.dll = LoadLibrary(dll_path)) == NULL) + return -1; + + /* Load the function addresses */ + if ((hash_prov.prov.cng.open_algorithm_provider = (hash_win32_cng_open_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptOpenAlgorithmProvider")) == NULL || + (hash_prov.prov.cng.get_property = (hash_win32_cng_get_property_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptGetProperty")) == NULL || + (hash_prov.prov.cng.create_hash = (hash_win32_cng_create_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCreateHash")) == NULL || + (hash_prov.prov.cng.finish_hash = (hash_win32_cng_finish_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptFinishHash")) == NULL || + (hash_prov.prov.cng.hash_data = (hash_win32_cng_hash_data_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptHashData")) == NULL || + (hash_prov.prov.cng.destroy_hash = (hash_win32_cng_destroy_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptDestroyHash")) == NULL || + (hash_prov.prov.cng.close_algorithm_provider = (hash_win32_cng_close_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCloseAlgorithmProvider")) == NULL) { + FreeLibrary(hash_prov.prov.cng.dll); + return -1; + } + + /* Load the SHA1 algorithm */ + 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) { + FreeLibrary(hash_prov.prov.cng.dll); + return -1; + } + + /* Get storage space for the hash object */ + 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) { + hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0); + FreeLibrary(hash_prov.prov.cng.dll); + return -1; + } + + hash_prov.type = CNG; + return 0; +} + +GIT_INLINE(void) hash_cng_prov_shutdown(void) +{ + hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0); + FreeLibrary(hash_prov.prov.cng.dll); + + hash_prov.type = INVALID; +} + +/* Initialize CryptoAPI */ +GIT_INLINE(int) hash_cryptoapi_prov_init() +{ + if (!CryptAcquireContext(&hash_prov.prov.cryptoapi.handle, NULL, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) + return -1; + + hash_prov.type = CRYPTOAPI; + return 0; +} + +GIT_INLINE(void) hash_cryptoapi_prov_shutdown(void) +{ + CryptReleaseContext(hash_prov.prov.cryptoapi.handle, 0); + + hash_prov.type = INVALID; +} + +int git_hash_global_init() +{ + int error = 0; + + if (hash_prov.type != INVALID) + return 0; + + if ((error = hash_cng_prov_init()) < 0) + error = hash_cryptoapi_prov_init(); + + return error; +} + +void git_hash_global_shutdown() +{ + if (hash_prov.type == CNG) + hash_cng_prov_shutdown(); + else if(hash_prov.type == CRYPTOAPI) + hash_cryptoapi_prov_shutdown(); +} + +/* CryptoAPI: available in Windows XP and newer */ + +GIT_INLINE(int) hash_ctx_cryptoapi_init(git_hash_ctx *ctx) +{ + ctx->type = CRYPTOAPI; + ctx->prov = &hash_prov; + + return git_hash_init(ctx); +} + +GIT_INLINE(int) hash_cryptoapi_init(git_hash_ctx *ctx) +{ + if (ctx->ctx.cryptoapi.valid) + CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); + + if (!CryptCreateHash(ctx->prov->prov.cryptoapi.handle, CALG_SHA1, 0, 0, &ctx->ctx.cryptoapi.hash_handle)) { + ctx->ctx.cryptoapi.valid = 0; + return -1; + } + + ctx->ctx.cryptoapi.valid = 1; + return 0; +} + +GIT_INLINE(int) hash_cryptoapi_update(git_hash_ctx *ctx, const void *data, size_t len) +{ + assert(ctx->ctx.cryptoapi.valid); + + if (!CryptHashData(ctx->ctx.cryptoapi.hash_handle, (const BYTE *)data, (DWORD)len, 0)) + return -1; + + return 0; +} + +GIT_INLINE(int) hash_cryptoapi_final(git_oid *out, git_hash_ctx *ctx) +{ + DWORD len = 20; + int error = 0; + + assert(ctx->ctx.cryptoapi.valid); + + if (!CryptGetHashParam(ctx->ctx.cryptoapi.hash_handle, HP_HASHVAL, out->id, &len, 0)) + error = -1; + + CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); + ctx->ctx.cryptoapi.valid = 0; + + return error; +} + +GIT_INLINE(void) hash_ctx_cryptoapi_cleanup(git_hash_ctx *ctx) +{ + if (ctx->ctx.cryptoapi.valid) + CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); +} + +/* CNG: Available in Windows Server 2008 and newer */ + +GIT_INLINE(int) hash_ctx_cng_init(git_hash_ctx *ctx) +{ + if ((ctx->ctx.cng.hash_object = git__malloc(hash_prov.prov.cng.hash_object_size)) == NULL) + return -1; + + 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) { + git__free(ctx->ctx.cng.hash_object); + return -1; + } + + ctx->type = CNG; + ctx->prov = &hash_prov; + + return 0; +} + +GIT_INLINE(int) hash_cng_init(git_hash_ctx *ctx) +{ + BYTE hash[GIT_OID_RAWSZ]; + + if (!ctx->ctx.cng.updated) + return 0; + + /* CNG needs to be finished to restart */ + if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, hash, GIT_OID_RAWSZ, 0) < 0) + return -1; + + ctx->ctx.cng.updated = 0; + + return 0; +} + +GIT_INLINE(int) hash_cng_update(git_hash_ctx *ctx, const void *data, size_t len) +{ + if (ctx->prov->prov.cng.hash_data(ctx->ctx.cng.hash_handle, (PBYTE)data, (ULONG)len, 0) < 0) + return -1; + + return 0; +} + +GIT_INLINE(int) hash_cng_final(git_oid *out, git_hash_ctx *ctx) +{ + if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, out->id, GIT_OID_RAWSZ, 0) < 0) + return -1; + + ctx->ctx.cng.updated = 0; + + return 0; +} + +GIT_INLINE(void) hash_ctx_cng_cleanup(git_hash_ctx *ctx) +{ + ctx->prov->prov.cng.destroy_hash(ctx->ctx.cng.hash_handle); + git__free(ctx->ctx.cng.hash_object); +} + +/* Indirection between CryptoAPI and CNG */ + +int git_hash_ctx_init(git_hash_ctx *ctx) +{ + int error = 0; + + assert(ctx); + + /* + * When compiled with GIT_THREADS, the global hash_prov data is + * initialized with git_threads_init. Otherwise, it must be initialized + * at first use. + */ + if (hash_prov.type == INVALID && (error = git_hash_global_init()) < 0) + return error; + + memset(ctx, 0x0, sizeof(git_hash_ctx)); + + return (hash_prov.type == CNG) ? hash_ctx_cng_init(ctx) : hash_ctx_cryptoapi_init(ctx); +} + +int git_hash_init(git_hash_ctx *ctx) +{ + assert(ctx && ctx->type); + return (ctx->type == CNG) ? hash_cng_init(ctx) : hash_cryptoapi_init(ctx); +} + +int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len) +{ + assert(ctx && ctx->type); + return (ctx->type == CNG) ? hash_cng_update(ctx, data, len) : hash_cryptoapi_update(ctx, data, len); +} + +int git_hash_final(git_oid *out, git_hash_ctx *ctx) +{ + assert(ctx && ctx->type); + return (ctx->type == CNG) ? hash_cng_final(out, ctx) : hash_cryptoapi_final(out, ctx); +} + +void git_hash_ctx_cleanup(git_hash_ctx *ctx) +{ + assert(ctx); + + if (ctx->type == CNG) + hash_ctx_cng_cleanup(ctx); + else if(ctx->type == CRYPTOAPI) + hash_ctx_cryptoapi_cleanup(ctx); +} |