diff options
Diffstat (limited to 'src/Common/libzip/zip_crypto_win.c')
-rw-r--r-- | src/Common/libzip/zip_crypto_win.c | 495 |
1 files changed, 495 insertions, 0 deletions
diff --git a/src/Common/libzip/zip_crypto_win.c b/src/Common/libzip/zip_crypto_win.c new file mode 100644 index 00000000..ee3ccc30 --- /dev/null +++ b/src/Common/libzip/zip_crypto_win.c @@ -0,0 +1,495 @@ +/* + zip_crypto_win.c -- Windows Crypto API wrapper. + Copyright (C) 2018-2021 Dieter Baron and Thomas Klausner + + This file is part of libzip, a library to manipulate ZIP archives. + The authors can be contacted at <info@libzip.org> + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + 3. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include <stdlib.h> +#include <limits.h> + +#include "zipint.h" + +#include "zip_crypto.h" + +#define WIN32_LEAN_AND_MEAN +#define NOCRYPT + +#include <windows.h> + +#include <bcrypt.h> + +#pragma comment(lib, "bcrypt.lib") + +/* + +This code is using the Cryptography API: Next Generation (CNG) +https://docs.microsoft.com/en-us/windows/desktop/seccng/cng-portal + +This API is supported on + - Windows Vista or later (client OS) + - Windows Server 2008 (server OS) + - Windows Embedded Compact 2013 (don't know about Windows Embedded Compact 7) + +The code was developed for Windows Embedded Compact 2013 (WEC2013), +but should be working for all of the above mentioned OSes. + +There are 2 restrictions for WEC2013, Windows Vista and Windows Server 2008: + +1.) The function "BCryptDeriveKeyPBKDF2" is not available + +I found some code which is implementing this function using the deprecated Crypto API here: +https://www.idrix.fr/Root/content/view/37/54/ + +I took this code and converted it to the newer CNG API. The original code was more +flexible, but this is not needed here so i refactored it a bit and just kept what is needed. + +The define "HAS_BCRYPTDERIVEKEYPBKDF2" controls whether "BCryptDeriveKeyPBKDF2" +of the CNG API is used or not. This define must not be set if you are compiling for WEC2013 or Windows Vista. + + +2.) "BCryptCreateHash" can't manage the memory needed for the hash object internally + +On Windows 7 or later it is possible to pass NULL for the hash object buffer. +This is not supported on WEC2013, so we have to handle the memory allocation/deallocation ourselves. +There is no #ifdef to control that, because this is working for all supported OSes. + +*/ + +#if !defined(WINCE) && !defined(__MINGW32__) +#define HAS_BCRYPTDERIVEKEYPBKDF2 +#endif + +#ifdef HAS_BCRYPTDERIVEKEYPBKDF2 + +bool +_zip_crypto_pbkdf2(const zip_uint8_t *key, zip_uint64_t key_length, const zip_uint8_t *salt, zip_uint16_t salt_length, zip_uint16_t iterations, zip_uint8_t *output, zip_uint16_t output_length) { + BCRYPT_ALG_HANDLE hAlgorithm = NULL; + bool result; + + if (!BCRYPT_SUCCESS(BCryptOpenAlgorithmProvider(&hAlgorithm, BCRYPT_SHA1_ALGORITHM, NULL, BCRYPT_ALG_HANDLE_HMAC_FLAG))) { + return false; + } + + result = BCRYPT_SUCCESS(BCryptDeriveKeyPBKDF2(hAlgorithm, (PUCHAR)key, (ULONG)key_length, (PUCHAR)salt, salt_length, iterations, output, output_length, 0)); + + BCryptCloseAlgorithmProvider(hAlgorithm, 0); + + return result; +} + +#else + +#include <math.h> + +#define DIGEST_SIZE 20 +#define BLOCK_SIZE 64 + +typedef struct { + BCRYPT_ALG_HANDLE hAlgorithm; + BCRYPT_HASH_HANDLE hInnerHash; + BCRYPT_HASH_HANDLE hOuterHash; + ULONG cbHashObject; + PUCHAR pbInnerHash; + PUCHAR pbOuterHash; +} PRF_CTX; + +static void +hmacFree(PRF_CTX *pContext) { + if (pContext->hOuterHash) + BCryptDestroyHash(pContext->hOuterHash); + if (pContext->hInnerHash) + BCryptDestroyHash(pContext->hInnerHash); + free(pContext->pbOuterHash); + free(pContext->pbInnerHash); + if (pContext->hAlgorithm) + BCryptCloseAlgorithmProvider(pContext->hAlgorithm, 0); +} + +static BOOL +hmacPrecomputeDigest(BCRYPT_HASH_HANDLE hHash, PUCHAR pbPassword, DWORD cbPassword, BYTE mask) { + BYTE buffer[BLOCK_SIZE]; + DWORD i; + + if (cbPassword > BLOCK_SIZE) { + return FALSE; + } + + memset(buffer, mask, sizeof(buffer)); + + for (i = 0; i < cbPassword; ++i) { + buffer[i] = (char)(pbPassword[i] ^ mask); + } + + return BCRYPT_SUCCESS(BCryptHashData(hHash, buffer, sizeof(buffer), 0)); +} + +static BOOL +hmacInit(PRF_CTX *pContext, PUCHAR pbPassword, DWORD cbPassword) { + BOOL bStatus = FALSE; + ULONG cbResult; + BYTE key[DIGEST_SIZE]; + + if (!BCRYPT_SUCCESS(BCryptOpenAlgorithmProvider(&pContext->hAlgorithm, BCRYPT_SHA1_ALGORITHM, NULL, 0)) || !BCRYPT_SUCCESS(BCryptGetProperty(pContext->hAlgorithm, BCRYPT_OBJECT_LENGTH, (PUCHAR)&pContext->cbHashObject, sizeof(pContext->cbHashObject), &cbResult, 0)) || ((pContext->pbInnerHash = malloc(pContext->cbHashObject)) == NULL) || ((pContext->pbOuterHash = malloc(pContext->cbHashObject)) == NULL) || !BCRYPT_SUCCESS(BCryptCreateHash(pContext->hAlgorithm, &pContext->hInnerHash, pContext->pbInnerHash, pContext->cbHashObject, NULL, 0, 0)) || !BCRYPT_SUCCESS(BCryptCreateHash(pContext->hAlgorithm, &pContext->hOuterHash, pContext->pbOuterHash, pContext->cbHashObject, NULL, 0, 0))) { + goto hmacInit_end; + } + + if (cbPassword > BLOCK_SIZE) { + BCRYPT_HASH_HANDLE hHash = NULL; + PUCHAR pbHashObject = malloc(pContext->cbHashObject); + if (pbHashObject == NULL) { + goto hmacInit_end; + } + + bStatus = BCRYPT_SUCCESS(BCryptCreateHash(pContext->hAlgorithm, &hHash, pbHashObject, pContext->cbHashObject, NULL, 0, 0)) && BCRYPT_SUCCESS(BCryptHashData(hHash, pbPassword, cbPassword, 0)) && BCRYPT_SUCCESS(BCryptGetProperty(hHash, BCRYPT_HASH_LENGTH, (PUCHAR)&cbPassword, sizeof(cbPassword), &cbResult, 0)) && BCRYPT_SUCCESS(BCryptFinishHash(hHash, key, cbPassword, 0)); + + if (hHash) + BCryptDestroyHash(hHash); + free(pbHashObject); + + if (!bStatus) { + goto hmacInit_end; + } + + pbPassword = key; + } + + bStatus = hmacPrecomputeDigest(pContext->hInnerHash, pbPassword, cbPassword, 0x36) && hmacPrecomputeDigest(pContext->hOuterHash, pbPassword, cbPassword, 0x5C); + +hmacInit_end: + + if (bStatus == FALSE) + hmacFree(pContext); + + return bStatus; +} + +static BOOL +hmacCalculateInternal(BCRYPT_HASH_HANDLE hHashTemplate, PUCHAR pbData, DWORD cbData, PUCHAR pbOutput, DWORD cbOutput, DWORD cbHashObject) { + BOOL success = FALSE; + BCRYPT_HASH_HANDLE hHash = NULL; + PUCHAR pbHashObject = malloc(cbHashObject); + + if (pbHashObject == NULL) { + return FALSE; + } + + if (BCRYPT_SUCCESS(BCryptDuplicateHash(hHashTemplate, &hHash, pbHashObject, cbHashObject, 0))) { + success = BCRYPT_SUCCESS(BCryptHashData(hHash, pbData, cbData, 0)) && BCRYPT_SUCCESS(BCryptFinishHash(hHash, pbOutput, cbOutput, 0)); + + BCryptDestroyHash(hHash); + } + + free(pbHashObject); + + return success; +} + +static BOOL +hmacCalculate(PRF_CTX *pContext, PUCHAR pbData, DWORD cbData, PUCHAR pbDigest) { + DWORD cbResult; + DWORD cbHashObject; + + return BCRYPT_SUCCESS(BCryptGetProperty(pContext->hAlgorithm, BCRYPT_OBJECT_LENGTH, (PUCHAR)&cbHashObject, sizeof(cbHashObject), &cbResult, 0)) && hmacCalculateInternal(pContext->hInnerHash, pbData, cbData, pbDigest, DIGEST_SIZE, cbHashObject) && hmacCalculateInternal(pContext->hOuterHash, pbDigest, DIGEST_SIZE, pbDigest, DIGEST_SIZE, cbHashObject); +} + +static void +myxor(LPBYTE ptr1, LPBYTE ptr2, DWORD dwLen) { + while (dwLen--) + *ptr1++ ^= *ptr2++; +} + +BOOL +pbkdf2(PUCHAR pbPassword, ULONG cbPassword, PUCHAR pbSalt, ULONG cbSalt, DWORD cIterations, PUCHAR pbDerivedKey, ULONG cbDerivedKey) { + BOOL bStatus = FALSE; + DWORD l, r, dwULen, i, j; + BYTE Ti[DIGEST_SIZE]; + BYTE V[DIGEST_SIZE]; + LPBYTE U = malloc(max((cbSalt + 4), DIGEST_SIZE)); + PRF_CTX prfCtx = {0}; + + if (U == NULL) { + return FALSE; + } + + if (pbPassword == NULL || cbPassword == 0 || pbSalt == NULL || cbSalt == 0 || cIterations == 0 || pbDerivedKey == NULL || cbDerivedKey == 0) { + free(U); + return FALSE; + } + + if (!hmacInit(&prfCtx, pbPassword, cbPassword)) { + goto PBKDF2_end; + } + + l = (DWORD)ceil((double)cbDerivedKey / (double)DIGEST_SIZE); + r = cbDerivedKey - (l - 1) * DIGEST_SIZE; + + for (i = 1; i <= l; i++) { + ZeroMemory(Ti, DIGEST_SIZE); + for (j = 0; j < cIterations; j++) { + if (j == 0) { + /* construct first input for PRF */ + (void)memcpy_s(U, cbSalt, pbSalt, cbSalt); + U[cbSalt] = (BYTE)((i & 0xFF000000) >> 24); + U[cbSalt + 1] = (BYTE)((i & 0x00FF0000) >> 16); + U[cbSalt + 2] = (BYTE)((i & 0x0000FF00) >> 8); + U[cbSalt + 3] = (BYTE)((i & 0x000000FF)); + dwULen = cbSalt + 4; + } + else { + (void)memcpy_s(U, DIGEST_SIZE, V, DIGEST_SIZE); + dwULen = DIGEST_SIZE; + } + + if (!hmacCalculate(&prfCtx, U, dwULen, V)) { + goto PBKDF2_end; + } + + myxor(Ti, V, DIGEST_SIZE); + } + + if (i != l) { + (void)memcpy_s(&pbDerivedKey[(i - 1) * DIGEST_SIZE], cbDerivedKey - (i - 1) * DIGEST_SIZE, Ti, DIGEST_SIZE); + } + else { + /* Take only the first r bytes */ + (void)memcpy_s(&pbDerivedKey[(i - 1) * DIGEST_SIZE], cbDerivedKey - (i - 1) * DIGEST_SIZE, Ti, r); + } + } + + bStatus = TRUE; + +PBKDF2_end: + + hmacFree(&prfCtx); + free(U); + return bStatus; +} + +bool +_zip_crypto_pbkdf2(const zip_uint8_t *key, zip_uint64_t key_length, const zip_uint8_t *salt, zip_uint16_t salt_length, zip_uint16_t iterations, zip_uint8_t *output, zip_uint16_t output_length) { + return (key_length <= ZIP_UINT32_MAX) && pbkdf2((PUCHAR)key, (ULONG)key_length, (PUCHAR)salt, salt_length, iterations, output, output_length); +} + +#endif + + +struct _zip_crypto_aes_s { + BCRYPT_ALG_HANDLE hAlgorithm; + BCRYPT_KEY_HANDLE hKey; + ULONG cbKeyObject; + PUCHAR pbKeyObject; +}; + +_zip_crypto_aes_t * +_zip_crypto_aes_new(const zip_uint8_t *key, zip_uint16_t key_size, zip_error_t *error) { + _zip_crypto_aes_t *aes = (_zip_crypto_aes_t *)calloc(1, sizeof(*aes)); + + ULONG cbResult; + ULONG key_length = key_size / 8; + + if (aes == NULL) { + zip_error_set(error, ZIP_ER_MEMORY, 0); + return NULL; + } + + if (!BCRYPT_SUCCESS(BCryptOpenAlgorithmProvider(&aes->hAlgorithm, BCRYPT_AES_ALGORITHM, NULL, 0))) { + _zip_crypto_aes_free(aes); + return NULL; + } + + if (!BCRYPT_SUCCESS(BCryptSetProperty(aes->hAlgorithm, BCRYPT_CHAINING_MODE, (PUCHAR)BCRYPT_CHAIN_MODE_ECB, sizeof(BCRYPT_CHAIN_MODE_ECB), 0))) { + _zip_crypto_aes_free(aes); + return NULL; + } + + if (!BCRYPT_SUCCESS(BCryptGetProperty(aes->hAlgorithm, BCRYPT_OBJECT_LENGTH, (PUCHAR)&aes->cbKeyObject, sizeof(aes->cbKeyObject), &cbResult, 0))) { + _zip_crypto_aes_free(aes); + return NULL; + } + + aes->pbKeyObject = malloc(aes->cbKeyObject); + if (aes->pbKeyObject == NULL) { + _zip_crypto_aes_free(aes); + zip_error_set(error, ZIP_ER_MEMORY, 0); + return NULL; + } + + if (!BCRYPT_SUCCESS(BCryptGenerateSymmetricKey(aes->hAlgorithm, &aes->hKey, aes->pbKeyObject, aes->cbKeyObject, (PUCHAR)key, key_length, 0))) { + _zip_crypto_aes_free(aes); + return NULL; + } + + return aes; +} + +void +_zip_crypto_aes_free(_zip_crypto_aes_t *aes) { + if (aes == NULL) { + return; + } + + if (aes->hKey != NULL) { + BCryptDestroyKey(aes->hKey); + } + + if (aes->pbKeyObject != NULL) { + free(aes->pbKeyObject); + } + + if (aes->hAlgorithm != NULL) { + BCryptCloseAlgorithmProvider(aes->hAlgorithm, 0); + } + + free(aes); +} + +bool +_zip_crypto_aes_encrypt_block(_zip_crypto_aes_t *aes, const zip_uint8_t *in, zip_uint8_t *out) { + ULONG cbResult; + NTSTATUS status = BCryptEncrypt(aes->hKey, (PUCHAR)in, ZIP_CRYPTO_AES_BLOCK_LENGTH, NULL, NULL, 0, (PUCHAR)out, ZIP_CRYPTO_AES_BLOCK_LENGTH, &cbResult, 0); + return BCRYPT_SUCCESS(status); +} + +struct _zip_crypto_hmac_s { + BCRYPT_ALG_HANDLE hAlgorithm; + BCRYPT_HASH_HANDLE hHash; + DWORD cbHashObject; + PUCHAR pbHashObject; + DWORD cbHash; + PUCHAR pbHash; +}; + +/* https://code.msdn.microsoft.com/windowsdesktop/Hmac-Computation-Sample-11fe8ec1/sourcecode?fileId=42820&pathId=283874677 */ + +_zip_crypto_hmac_t * +_zip_crypto_hmac_new(const zip_uint8_t *secret, zip_uint64_t secret_length, zip_error_t *error) { + NTSTATUS status; + ULONG cbResult; + _zip_crypto_hmac_t *hmac; + + if (secret_length > INT_MAX) { + zip_error_set(error, ZIP_ER_INVAL, 0); + return NULL; + } + + hmac = (_zip_crypto_hmac_t *)calloc(1, sizeof(*hmac)); + + if (hmac == NULL) { + zip_error_set(error, ZIP_ER_MEMORY, 0); + return NULL; + } + + status = BCryptOpenAlgorithmProvider(&hmac->hAlgorithm, BCRYPT_SHA1_ALGORITHM, NULL, BCRYPT_ALG_HANDLE_HMAC_FLAG); + if (!BCRYPT_SUCCESS(status)) { + _zip_crypto_hmac_free(hmac); + return NULL; + } + + status = BCryptGetProperty(hmac->hAlgorithm, BCRYPT_OBJECT_LENGTH, (PUCHAR)&hmac->cbHashObject, sizeof(hmac->cbHashObject), &cbResult, 0); + if (!BCRYPT_SUCCESS(status)) { + _zip_crypto_hmac_free(hmac); + return NULL; + } + + hmac->pbHashObject = malloc(hmac->cbHashObject); + if (hmac->pbHashObject == NULL) { + _zip_crypto_hmac_free(hmac); + zip_error_set(error, ZIP_ER_MEMORY, 0); + return NULL; + } + + status = BCryptGetProperty(hmac->hAlgorithm, BCRYPT_HASH_LENGTH, (PUCHAR)&hmac->cbHash, sizeof(hmac->cbHash), &cbResult, 0); + if (!BCRYPT_SUCCESS(status)) { + _zip_crypto_hmac_free(hmac); + return NULL; + } + + hmac->pbHash = malloc(hmac->cbHash); + if (hmac->pbHash == NULL) { + _zip_crypto_hmac_free(hmac); + zip_error_set(error, ZIP_ER_MEMORY, 0); + return NULL; + } + + status = BCryptCreateHash(hmac->hAlgorithm, &hmac->hHash, hmac->pbHashObject, hmac->cbHashObject, (PUCHAR)secret, (ULONG)secret_length, 0); + if (!BCRYPT_SUCCESS(status)) { + _zip_crypto_hmac_free(hmac); + return NULL; + } + + return hmac; +} + +void +_zip_crypto_hmac_free(_zip_crypto_hmac_t *hmac) { + if (hmac == NULL) { + return; + } + + if (hmac->hHash != NULL) { + BCryptDestroyHash(hmac->hHash); + } + + if (hmac->pbHash != NULL) { + free(hmac->pbHash); + } + + if (hmac->pbHashObject != NULL) { + free(hmac->pbHashObject); + } + + if (hmac->hAlgorithm) { + BCryptCloseAlgorithmProvider(hmac->hAlgorithm, 0); + } + + free(hmac); +} + +bool +_zip_crypto_hmac(_zip_crypto_hmac_t *hmac, zip_uint8_t *data, zip_uint64_t length) { + if (hmac == NULL || length > ULONG_MAX) { + return false; + } + + return BCRYPT_SUCCESS(BCryptHashData(hmac->hHash, data, (ULONG)length, 0)); +} + +bool +_zip_crypto_hmac_output(_zip_crypto_hmac_t *hmac, zip_uint8_t *data) { + if (hmac == NULL) { + return false; + } + + return BCRYPT_SUCCESS(BCryptFinishHash(hmac->hHash, data, hmac->cbHash, 0)); +} + +ZIP_EXTERN bool +zip_secure_random(zip_uint8_t *buffer, zip_uint16_t length) { + return BCRYPT_SUCCESS(BCryptGenRandom(NULL, buffer, length, BCRYPT_USE_SYSTEM_PREFERRED_RNG)); +} |