diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
commit | a9bcc81f821d7c66f623779fa5147e728eb3c388 (patch) | |
tree | 98676963bcdd537ae5908a067a8eb110b93486a6 /winpr/libwinpr/crypto/hash.c | |
parent | Initial commit. (diff) | |
download | freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip |
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'winpr/libwinpr/crypto/hash.c')
-rw-r--r-- | winpr/libwinpr/crypto/hash.c | 780 |
1 files changed, 780 insertions, 0 deletions
diff --git a/winpr/libwinpr/crypto/hash.c b/winpr/libwinpr/crypto/hash.c new file mode 100644 index 0000000..92ece48 --- /dev/null +++ b/winpr/libwinpr/crypto/hash.c @@ -0,0 +1,780 @@ +/** + * WinPR: Windows Portable Runtime + * + * Copyright 2015 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/config.h> + +#include <winpr/crt.h> +#include <winpr/assert.h> +#include <winpr/crypto.h> + +#ifdef WITH_OPENSSL +#include <openssl/md4.h> +#include <openssl/md5.h> +#include <openssl/sha.h> +#include <openssl/evp.h> +#include <openssl/hmac.h> +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include <openssl/core_names.h> +#endif +#endif + +#ifdef WITH_MBEDTLS +#ifdef MBEDTLS_MD5_C +#include <mbedtls/md5.h> +#endif +#include <mbedtls/sha1.h> +#include <mbedtls/md.h> +#if MBEDTLS_VERSION_MAJOR < 3 +#define mbedtls_md_info_from_ctx(_ctx) (_ctx->md_info) +#endif +#endif + +#if defined(WITH_INTERNAL_MD4) +#include "md4.h" +#endif + +#if defined(WITH_INTERNAL_MD5) +#include "md5.h" +#include "hmac_md5.h" +#endif + +#include "../log.h" +#define TAG WINPR_TAG("crypto.hash") + +/** + * HMAC + */ + +#ifdef WITH_OPENSSL +extern const EVP_MD* winpr_openssl_get_evp_md(WINPR_MD_TYPE md); +#endif + +#ifdef WITH_OPENSSL +const EVP_MD* winpr_openssl_get_evp_md(WINPR_MD_TYPE md) +{ + const char* name = winpr_md_type_to_string(md); + if (!name) + return NULL; + return EVP_get_digestbyname(name); +} +#endif + +#ifdef WITH_MBEDTLS +mbedtls_md_type_t winpr_mbedtls_get_md_type(int md) +{ + mbedtls_md_type_t type = MBEDTLS_MD_NONE; + + switch (md) + { + case WINPR_MD_MD5: + type = MBEDTLS_MD_MD5; + break; + + case WINPR_MD_SHA1: + type = MBEDTLS_MD_SHA1; + break; + + case WINPR_MD_SHA224: + type = MBEDTLS_MD_SHA224; + break; + + case WINPR_MD_SHA256: + type = MBEDTLS_MD_SHA256; + break; + + case WINPR_MD_SHA384: + type = MBEDTLS_MD_SHA384; + break; + + case WINPR_MD_SHA512: + type = MBEDTLS_MD_SHA512; + break; + } + + return type; +} +#endif + +struct hash_map +{ + const char* name; + WINPR_MD_TYPE md; +}; +static const struct hash_map hashes[] = { { "md2", WINPR_MD_MD2 }, + { "md4", WINPR_MD_MD4 }, + { "md5", WINPR_MD_MD5 }, + { "sha1", WINPR_MD_SHA1 }, + { "sha224", WINPR_MD_SHA224 }, + { "sha256", WINPR_MD_SHA256 }, + { "sha384", WINPR_MD_SHA384 }, + { "sha512", WINPR_MD_SHA512 }, + { "sha3_224", WINPR_MD_SHA3_224 }, + { "sha3_256", WINPR_MD_SHA3_256 }, + { "sha3_384", WINPR_MD_SHA3_384 }, + { "sha3_512", WINPR_MD_SHA3_512 }, + { "shake128", WINPR_MD_SHAKE128 }, + { "shake256", WINPR_MD_SHAKE256 }, + { NULL, WINPR_MD_NONE } }; + +WINPR_MD_TYPE winpr_md_type_from_string(const char* name) +{ + const struct hash_map* cur = hashes; + while (cur->name) + { + if (_stricmp(cur->name, name) == 0) + return cur->md; + cur++; + } + return WINPR_MD_NONE; +} + +const char* winpr_md_type_to_string(WINPR_MD_TYPE md) +{ + const struct hash_map* cur = hashes; + while (cur->name) + { + if (cur->md == md) + return cur->name; + cur++; + } + return NULL; +} + +struct winpr_hmac_ctx_private_st +{ + WINPR_MD_TYPE md; + +#if defined(WITH_INTERNAL_MD5) + WINPR_HMAC_MD5_CTX hmac_md5; +#endif +#if defined(WITH_OPENSSL) +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MAC_CTX* xhmac; +#else + HMAC_CTX* hmac; +#endif +#endif +#if defined(WITH_MBEDTLS) + mbedtls_md_context_t hmac; +#endif +}; + +WINPR_HMAC_CTX* winpr_HMAC_New(void) +{ + WINPR_HMAC_CTX* ctx = (WINPR_HMAC_CTX*)calloc(1, sizeof(WINPR_HMAC_CTX)); + if (!ctx) + return NULL; +#if defined(WITH_OPENSSL) +#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \ + (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL) + + if (!(ctx->hmac = (HMAC_CTX*)calloc(1, sizeof(HMAC_CTX)))) + goto fail; + + HMAC_CTX_init(ctx->hmac); +#elif OPENSSL_VERSION_NUMBER < 0x30000000L + if (!(ctx->hmac = HMAC_CTX_new())) + goto fail; +#else + EVP_MAC* emac = EVP_MAC_fetch(NULL, "HMAC", NULL); + if (!emac) + goto fail; + ctx->xhmac = EVP_MAC_CTX_new(emac); + EVP_MAC_free(emac); + if (!ctx->xhmac) + goto fail; +#endif +#elif defined(WITH_MBEDTLS) + mbedtls_md_init(&ctx->hmac); +#endif + return ctx; + +fail: + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + winpr_HMAC_Free(ctx); + WINPR_PRAGMA_DIAG_POP + return NULL; +} + +BOOL winpr_HMAC_Init(WINPR_HMAC_CTX* ctx, WINPR_MD_TYPE md, const void* key, size_t keylen) +{ + WINPR_ASSERT(ctx); + + ctx->md = md; + switch (ctx->md) + { +#if defined(WITH_INTERNAL_MD5) + case WINPR_MD_MD5: + hmac_md5_init(&ctx->hmac_md5, key, keylen); + return TRUE; +#endif + default: + break; + } + +#if defined(WITH_OPENSSL) +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + const char* hash = winpr_md_type_to_string(md); + + if (!ctx->xhmac) + return FALSE; + + const char* param_name = OSSL_MAC_PARAM_DIGEST; + const OSSL_PARAM param[] = { OSSL_PARAM_construct_utf8_string(param_name, hash, 0), + OSSL_PARAM_construct_end() }; + + if (EVP_MAC_init(ctx->xhmac, key, keylen, param) == 1) + return TRUE; +#else + HMAC_CTX* hmac = ctx->hmac; + const EVP_MD* evp = winpr_openssl_get_evp_md(md); + + if (!evp || !hmac) + return FALSE; + + if (keylen > INT_MAX) + return FALSE; +#if (OPENSSL_VERSION_NUMBER < 0x10000000L) || \ + (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL) + HMAC_Init_ex(hmac, key, (int)keylen, evp, NULL); /* no return value on OpenSSL 0.9.x */ + return TRUE; +#else + + if (HMAC_Init_ex(hmac, key, (int)keylen, evp, NULL) == 1) + return TRUE; + +#endif +#endif +#elif defined(WITH_MBEDTLS) + mbedtls_md_context_t* hmac = &ctx->hmac; + mbedtls_md_type_t md_type = winpr_mbedtls_get_md_type(md); + const mbedtls_md_info_t* md_info = mbedtls_md_info_from_type(md_type); + + if (!md_info || !hmac) + return FALSE; + + if (mbedtls_md_info_from_ctx(hmac) != md_info) + { + mbedtls_md_free(hmac); /* can be called at any time after mbedtls_md_init */ + + if (mbedtls_md_setup(hmac, md_info, 1) != 0) + return FALSE; + } + + if (mbedtls_md_hmac_starts(hmac, key, keylen) == 0) + return TRUE; + +#endif + return FALSE; +} + +BOOL winpr_HMAC_Update(WINPR_HMAC_CTX* ctx, const void* input, size_t ilen) +{ + WINPR_ASSERT(ctx); + + switch (ctx->md) + { +#if defined(WITH_INTERNAL_MD5) + case WINPR_MD_MD5: + hmac_md5_update(&ctx->hmac_md5, input, ilen); + return TRUE; +#endif + default: + break; + } + +#if defined(WITH_OPENSSL) +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + if (EVP_MAC_update(ctx->xhmac, input, ilen) == 1) + return TRUE; +#else + HMAC_CTX* hmac = ctx->hmac; +#if (OPENSSL_VERSION_NUMBER < 0x10000000L) || \ + (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL) + HMAC_Update(hmac, input, ilen); /* no return value on OpenSSL 0.9.x */ + return TRUE; +#else + + if (HMAC_Update(hmac, input, ilen) == 1) + return TRUE; +#endif +#endif +#elif defined(WITH_MBEDTLS) + mbedtls_md_context_t* mdctx = &ctx->hmac; + + if (mbedtls_md_hmac_update(mdctx, input, ilen) == 0) + return TRUE; + +#endif + return FALSE; +} + +BOOL winpr_HMAC_Final(WINPR_HMAC_CTX* ctx, void* output, size_t olen) +{ + WINPR_ASSERT(ctx); + + switch (ctx->md) + { +#if defined(WITH_INTERNAL_MD5) + case WINPR_MD_MD5: + if (olen < WINPR_MD5_DIGEST_LENGTH) + return FALSE; + hmac_md5_finalize(&ctx->hmac_md5, output); + return TRUE; +#endif + default: + break; + } + +#if defined(WITH_OPENSSL) +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + const int rc = EVP_MAC_final(ctx->xhmac, output, NULL, olen); + if (rc == 1) + return TRUE; +#else + HMAC_CTX* hmac = ctx->hmac; +#if (OPENSSL_VERSION_NUMBER < 0x10000000L) || \ + (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL) + HMAC_Final(hmac, output, NULL); /* no return value on OpenSSL 0.9.x */ + return TRUE; +#else + + if (HMAC_Final(hmac, output, NULL) == 1) + return TRUE; + +#endif +#endif +#elif defined(WITH_MBEDTLS) + mbedtls_md_context_t* mdctx = &ctx->hmac; + + if (mbedtls_md_hmac_finish(mdctx, output) == 0) + return TRUE; + +#endif + return FALSE; +} + +void winpr_HMAC_Free(WINPR_HMAC_CTX* ctx) +{ + if (!ctx) + return; + +#if defined(WITH_OPENSSL) +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MAC_CTX_free(ctx->xhmac); +#else + HMAC_CTX* hmac = ctx->hmac; + + if (hmac) + { +#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \ + (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL) + HMAC_CTX_cleanup(hmac); + free(hmac); +#else + HMAC_CTX_free(hmac); +#endif + } +#endif +#elif defined(WITH_MBEDTLS) + mbedtls_md_context_t* hmac = &ctx->hmac; + + if (hmac) + mbedtls_md_free(hmac); + +#endif + + free(ctx); +} + +BOOL winpr_HMAC(WINPR_MD_TYPE md, const void* key, size_t keylen, const void* input, size_t ilen, + void* output, size_t olen) +{ + BOOL result = FALSE; + WINPR_HMAC_CTX* ctx = winpr_HMAC_New(); + + if (!ctx) + return FALSE; + + if (!winpr_HMAC_Init(ctx, md, key, keylen)) + goto out; + + if (!winpr_HMAC_Update(ctx, input, ilen)) + goto out; + + if (!winpr_HMAC_Final(ctx, output, olen)) + goto out; + + result = TRUE; +out: + winpr_HMAC_Free(ctx); + return result; +} + +/** + * Generic Digest API + */ + +struct winpr_digest_ctx_private_st +{ + WINPR_MD_TYPE md; + +#if defined(WITH_INTERNAL_MD4) + WINPR_MD4_CTX md4; +#endif +#if defined(WITH_INTERNAL_MD5) + WINPR_MD5_CTX md5; +#endif +#if defined(WITH_OPENSSL) + EVP_MD_CTX* mdctx; +#endif +#if defined(WITH_MBEDTLS) + mbedtls_md_context_t* mdctx; +#endif +}; + +WINPR_DIGEST_CTX* winpr_Digest_New(void) +{ + WINPR_DIGEST_CTX* ctx = calloc(1, sizeof(WINPR_DIGEST_CTX)); + if (!ctx) + return NULL; + +#if defined(WITH_OPENSSL) +#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \ + (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL) + ctx->mdctx = EVP_MD_CTX_create(); +#else + ctx->mdctx = EVP_MD_CTX_new(); +#endif + if (!ctx->mdctx) + goto fail; + +#elif defined(WITH_MBEDTLS) + ctx->mdctx = (mbedtls_md_context_t*)calloc(1, sizeof(mbedtls_md_context_t)); + + if (!ctx->mdctx) + goto fail; + + mbedtls_md_init(ctx->mdctx); +#endif + return ctx; + +fail: + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + winpr_Digest_Free(ctx); + WINPR_PRAGMA_DIAG_POP + return NULL; +} + +#if defined(WITH_OPENSSL) +static BOOL winpr_Digest_Init_Internal(WINPR_DIGEST_CTX* ctx, const EVP_MD* evp) +{ + WINPR_ASSERT(ctx); + EVP_MD_CTX* mdctx = ctx->mdctx; + + if (!mdctx || !evp) + return FALSE; + + if (EVP_DigestInit_ex(mdctx, evp, NULL) != 1) + { + WLog_ERR(TAG, "Failed to initialize digest %s", winpr_md_type_to_string(ctx->md)); + return FALSE; + } + + return TRUE; +} + +#elif defined(WITH_MBEDTLS) +static BOOL winpr_Digest_Init_Internal(WINPR_DIGEST_CTX* ctx, WINPR_MD_TYPE md) +{ + WINPR_ASSERT(ctx); + mbedtls_md_context_t* mdctx = ctx->mdctx; + mbedtls_md_type_t md_type = winpr_mbedtls_get_md_type(md); + const mbedtls_md_info_t* md_info = mbedtls_md_info_from_type(md_type); + + if (!md_info) + return FALSE; + + if (mbedtls_md_info_from_ctx(mdctx) != md_info) + { + mbedtls_md_free(mdctx); /* can be called at any time after mbedtls_md_init */ + + if (mbedtls_md_setup(mdctx, md_info, 0) != 0) + return FALSE; + } + + if (mbedtls_md_starts(mdctx) != 0) + return FALSE; + + return TRUE; +} +#endif + +BOOL winpr_Digest_Init_Allow_FIPS(WINPR_DIGEST_CTX* ctx, WINPR_MD_TYPE md) +{ + WINPR_ASSERT(ctx); + + ctx->md = md; + switch (md) + { + case WINPR_MD_MD5: +#if defined(WITH_INTERNAL_MD5) + winpr_MD5_Init(&ctx->md5); + return TRUE; +#endif + break; + default: + WLog_ERR(TAG, "Invalid FIPS digest %s requested", winpr_md_type_to_string(md)); + return FALSE; + } + +#if defined(WITH_OPENSSL) + const EVP_MD* evp = winpr_openssl_get_evp_md(md); + EVP_MD_CTX_set_flags(ctx->mdctx, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); + return winpr_Digest_Init_Internal(ctx, evp); +#elif defined(WITH_MBEDTLS) + return winpr_Digest_Init_Internal(ctx, md); +#endif +} + +BOOL winpr_Digest_Init(WINPR_DIGEST_CTX* ctx, WINPR_MD_TYPE md) +{ + WINPR_ASSERT(ctx); + + ctx->md = md; + switch (md) + { +#if defined(WITH_INTERNAL_MD4) + case WINPR_MD_MD4: + winpr_MD4_Init(&ctx->md4); + return TRUE; +#endif +#if defined(WITH_INTERNAL_MD5) + case WINPR_MD_MD5: + winpr_MD5_Init(&ctx->md5); + return TRUE; +#endif + default: + break; + } + +#if defined(WITH_OPENSSL) + const EVP_MD* evp = winpr_openssl_get_evp_md(md); + return winpr_Digest_Init_Internal(ctx, evp); +#else + return winpr_Digest_Init_Internal(ctx, md); +#endif +} + +BOOL winpr_Digest_Update(WINPR_DIGEST_CTX* ctx, const void* input, size_t ilen) +{ + WINPR_ASSERT(ctx); + + switch (ctx->md) + { +#if defined(WITH_INTERNAL_MD4) + case WINPR_MD_MD4: + winpr_MD4_Update(&ctx->md4, input, ilen); + return TRUE; +#endif +#if defined(WITH_INTERNAL_MD5) + case WINPR_MD_MD5: + winpr_MD5_Update(&ctx->md5, input, ilen); + return TRUE; +#endif + default: + break; + } + +#if defined(WITH_OPENSSL) + EVP_MD_CTX* mdctx = ctx->mdctx; + + if (EVP_DigestUpdate(mdctx, input, ilen) != 1) + return FALSE; + +#elif defined(WITH_MBEDTLS) + mbedtls_md_context_t* mdctx = ctx->mdctx; + + if (mbedtls_md_update(mdctx, input, ilen) != 0) + return FALSE; + +#endif + return TRUE; +} + +BOOL winpr_Digest_Final(WINPR_DIGEST_CTX* ctx, void* output, size_t olen) +{ + WINPR_ASSERT(ctx); + + switch (ctx->md) + { +#if defined(WITH_INTERNAL_MD4) + case WINPR_MD_MD4: + if (olen < WINPR_MD4_DIGEST_LENGTH) + return FALSE; + winpr_MD4_Final(output, &ctx->md4); + return TRUE; +#endif +#if defined(WITH_INTERNAL_MD5) + case WINPR_MD_MD5: + if (olen < WINPR_MD5_DIGEST_LENGTH) + return FALSE; + winpr_MD5_Final(output, &ctx->md5); + return TRUE; +#endif + + default: + break; + } + +#if defined(WITH_OPENSSL) + EVP_MD_CTX* mdctx = ctx->mdctx; + + if (EVP_DigestFinal_ex(mdctx, output, NULL) == 1) + return TRUE; + +#elif defined(WITH_MBEDTLS) + mbedtls_md_context_t* mdctx = ctx->mdctx; + + if (mbedtls_md_finish(mdctx, output) == 0) + return TRUE; + +#endif + return FALSE; +} + +BOOL winpr_DigestSign_Init(WINPR_DIGEST_CTX* ctx, WINPR_MD_TYPE digest, void* key) +{ + WINPR_ASSERT(ctx); + +#if defined(WITH_OPENSSL) + const EVP_MD* evp = winpr_openssl_get_evp_md(digest); + if (!evp) + return FALSE; + + const int rdsi = EVP_DigestSignInit(ctx->mdctx, NULL, evp, NULL, key); + if (rdsi <= 0) + return FALSE; + return TRUE; +#else + return FALSE; +#endif +} + +BOOL winpr_DigestSign_Update(WINPR_DIGEST_CTX* ctx, const void* input, size_t ilen) +{ + WINPR_ASSERT(ctx); + +#if defined(WITH_OPENSSL) + EVP_MD_CTX* mdctx = ctx->mdctx; + + if (EVP_DigestSignUpdate(mdctx, input, ilen) != 1) + return FALSE; + return TRUE; +#else + return FALSE; +#endif +} + +BOOL winpr_DigestSign_Final(WINPR_DIGEST_CTX* ctx, void* output, size_t* piolen) +{ + WINPR_ASSERT(ctx); + +#if defined(WITH_OPENSSL) + EVP_MD_CTX* mdctx = ctx->mdctx; + + return EVP_DigestSignFinal(mdctx, output, piolen) == 1; +#else + return FALSE; +#endif +} + +void winpr_Digest_Free(WINPR_DIGEST_CTX* ctx) +{ + if (!ctx) + return; +#if defined(WITH_OPENSSL) + if (ctx->mdctx) + { +#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \ + (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL) + EVP_MD_CTX_destroy(ctx->mdctx); +#else + EVP_MD_CTX_free(ctx->mdctx); +#endif + } + +#elif defined(WITH_MBEDTLS) + if (ctx->mdctx) + { + mbedtls_md_free(ctx->mdctx); + free(ctx->mdctx); + } + +#endif + free(ctx); +} + +BOOL winpr_Digest_Allow_FIPS(WINPR_MD_TYPE md, const void* input, size_t ilen, void* output, + size_t olen) +{ + BOOL result = FALSE; + WINPR_DIGEST_CTX* ctx = winpr_Digest_New(); + + if (!ctx) + return FALSE; + + if (!winpr_Digest_Init_Allow_FIPS(ctx, md)) + goto out; + + if (!winpr_Digest_Update(ctx, input, ilen)) + goto out; + + if (!winpr_Digest_Final(ctx, output, olen)) + goto out; + + result = TRUE; +out: + winpr_Digest_Free(ctx); + return result; +} + +BOOL winpr_Digest(WINPR_MD_TYPE md, const void* input, size_t ilen, void* output, size_t olen) +{ + BOOL result = FALSE; + WINPR_DIGEST_CTX* ctx = winpr_Digest_New(); + + if (!ctx) + return FALSE; + + if (!winpr_Digest_Init(ctx, md)) + goto out; + + if (!winpr_Digest_Update(ctx, input, ilen)) + goto out; + + if (!winpr_Digest_Final(ctx, output, olen)) + goto out; + + result = TRUE; +out: + winpr_Digest_Free(ctx); + return result; +} |