ervu-sign-module/src/utils/cryptopro.c

1168 lines
32 KiB
C
Raw Normal View History

#include "cryptopro.h"
#include "base64.h"
#include "capi.h"
#include "library.h"
#include "logger.h"
static const char* CERT_TRUST_IS_UNTRUSTED_ROOT_ERR_CODE = "CERT_TRUST_IS_UNTRUSTED_ROOT";
static const char* CERT_TRUST_REVOCATION_STATUS_UNKNOWN_ERR_CODE = "CERT_TRUST_REVOCATION_STATUS_UNKNOWN";
static const char* CERT_TRUST_IS_NOT_TIME_VALID_ERR_CODE = "CERT_TRUST_IS_NOT_TIME_VALID";
static capi_function_list_t cp_function_list;
static library_t libcapi;
static int sign_hash_data(const cryptopro_context_t *ctx, const str_t *data,
/*out*/ str_t *sign, /*out*/ const char **error_code);
static int reverse_sign(const str_t *sign, /*out*/ str_t *sign_reversed);
static HCERTSTORE cert_open_store();
static void cert_close_store(HCERTSTORE hStoreHandle);
static PCCERT_CONTEXT get_signer_cert(const cryptopro_context_t *ctx, HCERTSTORE hStoreHandle);
static int set_password(PCCERT_CONTEXT cert_ctx, const str_t *password);
bool
cryptopro_init(const char* cp_file)
{
static bool is_initialized = false;
if (!is_initialized) {
library_init(&libcapi, cp_file);
if (!capi_function_list_init(&libcapi, &cp_function_list)) {
LOG_ERROR("capi_function_list_init failed");
return false;
}
is_initialized = true;
LOG_INFO("Cryptographic Provider initialized");
}
return true;
}
int
cryptopro_sign(const cryptopro_context_t *ctx, const str_t *data,
/*out*/ str_t *sign, /*out*/ const char **error_code)
{
str_t signed_data = str_t_null;
str_t sign_reversed = str_t_null;
LOG_TRACE("cryptopro_sign enter");
assert(ctx != NULL);
assert(!str_t_is_null(*ctx->cert_thumbprint));
assert(ctx->password != NULL);
assert(data != NULL && !str_t_is_null(*data));
assert(sign != NULL);
if (sign_hash_data(ctx, data, &signed_data, error_code)) {
goto error;
}
if (reverse_sign(&signed_data, &sign_reversed)) {
goto error;
}
sign->len = base64_encoded_length(sign_reversed.len);
sign->data = calloc(1, sign->len + 1);
if (sign->data == NULL) {
LOG_ERROR("Could not allocate memory for sign (%zu bytes)", sign->len + 1);
goto error;
}
encode_base64_url(sign, &sign_reversed);
str_t_clear(&signed_data);
str_t_clear(&sign_reversed);
LOG_TRACE("cryptopro_sign exit");
return 0;
error:
str_t_clear(&signed_data);
str_t_clear(&sign_reversed);
LOG_ERROR("cryptopro_sign exit with error (sign_cert_thumbprint = '%.*s')",
(int) ctx->cert_thumbprint->len, ctx->cert_thumbprint->data);
return -1;
}
static void
free_cert_chain(PCCERT_CHAIN_CONTEXT chain_ctx)
{
LOG_TRACE("free_cert_chain enter");
if (chain_ctx) {
cp_function_list.CertFreeCertificateChain(chain_ctx);
}
LOG_TRACE("free_cert_chain exit");
}
static void
print_crl_urls_from_certificate(PCCERT_CONTEXT cert)
{
LOG_TRACE("print_crl_urls_from_certificate enter");
PCERT_EXTENSION extension = cp_function_list.CertFindExtension(
szOID_CRL_DIST_POINTS,
cert->pCertInfo->cExtension,
cert->pCertInfo->rgExtension
);
if (!extension) {
LOG_WARN("CRL Distribution Points extension not found");
goto error;
}
DWORD info_size = 0;
CRL_DIST_POINTS_INFO *crl_dist_points = NULL;
if (!cp_function_list.CryptDecodeObjectEx(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
X509_CRL_DIST_POINTS,
extension->Value.pbData,
extension->Value.cbData,
CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG,
NULL,
&crl_dist_points,
&info_size
)) {
LOG_ERROR("CryptDecodeObjectEx failed. Desc: 0x%x", cp_function_list.GetLastError());
goto error;
}
if (crl_dist_points && crl_dist_points->cDistPoint > 0) {
for (DWORD i = 0; i < crl_dist_points->cDistPoint; i++) {
PCRL_DIST_POINT pDistPoint = &crl_dist_points->rgDistPoint[i];
if (pDistPoint->DistPointName.dwDistPointNameChoice == CRL_DIST_POINT_FULL_NAME) {
PCERT_ALT_NAME_ENTRY pAltNameEntry = pDistPoint->DistPointName._empty_union_.FullName.rgAltEntry;
for (DWORD j = 0; j < pDistPoint->DistPointName._empty_union_.FullName.cAltEntry; j++) {
if (pAltNameEntry[j].dwAltNameChoice == CERT_ALT_NAME_URL) {
LOG_INFO("CRL URL: %ls", pAltNameEntry[j]._empty_union_.pwszURL);
}
}
}
}
}
if (crl_dist_points) {
cp_function_list.LocalFree(crl_dist_points);
}
LOG_TRACE("print_crl_urls_from_certificate exit");
return;
error:
LOG_ERROR("print_crl_urls_from_certificate exit with error");
return;
}
static void
process_trust_status_error(PCCERT_CONTEXT cert, DWORD status, /*out*/ const char **error_code)
{
LOG_TRACE("process_trust_status_error enter");
switch (status) {
case CERT_TRUST_REVOCATION_STATUS_UNKNOWN:
*error_code = CERT_TRUST_REVOCATION_STATUS_UNKNOWN_ERR_CODE;
print_crl_urls_from_certificate(cert);
break;
case CERT_TRUST_IS_UNTRUSTED_ROOT:
*error_code = CERT_TRUST_IS_UNTRUSTED_ROOT_ERR_CODE;
break;
case CERT_TRUST_IS_NOT_TIME_VALID:
*error_code = CERT_TRUST_IS_NOT_TIME_VALID_ERR_CODE;
break;
default:
*error_code = NULL;
break;
}
if (*error_code) {
LOG_WARN("The certificate is not trusted. CERT_TRUST_STATUS: '0x%08x' (%s)",
status, *error_code);
} else {
LOG_WARN("The certificate is not trusted. CERT_TRUST_STATUS: '0x%08x'", status);
}
LOG_TRACE("process_trust_status_error exit");
}
static PCCERT_CHAIN_CONTEXT
get_cert_chain(PCCERT_CONTEXT certificate, /*out*/ const char **error_code)
{
LOG_TRACE("get_cert_chain enter");
CERT_CHAIN_PARA chain_para = {0};
chain_para.cbSize = sizeof(CERT_CHAIN_PARA);
PCCERT_CHAIN_CONTEXT chain_ctx = NULL;
if (!cp_function_list.CertGetCertificateChain(NULL,
certificate,
NULL,
NULL,
&chain_para,
(CERT_CHAIN_CACHE_END_CERT | CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT),
NULL,
&chain_ctx)) {
LOG_ERROR("CertGetCertificateChain() failed");
goto error;
}
if (chain_ctx->TrustStatus.dwErrorStatus) {
process_trust_status_error(certificate, chain_ctx->TrustStatus.dwErrorStatus, error_code);
goto error;
}
LOG_TRACE("get_cert_chain exit");
return chain_ctx;
error:
free_cert_chain(chain_ctx);
LOG_ERROR("get_cert_chain exit with error");
return NULL;
}
static bool
check_cert_chain_policy(PCCERT_CHAIN_CONTEXT chain_ctx)
{
LOG_TRACE("check_cert_chain_policy enter");
bool is_valid = false;
CERT_CHAIN_POLICY_PARA policy_para = {0};
policy_para.cbSize = sizeof(policy_para);
CERT_CHAIN_POLICY_STATUS status = {0};
status.cbSize = sizeof(status);
CPSIGNATURE_EXTRA_CERT_CHAIN_POLICY_STATUS extraStatus = {0};
extraStatus.cbSize = sizeof(extraStatus);
status.pvExtraPolicyStatus = &extraStatus;
if (!cp_function_list.CertVerifyCertificateChainPolicy(
CERT_CHAIN_POLICY_BASE,
chain_ctx,
&policy_para,
&status)) {
LOG_ERROR("CertVerifyCertificateChainPolicy() failed. Error code: 0x%08x",
cp_function_list.GetLastError());
goto exit;
}
if (status.dwError != 0) {
LOG_WARN("The certificate chain cannot be validated. "
"CERT_CHAIN_POLICY_STATUS: '0x%08x'", status.dwError);
goto exit;
}
if (extraStatus.dwError != 0) {
LOG_WARN("The certificate chain cannot be validated. "
"CPSIGNATURE_EXTRA_CERT_CHAIN_POLICY_STATUS: '0x%08x'", extraStatus.dwError);
goto exit;
}
is_valid = true;
exit:
LOG_TRACE("check_cert_chain_policy exit");
return is_valid;
}
static bool
verify_cert_chain(PCCERT_CONTEXT certificate, timer_context_t *timer_ctx, /*out*/ const char **error_code)
{
bool is_verified = false;
LOG_TRACE("verify_cert_chain enter");
timer_on_verify_cert_chain_enter(timer_ctx);
timer_on_get_cert_chain_enter(timer_ctx);
PCCERT_CHAIN_CONTEXT chain_ctx = get_cert_chain(certificate, error_code);
if (chain_ctx == NULL) {
goto exit;
}
timer_on_get_cert_chain_exit(timer_ctx);
if (!check_cert_chain_policy(chain_ctx)) {
goto exit;
}
is_verified = true;
exit:
free_cert_chain(chain_ctx);
LOG_DEBUG("cert_chain: %s", is_verified ? "verified" : "failed");
timer_on_verify_cert_chain_exit(timer_ctx);
LOG_DEBUG("verify_cert_chain exit");
return is_verified;
}
static bool
check_cert_key_usage(PCCERT_CONTEXT certificate)
{
bool is_digital_signature_key_usage = false;
BYTE key_usage;
LOG_TRACE("check_cert_key_usage enter");
if (cp_function_list.CertGetIntendedKeyUsage(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
certificate->pCertInfo,
&key_usage,
sizeof(key_usage))) {
if (key_usage & CERT_DIGITAL_SIGNATURE_KEY_USAGE) {
is_digital_signature_key_usage = true;
}
} else {
LOG_ERROR("CertGetIntendedKeyUsage failed: 0x%08x", cp_function_list.GetLastError());
}
if (is_digital_signature_key_usage) {
LOG_DEBUG("Certificate KeyUsage contains digitalSignature");
} else {
LOG_ERROR("Certificate KeyUsage does not contain digitalSignature");
}
LOG_TRACE("check_cert_key_usage exit");
return is_digital_signature_key_usage;
}
int
open_signer_cert(cryptopro_context_t *ctx)
{
LOG_TRACE("open_signer_cert enter");
ctx->cert_store = cert_open_store();
if (ctx->cert_store == NULL) {
goto error;
}
ctx->signer_cert = get_signer_cert(ctx, ctx->cert_store);
if (ctx->signer_cert == NULL) {
goto error;
}
if (!check_cert_key_usage(ctx->signer_cert)) {
goto error;
}
LOG_TRACE("open_signer_cert exit");
return 0;
error:
LOG_ERROR("open_signer_cert exit with error. Last error code: 0x%08x",
cp_function_list.GetLastError());
close_signer_cert(ctx);
return -1;
}
void
close_signer_cert(cryptopro_context_t *ctx)
{
LOG_TRACE("close_signer_cert enter");
if (ctx->signer_cert) {
cp_function_list.CertFreeCertificateContext(ctx->signer_cert);
ctx->signer_cert = NULL;
}
cert_close_store(ctx->cert_store);
ctx->cert_store = NULL;
LOG_TRACE("close_signer_cert exit");
}
static void
log_sign_hash_data_last_error()
{
DWORD error = cp_function_list.GetLastError();
switch (error) {
case ERROR_FUNCTION_FAILED:
LOG_ERROR("sign_hash_data exit with error. Last error code: "
"license is expired or not yet valid (0x%08x)", error);
break;
case SCARD_W_WRONG_CHV:
LOG_ERROR("sign_hash_data exit with error. Last error code: "
"the wrong PIN was presented (0x%08x)", error);
break;
default:
LOG_ERROR("sign_hash_data exit with error. Last error code: 0x%08x", error);
break;
}
}
static int
sign_hash_data(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t *sign, /*out*/ const char **error_code)
{
int rc = -1;
BOOL bReleaseContext = FALSE;
HCRYPTPROV hCryptProv;
HCRYPTHASH hash = 0;
BYTE *pbSignedMessageBlob;
DWORD cbSignedMessageBlob;
if (!verify_cert_chain(ctx->signer_cert, ctx->timer_ctx, error_code)) {
goto exit;
}
timer_on_acquire_private_key_enter(ctx->timer_ctx);
if (!cp_function_list.CryptAcquireCertificatePrivateKey(
ctx->signer_cert,
CRYPT_ACQUIRE_SILENT_FLAG | CRYPT_ACQUIRE_CACHE_FLAG,
NULL,
&hCryptProv,
NULL,
&bReleaseContext)) {
LOG_ERROR("Cannot acquire the certificate private key");
goto exit;
}
timer_on_acquire_private_key_exit(ctx->timer_ctx);
if (!cp_function_list.CryptCreateHash(hCryptProv, CALG_GR3411_2012_256, 0, 0, &hash)) {
LOG_ERROR("CryptCreateHash() failed");
goto exit;
}
if (!cp_function_list.CryptHashData(hash, (BYTE *)data->data, data->len, 0)) {
LOG_ERROR("CryptHashData() failed");
goto exit;
}
timer_on_sign_hash_enter(ctx->timer_ctx);
if (!cp_function_list.CryptSignHash(hash, AT_KEYEXCHANGE, NULL, 0, NULL, &cbSignedMessageBlob)) {
LOG_ERROR("CryptSignHash() failed");
goto exit;
}
pbSignedMessageBlob = malloc(cbSignedMessageBlob * sizeof(BYTE));
if (pbSignedMessageBlob == NULL) {
LOG_ERROR("Could not allocate memory for pbSignedMessageBlob (%zd bytes)", cbSignedMessageBlob * sizeof(BYTE));
goto exit;
}
if (!cp_function_list.CryptSignHash(hash, AT_KEYEXCHANGE, NULL, 0, pbSignedMessageBlob, &cbSignedMessageBlob)) {
LOG_ERROR("CryptSignHash() failed");
free(pbSignedMessageBlob);
goto exit;
}
timer_on_sign_hash_exit(ctx->timer_ctx);
sign->data = (char*)pbSignedMessageBlob;
sign->len = (size_t)cbSignedMessageBlob;
rc = 0;
exit:
if (hash) {
if (!cp_function_list.CryptDestroyHash(hash)) {
LOG_ERROR("CryptDestroyHash() failed");
rc = -1;
}
}
if (bReleaseContext) {
if (!cp_function_list.CryptReleaseContext(hCryptProv, 0)) {
LOG_ERROR("CryptReleaseContext() failed");
rc = -1;
}
}
if (rc) {
log_sign_hash_data_last_error();
}
return rc;
}
static int
reverse_sign(const str_t *sign, /*out*/ str_t *sign_reversed)
{
size_t i;
sign_reversed->data = malloc(sign->len);
if (sign_reversed->data == NULL) {
LOG_ERROR("Could not allocate memory for reversed sign (%zd bytes)", sign->len);
return -1;
}
for (i = 0; i < sign->len; i++) {
sign_reversed->data[i] = sign->data[sign->len - i - 1];
}
sign_reversed->len = sign->len;
return 0;
}
static HCERTSTORE
cert_open_store()
{
HCERTSTORE hStoreHandle;
LOG_TRACE("cert_open_store enter");
hStoreHandle = cp_function_list.CertOpenStore(CERT_STORE_PROV_SYSTEM,
0,
0,
CERT_SYSTEM_STORE_CURRENT_USER | CERT_STORE_OPEN_EXISTING_FLAG,
L"MY");
if (hStoreHandle == NULL) {
LOG_ERROR("The store could not be opened");
return NULL;
}
LOG_DEBUG("The MY store is open");
LOG_TRACE("cert_open_store exit");
return hStoreHandle;
}
static void
cert_close_store(HCERTSTORE hStoreHandle)
{
LOG_TRACE("cert_close_store enter");
if (hStoreHandle) {
if (!cp_function_list.CertCloseStore(hStoreHandle, CERT_CLOSE_STORE_CHECK_FLAG)) {
LOG_ERROR("CertCloseStore() failed");
}
}
LOG_TRACE("cert_close_store exit");
}
static PCCERT_CONTEXT
get_cert_by_thumbprint(HCERTSTORE hStoreHandle, const str_t* thumbprint)
{
str_t thumbprint_bin = str_t_null;
LOG_TRACE("get_cert_by_thumbprint enter");
if (hex_to_bin(thumbprint, &thumbprint_bin)) {
goto error;
}
CRYPT_HASH_BLOB hash = {
.pbData = (BYTE*)thumbprint_bin.data,
.cbData = thumbprint_bin.len
};
PCCERT_CONTEXT cert = cp_function_list.CertFindCertificateInStore(hStoreHandle,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
0,
CERT_FIND_HASH,
&hash,
NULL);
if (cert == NULL) {
LOG_ERROR("Could not find certificate in store");
goto error;
}
str_t_clear(&thumbprint_bin);
LOG_TRACE("get_cert_by_thumbprint exit");
return cert;
error:
str_t_clear(&thumbprint_bin);
LOG_ERROR("get_cert_by_thumbprint exit with error");
return NULL;
}
static PCCERT_CONTEXT
get_signer_cert(const cryptopro_context_t *ctx, HCERTSTORE hStoreHandle)
{
PCCERT_CONTEXT cert_ctx = get_cert_by_thumbprint(hStoreHandle, ctx->cert_thumbprint);
if (cert_ctx == NULL) {
goto error;
}
if (!str_t_is_null(*ctx->password)) {
if (set_password(cert_ctx, ctx->password)) {
goto error;
}
}
LOG_DEBUG("The signer's certificate was found");
return cert_ctx;
error:
if (cert_ctx) {
cp_function_list.CertFreeCertificateContext(cert_ctx);
}
LOG_ERROR("get_signer_cert exit with error");
return NULL;
}
static int
set_password(PCCERT_CONTEXT cert_ctx, const str_t *password)
{
DWORD dwSize;
if (!cp_function_list.CertGetCertificateContextProperty(
cert_ctx,
CERT_KEY_PROV_INFO_PROP_ID,
NULL,
&dwSize)) {
LOG_ERROR("Error getting key property");
LOG_ERROR("set_password exit with error. Last error code: 0x%08x", cp_function_list.GetLastError());
return -1;
}
unsigned char pKeyInfoBuffer[dwSize];
CRYPT_KEY_PROV_INFO* pKeyInfo = (CRYPT_KEY_PROV_INFO*) pKeyInfoBuffer;
if (!cp_function_list.CertGetCertificateContextProperty(
cert_ctx,
CERT_KEY_PROV_INFO_PROP_ID,
pKeyInfo,
&dwSize)){
LOG_ERROR("The second call to the function failed");
goto error;
}
DWORD keyType = (pKeyInfo->dwKeySpec == AT_SIGNATURE) ? PP_SIGNATURE_PIN
: PP_KEYEXCHANGE_PIN;
CRYPT_KEY_PROV_PARAM keyProvParam;
memset(&keyProvParam, 0, sizeof(CRYPT_KEY_PROV_PARAM));
keyProvParam.dwFlags = 0;
keyProvParam.dwParam = keyType;
keyProvParam.cbData = (DWORD)(password->len + 1);
keyProvParam.pbData = (BYTE*) password->data;
pKeyInfo->cProvParam = 1;
pKeyInfo->rgProvParam = &keyProvParam;
if (!cp_function_list.CertSetCertificateContextProperty(
cert_ctx,
CERT_KEY_PROV_INFO_PROP_ID,
0,
pKeyInfo)) {
LOG_ERROR("Error setting key property");
goto error;
}
return 0;
error:
LOG_ERROR("set_password exit with error. Last error code: 0x%08x", cp_function_list.GetLastError());
return -1;
}
static void
set_unknown_alg_error(/*out*/ char** verify_error, const str_t* alg)
{
LOG_TRACE("set_unknown_alg_error enter");
const str_t err_format = str_t_const("Unknown alg: '%.*s'");
size_t size = err_format.len + alg->len;
*verify_error = malloc(size);
if (*verify_error == NULL) {
LOG_ERROR("set_unknown_alg_error failed. Could not allocate memory for error string "
"(%zd bytes)", size);
goto exit;
}
int n = snprintf(*verify_error, size, err_format.data, (int) alg->len, alg->data);
if (n < 0 || (size_t)n >= size) {
LOG_ERROR("set_unknown_alg_error failed. Error occurred in concatenation err_string");
free(*verify_error);
*verify_error = NULL;
goto exit;
}
LOG_ERROR("%s", *verify_error);
exit:
LOG_TRACE("set_unknown_alg_error exit");
}
static int
alg_id_from_str(const str_t* alg, /*out*/ ALG_ID* alg_id, /*out*/ char** verify_error)
{
LOG_TRACE("alg_id_from_str enter");
if (strncmp(alg->data, "GOST3410_2012_256", alg->len) == 0) {
*alg_id = CALG_GR3411_2012_256;
} else if (strncmp(alg->data, "GOST3410_2012_512", alg->len) == 0) {
*alg_id = CALG_GR3411_2012_512;
} else {
set_unknown_alg_error(verify_error, alg);
LOG_ERROR("alg_id_from_str exit with error");
return -1;
}
LOG_TRACE("alg_id_from_str exit");
return 0;
}
static void
get_verify_error(char** verify_error)
{
LOG_TRACE("get_verify_error enter");
DWORD err = cp_function_list.GetLastError();
const char* err_string;
switch (err) {
case NTE_BAD_SIGNATURE:
err_string = "sign is invalid: bad signature (0x%08x)";
break;
case NTE_BAD_ALGID:
err_string = "sign is invalid: bad alg id (0x%08x)";
break;
default:
err_string = "sign is invalid. Last error code: 0x%08x";
break;
}
size_t size = strlen(err_string) + 4 /*error code*/ + 1 /*terminating null*/;
if (*verify_error != NULL) {
free(*verify_error); // в ответе отдаем последнюю ошибку
}
*verify_error = malloc(size);
if (*verify_error == NULL) {
LOG_ERROR("get_verify_error failed. Could not allocate memory for error string "
"(%zd bytes)", size);
return;
}
int n = snprintf(*verify_error, size, err_string, err);
if (n < 0 || (size_t)n >= size) {
LOG_ERROR("get_verify_error failed. Error occurred in concatenation err_string");
free(*verify_error);
*verify_error = NULL;
return;
}
LOG_DEBUG("verify error: %s", *verify_error);
LOG_TRACE("get_verify_error exit");
}
int
cryptopro_verify(cryptopro_context_t* ctx, const str_t* alg, const str_t* data,
const str_t* sign, bool* is_verified, char** verify_error, const char** error_code)
{
int rc = -1;
HCERTSTORE hStoreHandle = NULL;
PCCERT_CONTEXT certificate = NULL;
HCRYPTPROV hCryptProv = 0;
HCRYPTHASH hash = 0;
HCRYPTKEY hPubKey = 0;
str_t sign_reversed = str_t_null;
ALG_ID alg_id;
LOG_TRACE("cryptopro_verify enter");
timer_on_cryptopro_verify_enter(ctx->timer_ctx);
*is_verified = false;
if (alg_id_from_str(alg, &alg_id, verify_error)) {
goto exit;
}
if (reverse_sign(sign, &sign_reversed)) {
goto exit;
}
hStoreHandle = cert_open_store();
if (hStoreHandle == NULL) {
goto exit;
}
certificate = get_cert_by_thumbprint(hStoreHandle, ctx->cert_thumbprint);
if (certificate == NULL) {
goto exit;
}
if (!verify_cert_chain(certificate, ctx->timer_ctx, error_code)) {
goto exit;
}
LOG_DEBUG("provider: '%s', prov_type: %u", ctx->provider, ctx->prov_type);
if (!cp_function_list.CryptAcquireContext(&hCryptProv, NULL, ctx->provider, ctx->prov_type,
CRYPT_VERIFYCONTEXT)) {
LOG_ERROR("CryptAcquireContext() failed. provider: '%s', prov_type: %u",
ctx->provider, ctx->prov_type);
goto exit;
}
if (!cp_function_list.CryptImportPublicKeyInfo(hCryptProv, X509_ASN_ENCODING,
&certificate->pCertInfo->SubjectPublicKeyInfo,
&hPubKey)) {
LOG_ERROR("CryptImportPublicKeyInfo() failed");
goto exit;
}
if (!cp_function_list.CryptCreateHash(hCryptProv, alg_id, 0, 0, &hash)) {
LOG_ERROR("CryptCreateHash() failed");
goto exit;
}
if (!cp_function_list.CryptHashData(hash, (CONST BYTE *) data->data, data->len, 0)) {
LOG_ERROR("CryptHashData() failed");
goto exit;
}
if (cp_function_list.CryptVerifySignature(hash,
(CONST BYTE *) sign_reversed.data,
sign_reversed.len,
hPubKey,
NULL,
0)) {
LOG_DEBUG("sign is valid");
*is_verified = true;
} else {
get_verify_error(verify_error);
if (*verify_error == NULL) {
goto exit;
}
LOG_WARN("%s, cert_thumbprint: %.*s", *verify_error,
(int) ctx->cert_thumbprint->len, ctx->cert_thumbprint->data);
}
exit:
if (*is_verified || *verify_error) {
rc = 0;
}
str_t_clear(&sign_reversed);
if (hash) {
if (!cp_function_list.CryptDestroyHash(hash)) {
LOG_ERROR("CryptDestroyHash() failed");
rc = -1;
}
}
if (hPubKey) {
if (!cp_function_list.CryptDestroyKey(hPubKey)) {
LOG_ERROR("CryptDestroyKey() failed");
rc = -1;
}
}
if (hCryptProv) {
if (!cp_function_list.CryptReleaseContext(hCryptProv, 0)) {
LOG_ERROR("CryptReleaseContext() failed");
rc = -1;
}
}
if (certificate) {
cp_function_list.CertFreeCertificateContext(certificate);
}
cert_close_store(hStoreHandle);
if (rc == 0) {
LOG_TRACE("cryptopro_verify exit");
} else {
LOG_ERROR("cryptopro_verify exit with error. Last error code: 0x%08x",
cp_function_list.GetLastError());
}
timer_on_cryptopro_verify_exit(ctx->timer_ctx);
timer_log_verify(ctx->timer_ctx);
return rc;
}
static bool
get_subject_name(PCCERT_CONTEXT cert, /*out*/ char **subject_name)
{
LOG_TRACE("get_subject_name enter");
DWORD dwFlags = CERT_X500_NAME_STR | CERT_NAME_STR_NO_QUOTING_FLAG;
DWORD len = cp_function_list.CertNameToStr(
cert->dwCertEncodingType,
&cert->pCertInfo->Subject,
dwFlags,
NULL,
0
);
*subject_name = malloc(len);
if (*subject_name == NULL) {
LOG_ERROR("Could not allocate memory for Subject Name str (%d bytes)", len);
goto error;
}
cp_function_list.CertNameToStr(
cert->dwCertEncodingType,
&cert->pCertInfo->Subject,
dwFlags,
*subject_name,
len
);
LOG_DEBUG("Subject Name: %s", *subject_name);
LOG_TRACE("get_subject_name exit");
return true;
error:
free(*subject_name);
LOG_ERROR("get_subject_name exit with error");
return false;
}
static bool
get_issuer_name(PCCERT_CONTEXT cert, /*out*/ char **issuer_name)
{
LOG_TRACE("get_issuer_name enter");
DWORD dwFlags = CERT_X500_NAME_STR | CERT_NAME_STR_NO_QUOTING_FLAG;
DWORD len = cp_function_list.CertNameToStr(
cert->dwCertEncodingType,
&cert->pCertInfo->Issuer,
dwFlags,
NULL,
0
);
*issuer_name = malloc(len);
if (*issuer_name == NULL) {
LOG_ERROR("Could not allocate memory for Issuer Name str (%d bytes)", len);
goto error;
}
cp_function_list.CertNameToStr(
cert->dwCertEncodingType,
&cert->pCertInfo->Issuer,
dwFlags,
*issuer_name,
len
);
LOG_DEBUG("Issuer Name: %s", *issuer_name);
LOG_TRACE("get_issuer_name exit");
return true;
error:
free(*issuer_name);
LOG_ERROR("get_issuer_name exit with error");
return false;
}
static bool
get_signer_cert_info(PCCERT_CONTEXT signer_cert, cert_info_t *signer_cert_info)
{
LOG_TRACE("get_signer_cert_info enter");
if (!get_subject_name(signer_cert, &signer_cert_info->subject)) {
goto error;
}
if (!get_issuer_name(signer_cert, &signer_cert_info->issuer)) {
goto error;
}
LOG_TRACE("get_signer_cert_info exit");
return true;
error:
LOG_ERROR("get_signer_cert_info exit with error");
return false;
}
/*
* Проверка количества подписантов сообщения
* Предполагается, что количество подписантов должно быть равно 1
*/
static bool
check_message_signer_count(const str_t *sign)
{
int signer_count = cp_function_list.CryptGetMessageSignerCount(
PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
(const BYTE *) sign->data,
(DWORD) sign->len);
LOG_DEBUG("signer count = %d", signer_count);
if (signer_count == 1) {
LOG_DEBUG("signer count is ok");
return true;
}
if (signer_count < 0) {
LOG_ERROR("Could not get signer count. Last error code: 0x%08x",
cp_function_list.GetLastError());
} else {
LOG_ERROR("Invalid signer count: %d", signer_count);
}
return false;
}
/*
* Проверка подписи и извлечение сертификата подписанта
* Предполагается, что количество подписантов проверено и равно 1
*/
static bool
verify_detached_message_signature(const str_t *data, const str_t *sign,
timer_context_t *timer_ctx,
/*out*/ PCCERT_CONTEXT *signer_cert)
{
LOG_TRACE("verify_detached_message_signature enter");
timer_on_verify_detached_message_signature_enter(timer_ctx);
CRYPT_VERIFY_MESSAGE_PARA verifyParams = {
.cbSize = sizeof(CRYPT_VERIFY_MESSAGE_PARA),
.dwMsgAndCertEncodingType = PKCS_7_ASN_ENCODING | X509_ASN_ENCODING
};
BOOL r = cp_function_list.CryptVerifyDetachedMessageSignature(
&verifyParams,
0, // индекс проверяемой подписи
(CONST BYTE *) sign->data,
sign->len,
1, // количество подписанных сообщений
(const BYTE **) &(data->data),
(DWORD*) &(data->len),
signer_cert
);
timer_on_verify_detached_message_signature_exit(timer_ctx);
LOG_TRACE("verify_detached_message_signature exit");
return r;
}
int
cryptopro_verify_detached(cryptopro_context_t *ctx, const str_t *data, const str_t *sign,
bool *is_verified, /*out*/ cert_info_t *signer_cert_info,
/*out*/ char **verify_error, /*out*/ const char **error_code)
{
LOG_TRACE("cryptopro_verify_detached enter");
timer_on_cryptopro_verify_detached_enter(ctx->timer_ctx);
*is_verified = false;
PCCERT_CONTEXT signer_cert = NULL;
if (!check_message_signer_count(sign)) {
goto error;
}
if (verify_detached_message_signature(data, sign, ctx->timer_ctx, &signer_cert)) {
LOG_DEBUG("sign is valid");
if (!check_cert_key_usage(signer_cert)) {
goto error;
}
if (!verify_cert_chain(signer_cert, ctx->timer_ctx, error_code)) {
goto error;
}
if (!get_signer_cert_info(signer_cert, signer_cert_info)) {
goto error;
}
*is_verified = true;
} else {
LOG_DEBUG("sign is invalid");
get_verify_error(verify_error);
}
if (signer_cert != NULL) {
cp_function_list.CertFreeCertificateContext(signer_cert);
}
timer_on_cryptopro_verify_detached_exit(ctx->timer_ctx);
LOG_TRACE("cryptopro_verify_detached exit");
return 0;
error:
if (signer_cert != NULL) {
cp_function_list.CertFreeCertificateContext(signer_cert);
}
LOG_ERROR("cryptopro_verify_detached exit with error. Last error code: 0x%08x",
cp_function_list.GetLastError());
return -1;
}
int
cryptopro_gen_random(const cryptopro_context_t *ctx, unsigned char* data, size_t len)
{
HCRYPTPROV hCryptProv = 0;
LOG_TRACE("cryptopro_gen_random enter");
if (!cp_function_list.CryptAcquireContext(&hCryptProv, NULL, ctx->provider, ctx->prov_type,
CRYPT_VERIFYCONTEXT)) {
LOG_ERROR("CryptAcquireContext() failed. provider: '%s', prov_type: %u",
ctx->provider, ctx->prov_type);
goto error;
}
if (!cp_function_list.CryptGenRandom(hCryptProv, len, data)) {
LOG_ERROR("CryptGenRandom() failed");
goto error;
}
if (!cp_function_list.CryptReleaseContext(hCryptProv, 0)) {
LOG_ERROR("CryptReleaseContext() failed");
hCryptProv = 0;
goto error;
}
LOG_TRACE("cryptopro_gen_random exit");
return 0;
error:
if (hCryptProv) {
if (!cp_function_list.CryptReleaseContext(hCryptProv, 0)) {
LOG_ERROR("CryptReleaseContext() failed");
}
}
LOG_ERROR("cryptopro_gen_random exit with error. Last error code: 0x%08x",
cp_function_list.GetLastError());
return -1;
}