465 lines
14 KiB
C
465 lines
14 KiB
C
/*
|
|
zip_crypto_win.c -- Windows Crypto API wrapper.
|
|
Copyright (C) 2018 Dieter Baron and Thomas Klausner
|
|
|
|
This file is part of libzip, a library to manipulate ZIP archives.
|
|
The authors can be contacted at <libzip@nih.at>
|
|
|
|
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 "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;
|
|
|
|
if (!BCRYPT_SUCCESS(BCryptOpenAlgorithmProvider(&hAlgorithm, BCRYPT_SHA1_ALGORITHM, NULL, BCRYPT_ALG_HANDLE_HMAC_FLAG))) {
|
|
return false;
|
|
}
|
|
|
|
bool 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);
|
|
|
|
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 (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 xor
|
|
(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 (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
|
|
memcpy(U, 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 {
|
|
memcpy(U, V, DIGEST_SIZE);
|
|
dwULen = DIGEST_SIZE;
|
|
}
|
|
|
|
if (!hmacCalculate(&prfCtx, U, dwULen, V)) {
|
|
goto PBKDF2_end;
|
|
}
|
|
|
|
xor(Ti, V, DIGEST_SIZE);
|
|
}
|
|
|
|
if (i != l) {
|
|
memcpy(&pbDerivedKey[(i - 1) * DIGEST_SIZE], Ti, DIGEST_SIZE);
|
|
}
|
|
else {
|
|
// Take only the first r bytes
|
|
memcpy(&pbDerivedKey[(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 (!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);
|
|
|
|
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);
|
|
|
|
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_random(zip_uint8_t *buffer, zip_uint16_t length) {
|
|
return BCRYPT_SUCCESS(BCryptGenRandom(NULL, buffer, length, BCRYPT_USE_SYSTEM_PREFERRED_RNG));
|
|
}
|