#include "cryptopro.h" #include "base64.h" #include "capi.h" #include "library.h" #include "logger.h" #define SHA1_SIZE 20 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 const char* CERT_TRUST_IS_PARTIAL_CHAIN_ERR_CODE = "CERT_TRUST_IS_PARTIAL_CHAIN"; 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); static bool get_subject_name(PCCERT_CONTEXT cert, /*out*/ char **subject_name); static bool get_issuer_name(PCCERT_CONTEXT cert, /*out*/ char **issuer_name); 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. Error code: 0x%08x", 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 print_crt_urls_from_certificate(PCCERT_CONTEXT cert) { LOG_TRACE("print_crt_urls_from_certificate enter"); PCERT_EXTENSION extension = cp_function_list.CertFindExtension( szOID_AUTHORITY_INFO_ACCESS, cert->pCertInfo->cExtension, cert->pCertInfo->rgExtension ); if (!extension) { LOG_WARN("Authority Info Access extension not found"); goto error; } DWORD info_size = 0; PCERT_AUTHORITY_INFO_ACCESS info_access = NULL; if (!cp_function_list.CryptDecodeObjectEx( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, X509_AUTHORITY_INFO_ACCESS, extension->Value.pbData, extension->Value.cbData, CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, NULL, &info_access, &info_size )) { LOG_ERROR("CryptDecodeObjectEx failed. Error code: 0x%08x", cp_function_list.GetLastError()); goto error; } if (info_access && info_access->cAccDescr > 0) { for (DWORD i = 0; i < info_access->cAccDescr; i++) { if (strcmp(info_access->rgAccDescr[i].pszAccessMethod, szOID_PKIX_CA_ISSUERS) == 0) { if (info_access->rgAccDescr[i].AccessLocation.dwAltNameChoice == CERT_ALT_NAME_URL) { LOG_INFO("CA Issuers URL: %ls", info_access->rgAccDescr[i].AccessLocation._empty_union_.pwszURL); } } } } if (info_access) { cp_function_list.LocalFree(info_access); } LOG_TRACE("print_crt_urls_from_certificate exit"); return; error: LOG_ERROR("print_crt_urls_from_certificate exit with error"); return; } static char* bin_to_hex(const BYTE *bytes, DWORD len) { char* hex = NULL; hex = malloc(len * 2 + 1); if (hex == NULL) { LOG_ERROR("bin_to_hex exit with error: could not allocate memory for hex (%d bytes)", len * 2 + 1); return NULL; } for (DWORD i = 0; i < len; i++) { sprintf(hex + i * 2, "%02x", bytes[i]); } return hex; } static bool get_thumbprint(PCCERT_CONTEXT cert, /*out*/ char** thumbprint) { LOG_TRACE("get_thumbprint enter"); BYTE sha1_hash[SHA1_SIZE]; DWORD sha1_hash_size = sizeof(sha1_hash); if (!cp_function_list.CertGetCertificateContextProperty( cert, CERT_SHA1_HASH_PROP_ID, sha1_hash, &sha1_hash_size)) { LOG_ERROR("CertGetCertificateContextProperty(CERT_SHA1_HASH_PROP_ID) failed. Error code: 0x%08x", cp_function_list.GetLastError()); goto error; } *thumbprint = bin_to_hex(sha1_hash, sha1_hash_size); if (*thumbprint == NULL) { goto error; } LOG_DEBUG("thumbprint: '%s'", *thumbprint); LOG_TRACE("get_thumbprint exit"); return true; error: LOG_ERROR("get_thumbprint exit with error"); return false; } static void print_cert_time_validity(PCCERT_CONTEXT cert) { SYSTEMTIME st; LOG_TRACE("print_cert_time_validity enter"); if (cp_function_list.FileTimeToSystemTime(&cert->pCertInfo->NotBefore, &st)) { LOG_INFO("Not valid before: %02d.%02d.%04d", st.wDay, st.wMonth, st.wYear); } if (cp_function_list.FileTimeToSystemTime(&cert->pCertInfo->NotAfter, &st)) { LOG_INFO("Not valid after: %02d.%02d.%04d", st.wDay, st.wMonth, st.wYear); } LOG_TRACE("print_cert_time_validity exit"); } static void print_common_cert_info(PCCERT_CONTEXT cert) { char* subject = NULL; char* issuer = NULL; char* thumbprint = NULL; LOG_TRACE("print_common_cert_info enter"); if (get_thumbprint(cert, &thumbprint)) { LOG_INFO("SHA1 Hash: %s", thumbprint); } if (get_subject_name(cert, &subject)) { LOG_INFO("Subject: %s", subject); } if (get_issuer_name(cert, &issuer)) { LOG_INFO("Issuer: %s", issuer); } free(subject); free(issuer); free(thumbprint); LOG_TRACE("print_common_cert_info exit"); } static void print_cert_info(PCCERT_CONTEXT cert) { LOG_INFO("=========== Certificate Info: ==========="); print_common_cert_info(cert); print_cert_time_validity(cert); print_crt_urls_from_certificate(cert); print_crl_urls_from_certificate(cert); LOG_INFO("========================================="); } 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; 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; case CERT_TRUST_IS_PARTIAL_CHAIN: *error_code = CERT_TRUST_IS_PARTIAL_CHAIN_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); } print_cert_info(cert); 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; }