Blame src/hash/hash_win32.c

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