From 2149edd4ed30164af2b3e747f5fdd46fcf0e3d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Milkovi=C4=8D?= Date: Sat, 13 Jan 2024 23:17:18 +0100 Subject: [PATCH 01/13] Added support for RFC5652 types of MS countersignatures (#16) It seems that some (maybe newer) MS countersignatures are not PKCS7 per RFC2315 but rather CMS structures defined by RFC5652. Unfortunately, PKCS7_* family of OpenSSL functions is not able to handle it, but there are CMS_* functions which are. They however do not provide same set of functions to do the same things as with PKCS7 structures. This PR adds possibility to fall back to CMS if PKCS7 fails. Some functions had to be simulated and therefore might not be *that* accurate but just from testing on a few files, it seems to validate them correctly. But it might need a tuning in the future if we test it on a more extensive set of samples. --- src/countersignature.c | 357 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 321 insertions(+), 36 deletions(-) diff --git a/src/countersignature.c b/src/countersignature.c index 9d806d5..0e1f383 100644 --- a/src/countersignature.c +++ b/src/countersignature.c @@ -21,6 +21,7 @@ SOFTWARE. #include "countersignature.h" +#include #include #include #include @@ -28,6 +29,7 @@ SOFTWARE. #include #include #include +#include #include #include #include @@ -36,6 +38,71 @@ SOFTWARE. #include "helper.h" #include "structs.h" +struct CountersignatureImplStruct; + +typedef TS_TST_INFO* get_ts_tst_info_func(struct CountersignatureImplStruct*); +typedef STACK_OF(X509)* get_signers_func(struct CountersignatureImplStruct*); +typedef STACK_OF(X509)* get_certs_func(struct CountersignatureImplStruct*); +typedef int verify_digest_func(struct CountersignatureImplStruct*, uint8_t* digest, size_t digest_size); +typedef BIO* verify_signature_init_func(struct CountersignatureImplStruct*); +typedef int verify_signature_finish_func(struct CountersignatureImplStruct*, BIO* bio, X509* signer); + +#define IMPL_FUNC_NAME(func, type) ms_countersig_impl_##func##_##type##_ + +#define DECLARE_FUNCS(type) \ + get_ts_tst_info_func IMPL_FUNC_NAME(get_ts_tst_info, type); \ + get_signers_func IMPL_FUNC_NAME(get_signers, type); \ + get_certs_func IMPL_FUNC_NAME(get_certs, type); \ + verify_digest_func IMPL_FUNC_NAME(verify_digest, type); \ + verify_signature_init_func IMPL_FUNC_NAME(verify_signature_init, type); \ + verify_signature_finish_func IMPL_FUNC_NAME(verify_signature_finish, type); + +DECLARE_FUNCS(pkcs7) +DECLARE_FUNCS(cms) + +typedef struct { + get_ts_tst_info_func* get_ts_tst_info; + get_signers_func* get_signers; + get_certs_func* get_certs; + verify_digest_func* verify_digest; + verify_signature_init_func* verify_signature_init; + verify_signature_finish_func* verify_signature_finish; +} CountersignatureImplFuncs; + +#define FUNC_ARRAY_NAME_FOR_IMPL(type) countersig_impl_funcs_##type##_ +#define FUNC_ARRAY_FOR_IMPL(type) \ + static const CountersignatureImplFuncs FUNC_ARRAY_NAME_FOR_IMPL(type) = { \ + &IMPL_FUNC_NAME(get_ts_tst_info, type), \ + &IMPL_FUNC_NAME(get_signers, type), \ + &IMPL_FUNC_NAME(get_certs, type), \ + &IMPL_FUNC_NAME(verify_digest, type), \ + &IMPL_FUNC_NAME(verify_signature_init, type), \ + &IMPL_FUNC_NAME(verify_signature_finish, type), \ + }; + +FUNC_ARRAY_FOR_IMPL(pkcs7) +FUNC_ARRAY_FOR_IMPL(cms) + +typedef enum { + CS_IMPL_PKCS7, + CS_IMPL_CMS, +} CountersignatureImplType; + +typedef struct CountersignatureImplStruct { + CountersignatureImplType type; + const CountersignatureImplFuncs* funcs; + union { + PKCS7* pkcs7; + CMS_ContentInfo* cms; + }; + // this is here to serve as a cache for CMS because the only way to obtain + // certs from CMS is to use CMS_get1_certs which leaves the deallocation + // to the caller but it just complicates things if you need to remember to + // deallocate also certs. This makes it easier if CountersignatureImpl itself + // is an owner of this thing. + STACK_OF(X509)* _certs; +} CountersignatureImpl; + Countersignature* pkcs9_countersig_new( const uint8_t* data, long size, STACK_OF(X509) * certs, ASN1_STRING* enc_digest) { @@ -178,22 +245,261 @@ Countersignature* pkcs9_countersig_new( return result; } +TS_TST_INFO* IMPL_FUNC_NAME(get_ts_tst_info, pkcs7)(CountersignatureImpl* impl) +{ + assert(impl->type == CS_IMPL_PKCS7); + + return PKCS7_to_TS_TST_INFO(impl->pkcs7); +} + +TS_TST_INFO* IMPL_FUNC_NAME(get_ts_tst_info, cms)(CountersignatureImpl* impl) +{ + assert(impl->type == CS_IMPL_CMS); + + const ASN1_OBJECT* content_type = CMS_get0_eContentType(impl->cms); + if (OBJ_obj2nid(content_type) != NID_id_smime_ct_TSTInfo) { + return NULL; + } + + ASN1_OCTET_STRING** content = CMS_get0_content(impl->cms); + if (!content || !*content) { + return NULL; + } + + const uint8_t* data = (*content)->data; + TS_TST_INFO* ts_tst_info = d2i_TS_TST_INFO(NULL, &data, (*content)->length); + if (!ts_tst_info) { + return NULL; + } + + return ts_tst_info; +} + +STACK_OF(X509)* IMPL_FUNC_NAME(get_signers, pkcs7)(CountersignatureImpl* impl) +{ + assert(impl->type == CS_IMPL_PKCS7); + + return PKCS7_get0_signers(impl->pkcs7, impl->pkcs7->d.sign->cert, 0); +} + +STACK_OF(X509)* IMPL_FUNC_NAME(get_signers, cms)(CountersignatureImpl* impl) +{ + assert(impl->type == CS_IMPL_CMS); + + STACK_OF(CMS_SignerInfo)* signer_infos = CMS_get0_SignerInfos(impl->cms); + if (!signer_infos) { + return NULL; + } + + // Use our func points to cache the certs and don't create another copy + STACK_OF(X509)* certs = impl->funcs->get_certs(impl); + + int si_count = sk_CMS_SignerInfo_num(signer_infos); + int cert_count = certs ? sk_X509_num(certs) : 0; + STACK_OF(X509)* result = sk_X509_new_null(); + + // PKCS7_get0_signers() lets us specify the certificate array and looks up signer certificate there + // With CMS_ContentInfo, we don't have direct access to signer certificate, just all the certificates + // The only thing we can do is to go through all signer infos and find those which match some certificate + // in all certificates. It essentially simulates what PKCS7_get0_signers() does. + for (int i = 0; i < si_count; ++i) { + CMS_SignerInfo* si = sk_CMS_SignerInfo_value(signer_infos, i); + if (!si) { + continue; + } + + if (certs) { + for (int j = 0; j < cert_count; ++j) { + X509* cert = sk_X509_value(certs, j); + if (!cert) { + continue; + } + + if (CMS_SignerInfo_cert_cmp(si, cert) == 0) { + if (!sk_X509_push(result, cert)) { + return NULL; + } + } + } + } + } + + return result; +} + +STACK_OF(X509)* IMPL_FUNC_NAME(get_certs, pkcs7)(CountersignatureImpl* impl) +{ + assert(impl->type == CS_IMPL_PKCS7); + + return impl->pkcs7->d.sign->cert; +} + +STACK_OF(X509)* IMPL_FUNC_NAME(get_certs, cms)(CountersignatureImpl* impl) +{ + assert(impl->type == CS_IMPL_CMS); + + if (impl->_certs) { + return impl->_certs; + } + + impl->_certs = CMS_get1_certs(impl->cms); + return impl->_certs; +} + +int IMPL_FUNC_NAME(verify_digest, pkcs7)(CountersignatureImpl* impl, uint8_t* digest, size_t digest_size) +{ + assert(impl->type == CS_IMPL_PKCS7); + + X509_STORE* store = X509_STORE_new(); + TS_VERIFY_CTX* ctx = TS_VERIFY_CTX_new(); + TS_VERIFY_CTX_init(ctx); + + TS_VERIFY_CTX_set_flags(ctx, TS_VFY_VERSION | TS_VFY_IMPRINT); + TS_VERIFY_CTX_set_store(ctx, store); +#if OPENSSL_VERSION_NUMBER >= 0x3000000fL + TS_VERIFY_CTX_set_certs(ctx, impl->funcs->get_certs(impl)); +#else + TS_VERIFY_CTS_set_certs(ctx, impl->funcs->get_certs(impl)); +#endif + TS_VERIFY_CTX_set_imprint(ctx, digest, digest_size); + + int result = TS_RESP_verify_token(ctx, impl->pkcs7); + + X509_STORE_free(store); + OPENSSL_free(ctx); + + return result; +} + +int IMPL_FUNC_NAME(verify_digest, cms)(CountersignatureImpl* impl, uint8_t* digest, size_t digest_size) +{ + assert(impl->type == CS_IMPL_CMS); + + // This is essentially just reimplementation of TS_RESP_verify_token() from OpenSSL + TS_TST_INFO* ts_tst_info = impl->funcs->get_ts_tst_info(impl); + if (!ts_tst_info || TS_TST_INFO_get_version(ts_tst_info) != 1) { + if (ts_tst_info) + TS_TST_INFO_free(ts_tst_info); + return 0; + } + + TS_MSG_IMPRINT* ts_imprint = TS_TST_INFO_get_msg_imprint(ts_tst_info); + if (!ts_imprint) { + TS_TST_INFO_free(ts_tst_info); + return 0; + } + + ASN1_OCTET_STRING* ts_imprint_digest = TS_MSG_IMPRINT_get_msg(ts_imprint); + if (!ts_imprint_digest) { + TS_TST_INFO_free(ts_tst_info); + return 0; + } + + if (ts_imprint_digest->length != (int)digest_size + || memcmp(ts_imprint_digest->data, digest, digest_size) != 0) { + TS_TST_INFO_free(ts_tst_info); + return 0; + } + + TS_TST_INFO_free(ts_tst_info); + return 1; +} + +BIO* IMPL_FUNC_NAME(verify_signature_init, pkcs7)(CountersignatureImpl* impl) +{ + assert(impl->type == CS_IMPL_PKCS7); + + return PKCS7_dataInit(impl->pkcs7, NULL); +} + +BIO* IMPL_FUNC_NAME(verify_signature_init, cms)(CountersignatureImpl* impl) +{ + assert(impl->type == CS_IMPL_CMS); + + return CMS_dataInit(impl->cms, NULL); +} + +int IMPL_FUNC_NAME(verify_signature_finish, pkcs7)(CountersignatureImpl* impl, BIO* bio, X509* signer) +{ + assert(impl->type == CS_IMPL_PKCS7); + + /* Verify signature with PKCS7_signatureVerify + because TS_RESP_verify_token would try to verify + chain and without trust anchors it always fails */ + PKCS7_SIGNER_INFO* si = sk_PKCS7_SIGNER_INFO_value(PKCS7_get_signer_info(impl->pkcs7), 0); + return PKCS7_signatureVerify(bio, impl->pkcs7, si, signer); +} + +int IMPL_FUNC_NAME(verify_signature_finish, cms)(CountersignatureImpl* impl, BIO* bio, X509* signer) +{ + assert(impl->type == CS_IMPL_CMS); + + (void)signer; + CMS_SignerInfo* si = sk_CMS_SignerInfo_value(CMS_get0_SignerInfos(impl->cms), 0); + return CMS_SignerInfo_verify_content(si, bio); +} + +CountersignatureImpl* ms_countersig_impl_new(const uint8_t* data, long size) +{ + const uint8_t* d = data; + PKCS7* p7 = d2i_PKCS7(NULL, &d, size); + if (p7) { + printf("USES PKCS7\n"); + CountersignatureImpl* result = (CountersignatureImpl*)calloc(1, sizeof(CountersignatureImpl)); + result->type = CS_IMPL_PKCS7; + result->funcs = &FUNC_ARRAY_NAME_FOR_IMPL(pkcs7); + result->pkcs7 = p7; + return result; + } + + d = data; + CMS_ContentInfo* cms = d2i_CMS_ContentInfo(NULL, &d, size); + if (cms) { + printf("USES CMS\n"); + CountersignatureImpl* result = (CountersignatureImpl*)calloc(1, sizeof(CountersignatureImpl)); + result->type = CS_IMPL_CMS; + result->funcs = &FUNC_ARRAY_NAME_FOR_IMPL(cms); + result->cms = cms; + return result; + } + + return NULL; +} + +void ms_countersig_impl_free(CountersignatureImpl* impl) +{ + switch (impl->type) + { + case CS_IMPL_PKCS7: + PKCS7_free(impl->pkcs7); + break; + case CS_IMPL_CMS: + if (impl->_certs) { + sk_X509_pop_free(impl->_certs, X509_free); + } + CMS_ContentInfo_free(impl->cms); + break; + } + + free(impl); +} + Countersignature* ms_countersig_new(const uint8_t* data, long size, ASN1_STRING* enc_digest) { Countersignature* result = (Countersignature*)calloc(1, sizeof(*result)); if (!result) return NULL; - PKCS7* p7 = d2i_PKCS7(NULL, &data, size); - if (!p7) { + CountersignatureImpl* impl = ms_countersig_impl_new(data, size); + if (!impl) { result->verify_flags = COUNTERSIGNATURE_VFY_CANT_PARSE; return result; } - TS_TST_INFO* ts = PKCS7_to_TS_TST_INFO(p7); + TS_TST_INFO* ts = impl->funcs->get_ts_tst_info(impl); if (!ts) { result->verify_flags = COUNTERSIGNATURE_VFY_CANT_PARSE; - PKCS7_free(p7); + ms_countersig_impl_free(impl); return result; } @@ -201,20 +507,21 @@ Countersignature* ms_countersig_new(const uint8_t* data, long size, ASN1_STRING* if (!rawTime) { result->verify_flags = COUNTERSIGNATURE_VFY_TIME_MISSING; TS_TST_INFO_free(ts); - PKCS7_free(p7); + ms_countersig_impl_free(impl); return result; } result->sign_time = ASN1_TIME_to_int64_t(rawTime); - STACK_OF(X509)* sigs = PKCS7_get0_signers(p7, p7->d.sign->cert, 0); + STACK_OF(X509)* sigs = impl->funcs->get_signers(impl); X509* signCert = sk_X509_value(sigs, 0); if (!signCert) { result->verify_flags = COUNTERSIGNATURE_VFY_NO_SIGNER_CERT; goto end; } - result->chain = parse_signer_chain(signCert, p7->d.sign->cert); + STACK_OF(X509)* certs = impl->funcs->get_certs(impl); + result->chain = parse_signer_chain(signCert, certs); /* Imprint == digest */ TS_MSG_IMPRINT* imprint = TS_TST_INFO_get_msg_imprint(ts); @@ -259,52 +566,30 @@ Countersignature* ms_countersig_new(const uint8_t* data, long size, ASN1_STRING* goto end; } - TS_VERIFY_CTX* ctx = TS_VERIFY_CTX_new(); - X509_STORE* store = X509_STORE_new(); - TS_VERIFY_CTX_init(ctx); - - TS_VERIFY_CTX_set_flags(ctx, TS_VFY_VERSION | TS_VFY_IMPRINT); - TS_VERIFY_CTX_set_store(ctx, store); -#if OPENSSL_VERSION_NUMBER >= 0x3000000fL - TS_VERIFY_CTX_set_certs(ctx, p7->d.sign->cert); -#else - TS_VERIFY_CTS_set_certs(ctx, p7->d.sign->cert); -#endif - TS_VERIFY_CTX_set_imprint(ctx, calc_digest, mdLen); - - bool isValid = TS_RESP_verify_token(ctx, p7) == 1; - - X509_STORE_free(store); - OPENSSL_free(ctx); - + bool isValid = impl->funcs->verify_digest(impl, calc_digest, mdLen) == 1; if (!isValid) { result->verify_flags = COUNTERSIGNATURE_VFY_INVALID; goto end; } - /* Verify signature with PKCS7_signatureVerify - because TS_RESP_verify_token would try to verify - chain and without trust anchors it always fails */ - BIO* p7bio = PKCS7_dataInit(p7, NULL); + BIO* bio = impl->funcs->verify_signature_init(impl); char buf[4096]; - /* We now have to 'read' from p7bio to calculate digests etc. */ - while (BIO_read(p7bio, buf, sizeof(buf)) > 0) + /* We now have to 'read' from bio to calculate digests etc. */ + while (BIO_read(bio, buf, sizeof(buf)) > 0) continue; - PKCS7_SIGNER_INFO* si = sk_PKCS7_SIGNER_INFO_value(PKCS7_get_signer_info(p7), 0); - - isValid = PKCS7_signatureVerify(p7bio, p7, si, signCert) == 1; + isValid = impl->funcs->verify_signature_finish(impl, bio, signCert) == 1; - BIO_free_all(p7bio); + BIO_free_all(bio); if (!isValid) result->verify_flags = COUNTERSIGNATURE_VFY_INVALID; end: sk_X509_free(sigs); - PKCS7_free(p7); TS_TST_INFO_free(ts); + ms_countersig_impl_free(impl); return result; } From a4408b7223af554f1ac161a5f176a20124f12db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Milkovi=C4=8D?= Date: Mon, 22 Jan 2024 19:27:04 +0100 Subject: [PATCH 02/13] Removed debug prints --- src/countersignature.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/countersignature.c b/src/countersignature.c index 0e1f383..da53532 100644 --- a/src/countersignature.c +++ b/src/countersignature.c @@ -444,7 +444,6 @@ CountersignatureImpl* ms_countersig_impl_new(const uint8_t* data, long size) const uint8_t* d = data; PKCS7* p7 = d2i_PKCS7(NULL, &d, size); if (p7) { - printf("USES PKCS7\n"); CountersignatureImpl* result = (CountersignatureImpl*)calloc(1, sizeof(CountersignatureImpl)); result->type = CS_IMPL_PKCS7; result->funcs = &FUNC_ARRAY_NAME_FOR_IMPL(pkcs7); @@ -455,7 +454,6 @@ CountersignatureImpl* ms_countersig_impl_new(const uint8_t* data, long size) d = data; CMS_ContentInfo* cms = d2i_CMS_ContentInfo(NULL, &d, size); if (cms) { - printf("USES CMS\n"); CountersignatureImpl* result = (CountersignatureImpl*)calloc(1, sizeof(CountersignatureImpl)); result->type = CS_IMPL_CMS; result->funcs = &FUNC_ARRAY_NAME_FOR_IMPL(cms); From b6b9bf3cc2d89525b7643f87eafe4923a0631d19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Milkovi=C4=8D?= Date: Mon, 22 Jan 2024 21:02:14 +0100 Subject: [PATCH 03/13] CMake adjustments in hopes to fix Windows build --- CMakeLists.txt | 4 ++-- tests/CMakeLists.txt | 11 +++-------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4176760..1709400 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ if(IS_BIG_ENDIAN) endif() if(MSVC) - target_compile_options(authenticode PRIVATE /W4 -fpie) + target_compile_options(authenticode PRIVATE /W4) else() target_compile_options(authenticode PRIVATE -Wall -Wextra -Wpedantic -fpie) endif() @@ -37,7 +37,7 @@ target_include_directories(authenticode ) target_link_libraries(authenticode - PRIVATE + PUBLIC OpenSSL::Crypto ) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 30d85d9..29c7a9c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,8 +1,4 @@ -project(test) - -set(CMAKE_CXX_STANDARD 11) - -find_package(OpenSSL 1.1.1 REQUIRED) +project(tests LANGUAGES C CXX) include(FetchContent) FetchContent_Declare( @@ -25,8 +21,7 @@ add_executable(tests target_link_libraries(tests PRIVATE gtest_main - authenticode - OpenSSL::Crypto) + authenticode) include(GoogleTest) -gtest_add_tests(TARGET tests) \ No newline at end of file +gtest_add_tests(TARGET tests) From f644da401e8cd9f0124065df9c454436abeb4a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Milkovi=C4=8D?= Date: Mon, 22 Jan 2024 22:30:21 +0100 Subject: [PATCH 04/13] Debugging GitHub Workflow issue --- .github/workflows/cmake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 2a471d4..e65a044 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -41,7 +41,7 @@ jobs: run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_TESTS=ON -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -- /verbosity:detailed - name: Test working-directory: ${{github.workspace}}/build From f63b8d0a59be858403f6bc76e3e48379c1eaabc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20H=C3=A1jek?= Date: Tue, 23 Jan 2024 15:44:01 +0100 Subject: [PATCH 05/13] Revert "CMake adjustments in hopes to fix Windows build" This reverts commit b6b9bf3cc2d89525b7643f87eafe4923a0631d19. --- CMakeLists.txt | 4 ++-- tests/CMakeLists.txt | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1709400..4176760 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ if(IS_BIG_ENDIAN) endif() if(MSVC) - target_compile_options(authenticode PRIVATE /W4) + target_compile_options(authenticode PRIVATE /W4 -fpie) else() target_compile_options(authenticode PRIVATE -Wall -Wextra -Wpedantic -fpie) endif() @@ -37,7 +37,7 @@ target_include_directories(authenticode ) target_link_libraries(authenticode - PUBLIC + PRIVATE OpenSSL::Crypto ) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 29c7a9c..30d85d9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,4 +1,8 @@ -project(tests LANGUAGES C CXX) +project(test) + +set(CMAKE_CXX_STANDARD 11) + +find_package(OpenSSL 1.1.1 REQUIRED) include(FetchContent) FetchContent_Declare( @@ -21,7 +25,8 @@ add_executable(tests target_link_libraries(tests PRIVATE gtest_main - authenticode) + authenticode + OpenSSL::Crypto) include(GoogleTest) -gtest_add_tests(TARGET tests) +gtest_add_tests(TARGET tests) \ No newline at end of file From 9f7b27711d2dbd983d5bc5019347758e68568c5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20H=C3=A1jek?= Date: Tue, 23 Jan 2024 15:44:22 +0100 Subject: [PATCH 06/13] Revert "Debugging GitHub Workflow issue" This reverts commit f644da401e8cd9f0124065df9c454436abeb4a0e. --- .github/workflows/cmake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index e65a044..2a471d4 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -41,7 +41,7 @@ jobs: run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_TESTS=ON -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -- /verbosity:detailed + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - name: Test working-directory: ${{github.workspace}}/build From b3ff48c92f670fa5326ce7652f33b9c278d27749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20H=C3=A1jek?= Date: Tue, 23 Jan 2024 15:47:08 +0100 Subject: [PATCH 07/13] Include opensslv.h for OPENSSL_VERSION_NUMBER --- src/authenticode.c | 1 + src/certificate.c | 1 + src/countersignature.c | 1 + 3 files changed, 3 insertions(+) diff --git a/src/authenticode.c b/src/authenticode.c index 385ee9c..08fe9ca 100644 --- a/src/authenticode.c +++ b/src/authenticode.c @@ -28,6 +28,7 @@ SOFTWARE. #include #include #include +#include #include #include #include diff --git a/src/certificate.c b/src/certificate.c index ce89a48..0bac7f9 100644 --- a/src/certificate.c +++ b/src/certificate.c @@ -25,6 +25,7 @@ SOFTWARE. #include #include #include +#include #include #include diff --git a/src/countersignature.c b/src/countersignature.c index da53532..8fa4972 100644 --- a/src/countersignature.c +++ b/src/countersignature.c @@ -29,6 +29,7 @@ SOFTWARE. #include #include #include +#include #include #include #include From adbe221a6c3cdde96bb5ad07e393dbd7163ee19b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20H=C3=A1jek?= Date: Tue, 23 Jan 2024 15:47:46 +0100 Subject: [PATCH 08/13] Format changes using clang-formatter --- src/authenticode.c | 8 ++-- src/certificate.c | 2 +- src/countersignature.c | 103 ++++++++++++++++++++++------------------- 3 files changed, 61 insertions(+), 52 deletions(-) diff --git a/src/authenticode.c b/src/authenticode.c index 08fe9ca..0b5f724 100644 --- a/src/authenticode.c +++ b/src/authenticode.c @@ -22,13 +22,13 @@ SOFTWARE. #include #include #include +#include #include #include #include #include #include #include -#include #include #include #include @@ -275,7 +275,8 @@ static bool authenticode_verify(PKCS7* p7, PKCS7_SIGNER_INFO* si, X509* signCert } /* Creates all the Authenticode objects so we can parse them with OpenSSL, is not thread-safe, needs - * to be called once before any multi-threading environmentt - https://github.com/openssl/openssl/issues/13524 */ + * to be called once before any multi-threading environmentt - + * https://github.com/openssl/openssl/issues/13524 */ void initialize_authenticode_parser() { OBJ_create("1.3.6.1.4.1.311.2.1.12", "spcSpOpusInfo", "SPC_SP_OPUS_INFO_OBJID"); @@ -568,7 +569,8 @@ AuthenticodeArray* parse_authenticode(const uint8_t* pe_data, uint64_t pe_len) uint32_t dwLength = letoh32(*(uint32_t*)(pe_data + cert_addr)); if (pe_len < cert_addr + dwLength) return NULL; - /* dwLength = offsetof(WIN_CERTIFICATE, bCertificate) + (size of the variable-length binary array contained within bCertificate) */ + /* dwLength = offsetof(WIN_CERTIFICATE, bCertificate) + (size of the variable-length binary + * array contained within bCertificate) */ AuthenticodeArray* auth_array = authenticode_new(pe_data + cert_addr + 0x8, dwLength - 0x8); if (!auth_array) return NULL; diff --git a/src/certificate.c b/src/certificate.c index 0bac7f9..cf688e7 100644 --- a/src/certificate.c +++ b/src/certificate.c @@ -24,8 +24,8 @@ SOFTWARE. #include #include #include -#include #include +#include #include #include diff --git a/src/countersignature.c b/src/countersignature.c index 8fa4972..88b7856 100644 --- a/src/countersignature.c +++ b/src/countersignature.c @@ -21,16 +21,16 @@ SOFTWARE. #include "countersignature.h" +#include #include #include #include +#include #include #include #include #include #include -#include -#include #include #include #include @@ -42,20 +42,22 @@ SOFTWARE. struct CountersignatureImplStruct; typedef TS_TST_INFO* get_ts_tst_info_func(struct CountersignatureImplStruct*); -typedef STACK_OF(X509)* get_signers_func(struct CountersignatureImplStruct*); -typedef STACK_OF(X509)* get_certs_func(struct CountersignatureImplStruct*); -typedef int verify_digest_func(struct CountersignatureImplStruct*, uint8_t* digest, size_t digest_size); +typedef STACK_OF(X509) * get_signers_func(struct CountersignatureImplStruct*); +typedef STACK_OF(X509) * get_certs_func(struct CountersignatureImplStruct*); +typedef int +verify_digest_func(struct CountersignatureImplStruct*, uint8_t* digest, size_t digest_size); typedef BIO* verify_signature_init_func(struct CountersignatureImplStruct*); -typedef int verify_signature_finish_func(struct CountersignatureImplStruct*, BIO* bio, X509* signer); +typedef int +verify_signature_finish_func(struct CountersignatureImplStruct*, BIO* bio, X509* signer); #define IMPL_FUNC_NAME(func, type) ms_countersig_impl_##func##_##type##_ -#define DECLARE_FUNCS(type) \ - get_ts_tst_info_func IMPL_FUNC_NAME(get_ts_tst_info, type); \ - get_signers_func IMPL_FUNC_NAME(get_signers, type); \ - get_certs_func IMPL_FUNC_NAME(get_certs, type); \ - verify_digest_func IMPL_FUNC_NAME(verify_digest, type); \ - verify_signature_init_func IMPL_FUNC_NAME(verify_signature_init, type); \ +#define DECLARE_FUNCS(type) \ + get_ts_tst_info_func IMPL_FUNC_NAME(get_ts_tst_info, type); \ + get_signers_func IMPL_FUNC_NAME(get_signers, type); \ + get_certs_func IMPL_FUNC_NAME(get_certs, type); \ + verify_digest_func IMPL_FUNC_NAME(verify_digest, type); \ + verify_signature_init_func IMPL_FUNC_NAME(verify_signature_init, type); \ verify_signature_finish_func IMPL_FUNC_NAME(verify_signature_finish, type); DECLARE_FUNCS(pkcs7) @@ -71,14 +73,14 @@ typedef struct { } CountersignatureImplFuncs; #define FUNC_ARRAY_NAME_FOR_IMPL(type) countersig_impl_funcs_##type##_ -#define FUNC_ARRAY_FOR_IMPL(type) \ - static const CountersignatureImplFuncs FUNC_ARRAY_NAME_FOR_IMPL(type) = { \ - &IMPL_FUNC_NAME(get_ts_tst_info, type), \ - &IMPL_FUNC_NAME(get_signers, type), \ - &IMPL_FUNC_NAME(get_certs, type), \ - &IMPL_FUNC_NAME(verify_digest, type), \ - &IMPL_FUNC_NAME(verify_signature_init, type), \ - &IMPL_FUNC_NAME(verify_signature_finish, type), \ +#define FUNC_ARRAY_FOR_IMPL(type) \ + static const CountersignatureImplFuncs FUNC_ARRAY_NAME_FOR_IMPL(type) = { \ + &IMPL_FUNC_NAME(get_ts_tst_info, type), \ + &IMPL_FUNC_NAME(get_signers, type), \ + &IMPL_FUNC_NAME(get_certs, type), \ + &IMPL_FUNC_NAME(verify_digest, type), \ + &IMPL_FUNC_NAME(verify_signature_init, type), \ + &IMPL_FUNC_NAME(verify_signature_finish, type), \ }; FUNC_ARRAY_FOR_IMPL(pkcs7) @@ -101,7 +103,7 @@ typedef struct CountersignatureImplStruct { // to the caller but it just complicates things if you need to remember to // deallocate also certs. This makes it easier if CountersignatureImpl itself // is an owner of this thing. - STACK_OF(X509)* _certs; + STACK_OF(X509) * _certs; } CountersignatureImpl; Countersignature* pkcs9_countersig_new( @@ -201,7 +203,7 @@ Countersignature* pkcs9_countersig_new( /* compare the encrypted digest and calculated digest */ bool isValid = false; - + #if OPENSSL_VERSION_NUMBER >= 0x3000000fL size_t mdLen = EVP_MD_get_size(md); #else @@ -276,14 +278,14 @@ TS_TST_INFO* IMPL_FUNC_NAME(get_ts_tst_info, cms)(CountersignatureImpl* impl) return ts_tst_info; } -STACK_OF(X509)* IMPL_FUNC_NAME(get_signers, pkcs7)(CountersignatureImpl* impl) +STACK_OF(X509) * IMPL_FUNC_NAME(get_signers, pkcs7)(CountersignatureImpl* impl) { assert(impl->type == CS_IMPL_PKCS7); return PKCS7_get0_signers(impl->pkcs7, impl->pkcs7->d.sign->cert, 0); } -STACK_OF(X509)* IMPL_FUNC_NAME(get_signers, cms)(CountersignatureImpl* impl) +STACK_OF(X509) * IMPL_FUNC_NAME(get_signers, cms)(CountersignatureImpl* impl) { assert(impl->type == CS_IMPL_CMS); @@ -299,10 +301,11 @@ STACK_OF(X509)* IMPL_FUNC_NAME(get_signers, cms)(CountersignatureImpl* impl) int cert_count = certs ? sk_X509_num(certs) : 0; STACK_OF(X509)* result = sk_X509_new_null(); - // PKCS7_get0_signers() lets us specify the certificate array and looks up signer certificate there - // With CMS_ContentInfo, we don't have direct access to signer certificate, just all the certificates - // The only thing we can do is to go through all signer infos and find those which match some certificate - // in all certificates. It essentially simulates what PKCS7_get0_signers() does. + // PKCS7_get0_signers() lets us specify the certificate array and looks up signer certificate + // there With CMS_ContentInfo, we don't have direct access to signer certificate, just all the + // certificates The only thing we can do is to go through all signer infos and find those which + // match some certificate in all certificates. It essentially simulates what + // PKCS7_get0_signers() does. for (int i = 0; i < si_count; ++i) { CMS_SignerInfo* si = sk_CMS_SignerInfo_value(signer_infos, i); if (!si) { @@ -328,14 +331,14 @@ STACK_OF(X509)* IMPL_FUNC_NAME(get_signers, cms)(CountersignatureImpl* impl) return result; } -STACK_OF(X509)* IMPL_FUNC_NAME(get_certs, pkcs7)(CountersignatureImpl* impl) +STACK_OF(X509) * IMPL_FUNC_NAME(get_certs, pkcs7)(CountersignatureImpl* impl) { assert(impl->type == CS_IMPL_PKCS7); return impl->pkcs7->d.sign->cert; } -STACK_OF(X509)* IMPL_FUNC_NAME(get_certs, cms)(CountersignatureImpl* impl) +STACK_OF(X509) * IMPL_FUNC_NAME(get_certs, cms)(CountersignatureImpl* impl) { assert(impl->type == CS_IMPL_CMS); @@ -347,7 +350,8 @@ STACK_OF(X509)* IMPL_FUNC_NAME(get_certs, cms)(CountersignatureImpl* impl) return impl->_certs; } -int IMPL_FUNC_NAME(verify_digest, pkcs7)(CountersignatureImpl* impl, uint8_t* digest, size_t digest_size) +int IMPL_FUNC_NAME(verify_digest, pkcs7)( + CountersignatureImpl* impl, uint8_t* digest, size_t digest_size) { assert(impl->type == CS_IMPL_PKCS7); @@ -372,7 +376,8 @@ int IMPL_FUNC_NAME(verify_digest, pkcs7)(CountersignatureImpl* impl, uint8_t* di return result; } -int IMPL_FUNC_NAME(verify_digest, cms)(CountersignatureImpl* impl, uint8_t* digest, size_t digest_size) +int IMPL_FUNC_NAME(verify_digest, cms)( + CountersignatureImpl* impl, uint8_t* digest, size_t digest_size) { assert(impl->type == CS_IMPL_CMS); @@ -396,8 +401,8 @@ int IMPL_FUNC_NAME(verify_digest, cms)(CountersignatureImpl* impl, uint8_t* dige return 0; } - if (ts_imprint_digest->length != (int)digest_size - || memcmp(ts_imprint_digest->data, digest, digest_size) != 0) { + if (ts_imprint_digest->length != (int)digest_size || + memcmp(ts_imprint_digest->data, digest, digest_size) != 0) { TS_TST_INFO_free(ts_tst_info); return 0; } @@ -420,7 +425,8 @@ BIO* IMPL_FUNC_NAME(verify_signature_init, cms)(CountersignatureImpl* impl) return CMS_dataInit(impl->cms, NULL); } -int IMPL_FUNC_NAME(verify_signature_finish, pkcs7)(CountersignatureImpl* impl, BIO* bio, X509* signer) +int IMPL_FUNC_NAME(verify_signature_finish, pkcs7)( + CountersignatureImpl* impl, BIO* bio, X509* signer) { assert(impl->type == CS_IMPL_PKCS7); @@ -445,7 +451,8 @@ CountersignatureImpl* ms_countersig_impl_new(const uint8_t* data, long size) const uint8_t* d = data; PKCS7* p7 = d2i_PKCS7(NULL, &d, size); if (p7) { - CountersignatureImpl* result = (CountersignatureImpl*)calloc(1, sizeof(CountersignatureImpl)); + CountersignatureImpl* result = + (CountersignatureImpl*)calloc(1, sizeof(CountersignatureImpl)); result->type = CS_IMPL_PKCS7; result->funcs = &FUNC_ARRAY_NAME_FOR_IMPL(pkcs7); result->pkcs7 = p7; @@ -455,7 +462,8 @@ CountersignatureImpl* ms_countersig_impl_new(const uint8_t* data, long size) d = data; CMS_ContentInfo* cms = d2i_CMS_ContentInfo(NULL, &d, size); if (cms) { - CountersignatureImpl* result = (CountersignatureImpl*)calloc(1, sizeof(CountersignatureImpl)); + CountersignatureImpl* result = + (CountersignatureImpl*)calloc(1, sizeof(CountersignatureImpl)); result->type = CS_IMPL_CMS; result->funcs = &FUNC_ARRAY_NAME_FOR_IMPL(cms); result->cms = cms; @@ -467,17 +475,16 @@ CountersignatureImpl* ms_countersig_impl_new(const uint8_t* data, long size) void ms_countersig_impl_free(CountersignatureImpl* impl) { - switch (impl->type) - { - case CS_IMPL_PKCS7: - PKCS7_free(impl->pkcs7); - break; - case CS_IMPL_CMS: - if (impl->_certs) { - sk_X509_pop_free(impl->_certs, X509_free); - } - CMS_ContentInfo_free(impl->cms); - break; + switch (impl->type) { + case CS_IMPL_PKCS7: + PKCS7_free(impl->pkcs7); + break; + case CS_IMPL_CMS: + if (impl->_certs) { + sk_X509_pop_free(impl->_certs, X509_free); + } + CMS_ContentInfo_free(impl->cms); + break; } free(impl); From 2aac779a5f0311b633a37da0a4e3b3f550306b17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20H=C3=A1jek?= Date: Thu, 25 Jan 2024 19:33:54 +0100 Subject: [PATCH 09/13] Remove and move openssl to correct folder on Windows build --- .github/workflows/cmake.yml | 13 ++++++++++--- CMakeLists.txt | 5 +++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 2a471d4..8deec35 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -30,10 +30,17 @@ jobs: - name: Install OpenSSL on Windows if: matrix.os == 'windows-latest' - run: choco install openssl + run: | + rd -r "C:/Program Files/OpenSSL" + choco install openssl + Copy-Item -Path "C:/Program Files/OpenSSL/lib/VC/x64/MD/*" -Destination "C:/Program Files/OpenSSL/lib/VC" -Recurse - - name: Configure CMake - if: matrix.os != 'macos-latest' + - name: Configure Ubuntu CMake + if: matrix.os == 'ubuntu-latest' + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_TESTS=ON + + - name: Configure Windows CMake + if: matrix.os == 'windows-latest' run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_TESTS=ON - name: Configure MacOS CMake diff --git a/CMakeLists.txt b/CMakeLists.txt index 4176760..6a80915 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,11 @@ project(authenticode_parser VERSION 1.0.0 LANGUAGES C) find_package(OpenSSL 1.1.1 REQUIRED) +message(STATUS "SSL library is ${OPENSSL_SSL_LIBRARY}") +message(STATUS "Crypto library is ${OPENSSL_SSL_LIBRARY}") +message(STATUS "All openssl libraries are ${OPENSSL_SSL_LIBRARY}") +message(STATUS "OpenSSL version is ${OPENSSL_SSL_LIBRARY}") + include(GNUInstallDirs) add_library(authenticode STATIC From 155213c7c4b1eba91b6977c139767e91d9e3fb1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20H=C3=A1jek?= Date: Fri, 26 Jan 2024 02:15:41 +0100 Subject: [PATCH 10/13] Add python scripts for test dev purposes --- dev_scripts.py/dump_pe_signature.py | 23 +++++++++++++++++++++++ dev_scripts.py/hex_to_c.py | 18 ++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 dev_scripts.py/dump_pe_signature.py create mode 100644 dev_scripts.py/hex_to_c.py diff --git a/dev_scripts.py/dump_pe_signature.py b/dev_scripts.py/dump_pe_signature.py new file mode 100644 index 0000000..d6d12c9 --- /dev/null +++ b/dev_scripts.py/dump_pe_signature.py @@ -0,0 +1,23 @@ +import sys +import pefile + + +# Function to dump the signature from a PE file for tests +def dump_signature(path: str): + pe = pefile.PE(path) + security_directory = pe.OPTIONAL_HEADER.DATA_DIRECTORY[ + pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_SECURITY"] + ] + win_certificate = pe.__data__[ + security_directory.VirtualAddress + + 8 : security_directory.VirtualAddress + + security_directory.Size + ] # Extract WIN_CERTIFICATE + with open("dump.pkcs7.der", "wb") as fp: + fp.write(win_certificate) + + +# Use the function +file_path = sys.argv[1] +# To convert to ASCII PEM to use in tests, use +# openssl pkcs7 -inform der -in dump.pkcs7.der -out sig.pem diff --git a/dev_scripts.py/hex_to_c.py b/dev_scripts.py/hex_to_c.py new file mode 100644 index 0000000..8dc2ae6 --- /dev/null +++ b/dev_scripts.py/hex_to_c.py @@ -0,0 +1,18 @@ +"""Convert hex string like c7fef94e329bd9b66b281539265f989313356cbd9c345df9e670e9c4b6e0edce to C array init""" +import sys + + +def hex_to_c_array(hex_string: str) -> str: + # Split the hex string into bytes + bytes = [hex_string[i : i + 2] for i in range(0, len(hex_string), 2)] + + # Format the bytes as a C array + c_array = ", ".join("0x" + byte for byte in bytes) + c_array = "uint8_t array[] = {" + c_array + "};" + + return c_array + + +# Use the function +hex_string = sys.argv[1] +print(hex_to_c_array(hex_string)) From c106d1bdbb416fc68d98036dd4e32614681962be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20H=C3=A1jek?= Date: Fri, 26 Jan 2024 02:15:53 +0100 Subject: [PATCH 11/13] Add program name to authenticode dumper --- examples/authenticode_dumper.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/authenticode_dumper.c b/examples/authenticode_dumper.c index abf20b3..ab1a30f 100644 --- a/examples/authenticode_dumper.c +++ b/examples/authenticode_dumper.c @@ -69,7 +69,11 @@ void print_authenticode(Authenticode *auth) printf("%sDigest Algorithm : %s\n", indent, auth->digest_alg); printf("%sVerify flags : %d\n", indent, auth->verify_flags); printf("%sCertificate count : %ld\n", indent, auth->certs->count); - printf("%sCertificates: \n\n", indent); + printf("%sCertificates: \n", indent); + if (auth->signer->program_name) { + printf("%sProgram name : %s\n", indent, auth->signer->program_name); + } + printf("\n"); if (auth->certs) { for (size_t i = 0; i < auth->certs->count; ++i) { From bee84cd966119042058691de922cae7bd2d35c7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20H=C3=A1jek?= Date: Fri, 26 Jan 2024 02:16:23 +0100 Subject: [PATCH 12/13] Add tests for new MS countersignature feature --- authenticode-config.cmake | 2 +- tests/CMakeLists.txt | 3 +- tests/data.h | 183 +++++++++++++ tests/integration/test_microsoft.cpp | 243 ++++++++++++++++++ .../{test.cpp => test_non_microsoft.cpp} | 0 5 files changed, 429 insertions(+), 2 deletions(-) create mode 100644 tests/integration/test_microsoft.cpp rename tests/integration/{test.cpp => test_non_microsoft.cpp} (100%) diff --git a/authenticode-config.cmake b/authenticode-config.cmake index da5957e..6b8ee3d 100644 --- a/authenticode-config.cmake +++ b/authenticode-config.cmake @@ -1,2 +1,2 @@ -find_package(OpenSSL 1.0.1 REQUIRED) +find_package(OpenSSL 1.1.1 REQUIRED) include(${CMAKE_CURRENT_LIST_DIR}/authenticode-targets.cmake) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 30d85d9..45a157b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -16,7 +16,8 @@ set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) add_executable(tests - integration/test.cpp + integration/test_microsoft.cpp + integration/test_non_microsoft.cpp unit/countersignature.cpp unit/certificate.cpp unit/helper.cpp) diff --git a/tests/data.h b/tests/data.h index 4bf0c5a..70e8404 100644 --- a/tests/data.h +++ b/tests/data.h @@ -405,6 +405,189 @@ "76k3su0TSVhNZbXFb80ASw==\n" \ "-----END PKCS7-----\n" +#define VALID_SIGNATURE_PEM_MICROSOFT_COUNTER \ + "-----BEGIN PKCS7-----\n" \ + "MIIhXQYJKoZIhvcNAQcCoIIhTjCCIUoCAQExDzANBglghkgBZQMEAgEFADBcBgor\n" \ + "BgEEAYI3AgEEoE4wTDAXBgorBgEEAYI3AgEPMAkDAQCgBKICgAAwMTANBglghkgB\n" \ + "ZQMEAgEFAAQgx/75TjKb2bZrKBU5Jl+YkxM1bL2cNF355nDpxLbg7c6gggtYMIIF\n" \ + "bzCCBFegAwIBAgITMwAAALIPmthnlPMi9gAAAAAAsjANBgkqhkiG9w0BAQsFADCB\n" \ + "jjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl\n" \ + "ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjE4MDYGA1UEAxMv\n" \ + "TWljcm9zb2Z0IFdpbmRvd3MgVGhpcmQgUGFydHkgQ29tcG9uZW50IENBIDIwMTIw\n" \ + "HhcNMjAxMjE1MjIxNTMwWhcNMjExMjAyMjIxNTMwWjCBkTELMAkGA1UEBhMCVVMx\n" \ + "EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT\n" \ + "FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjE7MDkGA1UEAxMyTWljcm9zb2Z0IFdpbmRv\n" \ + "d3MgSGFyZHdhcmUgQ29tcGF0aWJpbGl0eSBQdWJsaXNoZXIwggEiMA0GCSqGSIb3\n" \ + "DQEBAQUAA4IBDwAwggEKAoIBAQCZ8Mvh97s1+NdSDuMQQmFP8WJfahRttWXzOsi1\n" \ + "91Lm8LgV4Hc1Dlp2xcP5VVHFo/t9VJn1GDzJca4vskbeCAUSjx+3LVhZMnkM3YB2\n" \ + "dy0WryH1libJD5USJNVnpqaJBx4laPD+BvZDcGO+MSlL4ADlje2q1YCov9m6Ob3t\n" \ + "fCVDkOSCrN1Rm5gWTdmGQZVl/zKIq/MO133qd4EPLfFWweOeNDknKw2PG1WfgyP1\n" \ + "rxLuPMPzTF3ItPv3M3B28JGQSxYKLVP60BqVTSaLByfkQRQw33Unj3nMrIEFW0ba\n" \ + "/96m6prXTr82/S3+XwW1PDnC0GKylnxRxmeObM0IMgMoRy2HAgMBAAGjggG/MIIB\n" \ + "uzAfBgNVHSUEGDAWBgorBgEEAYI3CgMFBggrBgEFBQcDAzAdBgNVHQ4EFgQUAacC\n" \ + "woi4cJdR/kAf/aPCsLeFqIgwUAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jv\n" \ + "c29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAxNTMrNDYz\n" \ + "MDM2MB8GA1UdIwQYMBaAFGFxp4ev/2nVIXZPUpMoAL55EquEMHQGA1UdHwRtMGsw\n" \ + "aaBnoGWGY2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jv\n" \ + "c29mdCUyMFdpbmRvd3MlMjBUaGlyZCUyMFBhcnR5JTIwQ29tcG9uZW50JTIwQ0El\n" \ + "MjAyMDEyLmNybDCBgQYIKwYBBQUHAQEEdTBzMHEGCCsGAQUFBzAChmVodHRwOi8v\n" \ + "d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFdpbmRv\n" \ + "d3MlMjBUaGlyZCUyMFBhcnR5JTIwQ29tcG9uZW50JTIwQ0ElMjAyMDEyLmNydDAM\n" \ + "BgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQAZmsxqhxfA21tLIxLczYux\n" \ + "4z70knMfuOHWC9b2kN4HS22SKTyCYAEtysxmimjxDnJqN9L/fuZrHupCT1bxBCSb\n" \ + "1tfn66jBdF9PEUO6x+ZI5IwbKhrfaVS13hZp3xnEvlYzt5G3o8uiNkEAb9WKwtSU\n" \ + "odANrbw7P+UKetAWPLOGaTgkEGtd2fm4pXnkX1xfiASDK4p3NwHgyjHe6aAS/OWR\n" \ + "FJLek77qRKOCL3qDxEikhO65N6T6f0Bnh5uRDlNMlm0mUL1ck/BmZWqg9MfDGBYd\n" \ + "Sos2cFbfQq9goKrQ6y3ju0e5a5SPLISfMwz+9Znxd1u21BzxUN7LQKg9WAByfZd+\n" \ + "MIIF4TCCA8mgAwIBAgIKYQuqwQAAAAAACTANBgkqhkiG9w0BAQsFADCBiDELMAkG\n" \ + "A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx\n" \ + "HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9z\n" \ + "b2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTIwNDE4MjM0\n" \ + "ODM4WhcNMjcwNDE4MjM1ODM4WjCBjjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh\n" \ + "c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD\n" \ + "b3Jwb3JhdGlvbjE4MDYGA1UEAxMvTWljcm9zb2Z0IFdpbmRvd3MgVGhpcmQgUGFy\n" \ + "dHkgQ29tcG9uZW50IENBIDIwMTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n" \ + "AoIBAQCjnDCECadjLs8KR/DqJPmjMCAPXlcxJoGaMQeyUNTOZwkIZQpapUuu1e0Q\n" \ + "LuelmbWfaC+Yi1gCrCC0KcRxvSgcpf08m2TkxevfYSW88O5ov9Gny34qAoFOZFwM\n" \ + "U4Z5Vxk3YbeY+QygTiJZm/kbLWc8JzxWkGbj/X9lfQ+GvTVH6IrM9NqO6WpOq6dV\n" \ + "7KKJHtUzRVPL+Z53vc0s+QW4f3QBHej7GOFD0Q3pqtw3b73+uA/tHU0BRk4KrPyC\n" \ + "6OxWgxOOOgHtFGR06mSyZhC2aG3IcAB9UEguPUPu4CSVxs2Ox/245JXP3X77lV6h\n" \ + "Ac1DsQfXpDDum4YaKm7BC1midG+LAgMBAAGjggFDMIIBPzAQBgkrBgEEAYI3FQEE\n" \ + "AwIBADAdBgNVHQ4EFgQUYXGnh6//adUhdk9SkygAvnkSq4QwGQYJKwYBBAGCNxQC\n" \ + "BAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYD\n" \ + "VR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZF\n" \ + "aHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9v\n" \ + "Q2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcw\n" \ + "AoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJB\n" \ + "dXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcNAQELBQADggIBAFqKZ9rM1f0NJkF3\n" \ + "vwpGeLSz3hJpK3cjwmUvAV/SA/RhulCdLow5cvNsPmqxHnZt7LfzgtzMu8VpcChz\n" \ + "Zhc/VOvuARZIxEbZG4CugTqND3ltaLCe6i0/OdPKOH69XnwIbhncxsL0ODNoYeJS\n" \ + "R4PhAAFW0rrLh4IFMQpBi07nf19f7V/TOS1F66ITv/0ewphBcWEWX8gKcCV8WWkx\n" \ + "JORx5wq7BBf3n3IeydK7Gr49Av4JDLJDtFkamVOTliFf4Na3JgFClTasJ/2+9IV3\n" \ + "aD0YvfS+mIgiEYZSFvNF7AOXEHCHo3BDcTzbyYYDFwz1c1vGfeFcZO3XxUjX7TLi\n" \ + "0arTz6f2V05h+XfrZ/KIs94A2gOP0Io0Nz4d2GK40rHz4S+LcjuBlnxv/OxmdnJg\n" \ + "GyTyoIltW20ALu8o3YaHBcK0ueW+ZMIq8koVXJjixCeF/1LjYn4PsgIL12bHCrLT\n" \ + "PSAEFFAyWYMKfZvtWjgSAVK6L14gco5K8f3ncQKMO+EHvslz9N1H2LTvtKSzMLmJ\n" \ + "PnbKuQCYVn6r6oq4pdA4q2l3EwsUL+mqQR/3ur06KzSK7gqrY+Zj94gkjiANKzud\n" \ + "48JJUqyfHw45O13UblBq5n1SOqp8MxUpDSZeAVinTqk9eoRvdD9gn+QyTzYAr21x\n" \ + "0z6mRmVfgXTx/sFx2kygQVqC3fEfMYIVeDCCFXQCAQEwgaYwgY4xCzAJBgNVBAYT\n" \ + "AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD\n" \ + "VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xODA2BgNVBAMTL01pY3Jvc29mdCBX\n" \ + "aW5kb3dzIFRoaXJkIFBhcnR5IENvbXBvbmVudCBDQSAyMDEyAhMzAAAAsg+a2GeU\n" \ + "8yL2AAAAAACyMA0GCWCGSAFlAwQCAQUAoIGuMBkGCSqGSIb3DQEJAzEMBgorBgEE\n" \ + "AYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJ\n" \ + "BDEiBCAW78UlDE1KmaAO0q2aDj2PvCHaW+law1rTOz2cPzcZoTBCBgorBgEEAYI3\n" \ + "AgEMMTQwMqAQgA4AUAByAG8AYwBlAHgAcKEegBxodHRwczovL3d3dy5zeXNpbnRl\n" \ + "cm5hbHMuY29tMA0GCSqGSIb3DQEBAQUABIIBADFezXsgSTCtb/LuWnk3z2zPMi20\n" \ + "W3F/JeY8sjPVTwmoEHYhglcCh0a75ilUymow6ut8xCILl+7Evlm2e6U4A5goHn2y\n" \ + "X/06UUI5l0jXFjSXby6tyoD2xZZHhXN+o5gT5Ke92ryvXkKzYsOc37cO3Q5/7Jxr\n" \ + "5lqWqCGXJ3pOqBI4KDCA7mgyZtzSk6POoXHEbb+PBKvBB0Fj0HGF/j/q+8m2jpV3\n" \ + "iDEII1StD5C9BjlHdZeRiCKnAvvWjomSnGFg/VXJ6rzqahqij3JTcmBaYnrcJXub\n" \ + "5kY7OYXP4CDO+rrY14LY4VHEdHNOm3i2Ihae5wqIRjKyBdvg6iUz7D7H9bOhghLx\n" \ + "MIIS7QYKKwYBBAGCNwMDATGCEt0wghLZBgkqhkiG9w0BBwKgghLKMIISxgIBAzEP\n" \ + "MA0GCWCGSAFlAwQCAQUAMIIBVQYLKoZIhvcNAQkQAQSgggFEBIIBQDCCATwCAQEG\n" \ + "CisGAQQBhFkKAwEwMTANBglghkgBZQMEAgEFAAQg7d+KRTQOFrNVao5Ss/zS5zxc\n" \ + "R9NqpnFP/u8sGWA3Z28CBmD69yXH+BgTMjAyMTA4MTcwMjAxMzMuMDYzWjAEgAIB\n" \ + "9KCB1KSB0TCBzjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO\n" \ + "BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEp\n" \ + "MCcGA1UECxMgTWljcm9zb2Z0IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNV\n" \ + "BAsTHVRoYWxlcyBUU1MgRVNOOjMyQkQtRTNENS0zQjFEMSUwIwYDVQQDExxNaWNy\n" \ + "b3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloIIORDCCBPUwggPdoAMCAQICEzMAAAFi\n" \ + "0P4C8wHlzUkAAAAAAWIwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzAR\n" \ + "BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p\n" \ + "Y3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3Rh\n" \ + "bXAgUENBIDIwMTAwHhcNMjEwMTE0MTkwMjIyWhcNMjIwNDExMTkwMjIyWjCBzjEL\n" \ + "MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v\n" \ + "bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWlj\n" \ + "cm9zb2Z0IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBU\n" \ + "U1MgRVNOOjMyQkQtRTNENS0zQjFEMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1T\n" \ + "dGFtcCBTZXJ2aWNlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA74ah\n" \ + "1Pa5wvcyvYNCy/YQs1tK8rIGlh1Qq1QFaJmYVXLXykb+m5yCStzmL227wJjsalZX\n" \ + "8JA2YcbaZV5Icwm9vAJz8AC/sk/dsUK3pmDvkhtVI04YDV6otuZCILpQB9Ipcs3d\n" \ + "0e1Dl2KKFvdibOk0/0rRxU9l+/Yxeb5lVTRERLxzI+Rd6Xv5QQYT6Sp2IE0N1vzI\n" \ + "Fd3yyO773T5XifNgL5lZbtIUnYUVmUBKlVoemO/54aiFeVBpIG+YzhDTF7cuHNAz\n" \ + "xWIbP1wt4VIqAV9JjuqLMvvBSD56pi8NTKM9fxrERAeaTS2HbfBYfmnRZ27Czjeo\n" \ + "0ijQ5DSZGi0ErvWfKQIDAQABo4IBGzCCARcwHQYDVR0OBBYEFMvEShFgSkO3Onzg\n" \ + "HlaVk3aQ/iprMB8GA1UdIwQYMBaAFNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1Ud\n" \ + "HwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3By\n" \ + "b2R1Y3RzL01pY1RpbVN0YVBDQV8yMDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQRO\n" \ + "MEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2Vy\n" \ + "dHMvTWljVGltU3RhUENBXzIwMTAtMDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYD\n" \ + "VR0lBAwwCgYIKwYBBQUHAwgwDQYJKoZIhvcNAQELBQADggEBAC1BrcOhdhtb9xcA\n" \ + "JtxVIUZ7iALwZewXFIdPcmDAVT810k5xuRwVNW9Onq+WZO8ebqwiOSdEEHReLU0F\n" \ + "Oo/DbS7q79PsKdz/PSBPqZ/1ysjRVH0L5HUK2N7NgpkR1lnt+41BaOzJ+00OFDL5\n" \ + "GqeqvK3RWh7MtqWF6KKcfNkP/hjiFlg9/S7xNK/Vl8q10HB5YbdBTQun8j1Jsih6\n" \ + "YMb3tFQsxw++ra5+FSnc4yJhAYvVaqTKRKepEmwzYhwDiXh2ag80/p0uDkOvs1Wh\n" \ + "gogwidpBVmNLAMxmFavK9+LNfRKvPIuCQw+EsxWR8vFBBJDfs14WTsXVF94CQ1YC\n" \ + "HqYI5EEwggZxMIIEWaADAgECAgphCYEqAAAAAAACMA0GCSqGSIb3DQEBCwUAMIGI\n" \ + "MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk\n" \ + "bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylN\n" \ + "aWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0xMDA3\n" \ + "MDEyMTM2NTVaFw0yNTA3MDEyMTQ2NTVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI\n" \ + "EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv\n" \ + "ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD\n" \ + "QSAyMDEwMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqR0NvHcRijog\n" \ + "7PwTl/X6f2mUa3RUENWlCgCChfvtfGhLLF/Fw+Vhwna3PmYrW/AVUycEMR9BGxqV\n" \ + "Hc4JE458YTBZsTBED/FgiIRUQwzXTbg4CLNC3ZOs1nMwVyaCo0UN0Or1R4HNvyRg\n" \ + "MlhgRvJYR4YyhB50YWeRX4FUsc+TTJLBxKZd0WETbijGGvmGgLvfYfxGwScdJGcS\n" \ + "chohiq9LZIlQYrFd/XcfPfBXday9ikJNQFHRD5wGPmd/9WbAA5ZEfu/QS/1u5ZrK\n" \ + "sajyeioKMfDaTgaRtogINeh4HLDpmc085y9Euqf03GS9pAHBIAmTeM38vMDJRF1e\n" \ + "FpwBBU8iTQIDAQABo4IB5jCCAeIwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYE\n" \ + "FNVjOlyKMZDzQ3t8RhvFM2hahW1VMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBB\n" \ + "MAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP\n" \ + "6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWlj\n" \ + "cm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2\n" \ + "LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cu\n" \ + "bWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMu\n" \ + "Y3J0MIGgBgNVHSABAf8EgZUwgZIwgY8GCSsGAQQBgjcuAzCBgTA9BggrBgEFBQcC\n" \ + "ARYxaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL1BLSS9kb2NzL0NQUy9kZWZhdWx0\n" \ + "Lmh0bTBABggrBgEFBQcCAjA0HjIgHQBMAGUAZwBhAGwAXwBQAG8AbABpAGMAeQBf\n" \ + "AFMAdABhAHQAZQBtAGUAbgB0AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEAB+aIUQ3i\n" \ + "xuCYP4FxAz2do6Ehb7Prpsz1Mb7PBeKp/vpXbRkws8LFZslq3/Xn8Hi9x6ieJeP5\n" \ + "vO1rVFcIK1GCRBL7uVOMzPRgEop2zEBAQZvcXBf/XPleFzWYJFZLdO9CEMivv3/G\n" \ + "f/I3fVo/HPKZeUqRUgCvOA8X9S95gWXZqbVr5MfO9sp6AG9LMEQkIjzP7QOllo9Z\n" \ + "Kby2/QThcJ8ySif9Va8v/rbljjO7Yl+a21dA6fHOmWaQjP9qYn/dxUoLkSbiOewZ\n" \ + "SnFjnXshbcOco6I8+n99lmqQeKZt0uGc+R38ONiU9MalCpaGpL2eGq4EQoO4tYCb\n" \ + "IjggtSXlZOz39L9+Y1klD3ouOVd2onGqBooPiRa6YacRy5rYDkeagMXQzafQ732D\n" \ + "8OE7cQnfXXSYIghh2rBQHm+98eEA3+cxB6STOvdlR3jo+KhIq/fecn5ha293qYHL\n" \ + "pwmsObvsxsvYgrRyzR30uIUBHoD7G4kqVDmyW9rIDVWZeodzOwjmmC3qjeAzLhIp\n" \ + "9cAvVCch98isTtoouLGp25ayp0Kiyc8ZQU3ghvkqmqMRZjDTu3QyS99je/WZii8b\n" \ + "xyGvWbWu3EQ8l1Bx16HSxVXjad5XwdHeMMD9zOZN+w2/XU/pnR4ZOC+8z1gFLu8N\n" \ + "oFA12u8JJxzVs341Hgi62jbb01+P3nSISRKhggLSMIICOwIBATCB/KGB1KSB0TCB\n" \ + "zjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl\n" \ + "ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMg\n" \ + "TWljcm9zb2Z0IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxl\n" \ + "cyBUU1MgRVNOOjMyQkQtRTNENS0zQjFEMSUwIwYDVQQDExxNaWNyb3NvZnQgVGlt\n" \ + "ZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQCas/oKGtvPRrHuznufk+in\n" \ + "dULyDKCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u\n" \ + "MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp\n" \ + "b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqG\n" \ + "SIb3DQEBBQUAAgUA5MUY7TAiGA8yMDIxMDgxNjIxMDM0MVoYDzIwMjEwODE3MjEw\n" \ + "MzQxWjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDkxRjtAgEAMAoCAQACAg4WAgH/\n" \ + "MAcCAQACAhEqMAoCBQDkxmptAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQB\n" \ + "hFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEA\n" \ + "KNml34ZXWEvO8kgJa9uw7u+F2acSHsoxOHOZ/so7COqb+iwKmVx74jfqyqorssnn\n" \ + "Vmm8+hRy4zFbEo5lACNtYIlCMB6uIgcfJlHvHAf18unlj3U7H4+BisX27wXJDshB\n" \ + "lyX3cr7ib4AVQhXjGPEUqj2MBZS/bVda3ZhDwLKqGFkxggMNMIIDCQIBATCBkzB8\n" \ + "MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk\n" \ + "bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N\n" \ + "aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAWLQ/gLzAeXNSQAAAAAB\n" \ + "YjANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE\n" \ + "MC8GCSqGSIb3DQEJBDEiBCBPYkNXIBZ7/hs5bcFJ60pVHV91A+hbat8GKp+oVfgL\n" \ + "sTCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIIqqGJX7PA0OulTsNEHsyLnv\n" \ + "GLoYE1iwaOBmqrapUwoyMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT\n" \ + "Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m\n" \ + "dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB\n" \ + "IDIwMTACEzMAAAFi0P4C8wHlzUkAAAAAAWIwIgQggc576of3UYTIpBYSSlOC3fTU\n" \ + "CdPUhARDA3k0nsKK9iUwDQYJKoZIhvcNAQELBQAEggEATYtDaVhvhu0N8/NxvA4r\n" \ + "7MuRznFgDqSp3kMNY9vl0mRZZodTTzmbJvy35qAB36qm5FvEtLASS8oCJnhokn/3\n" \ + "qIMEJpwyvd3r/oWRElnF0sWrhDW3m1zJ5aILJVwEK+h1tXSTNVxzyM2m7SmlEb35\n" \ + "Wr5URlinyoOJ74AL2CTCLuq47sx68ZdaPCHXXDn94Ofayk+kPMWh9SswJ2yvhjZf\n" \ + "HSGSLyJ3X14M4dSxJPazz8b5887FOyvmK5Vitci5/Gcou2ydyESC4DGozGMTMW07\n" \ + "ii94+89h6CigWDLhgq3p8YUuO45tzZbN7YrPnS3KHl6DmfxcCbQtfCo40WWOm8vs\n" \ + "kA==\n" \ + "-----END PKCS7-----" + #define CERTIFICATE_PEM \ "-----BEGIN CERTIFICATE-----\n" \ "MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML\n" \ diff --git a/tests/integration/test_microsoft.cpp b/tests/integration/test_microsoft.cpp new file mode 100644 index 0000000..8cc58ba --- /dev/null +++ b/tests/integration/test_microsoft.cpp @@ -0,0 +1,243 @@ +/* Copyright (c) 2021 Avast Software + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "../data.h" +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +class MicrosoftSignatureTest : public testing::Test +{ + protected: + unsigned char *data = nullptr; + long data_len = 0; + + void SetUp() override + { + BIO *bio = BIO_new(BIO_s_mem()); + BIO_write( + bio, + VALID_SIGNATURE_PEM_MICROSOFT_COUNTER, + std::strlen(VALID_SIGNATURE_PEM_MICROSOFT_COUNTER)); + char *name = nullptr; + char *header = nullptr; + PEM_read_bio(bio, &name, &header, &data, &data_len); + BIO_free_all(bio); + OPENSSL_free(name); + OPENSSL_free(header); + + initialize_authenticode_parser(); + } + + void TearDown() override { OPENSSL_free(data); } +}; + +TEST_F(MicrosoftSignatureTest, ResultOverview) +{ + AuthenticodeArray *auth = authenticode_new(data, data_len); + ASSERT_NE(auth, nullptr); + + ASSERT_EQ(auth->count, 1); + ASSERT_NE(auth->signatures, nullptr); + + for (size_t i = 0; i < auth->count; ++i) { + ASSERT_TRUE(auth->signatures[i]); + } + + authenticode_array_free(auth); +} + +TEST_F(MicrosoftSignatureTest, SignatureContent) +{ + AuthenticodeArray *auth = authenticode_new(data, data_len); + ASSERT_NE(auth, nullptr); + + ASSERT_EQ(auth->count, 1); + ASSERT_NE(auth->signatures, nullptr); + + const Authenticode *first_sig = auth->signatures[0]; + ASSERT_TRUE(first_sig); + + //***********************************// + // Check the first signature content // + EXPECT_EQ(first_sig->version, 1); + + EXPECT_TRUE(first_sig->digest.data); + uint8_t file_digest[32] = {0xc7, 0xfe, 0xf9, 0x4e, 0x32, 0x9b, 0xd9, 0xb6, 0x6b, 0x28, 0x15, + 0x39, 0x26, 0x5f, 0x98, 0x93, 0x13, 0x35, 0x6c, 0xbd, 0x9c, 0x34, + 0x5d, 0xf9, 0xe6, 0x70, 0xe9, 0xc4, 0xb6, 0xe0, 0xed, 0xce}; + EXPECT_EQ(first_sig->digest.len, 32); + EXPECT_TRUE(std::memcmp(file_digest, first_sig->digest.data, 32) == 0); + EXPECT_STREQ(first_sig->digest_alg, "sha256"); + + EXPECT_EQ(first_sig->verify_flags, AUTHENTICODE_VFY_VALID); + + //****************************// + // Check SignerInfo structure // + ASSERT_TRUE(first_sig->signer); + EXPECT_STREQ(first_sig->signer->digest_alg, "sha256"); + + ASSERT_TRUE(first_sig->signer->digest.data); + ASSERT_EQ(first_sig->signer->digest.len, 32); + uint8_t message_digest[32] = {0x16, 0xef, 0xc5, 0x25, 0x0c, 0x4d, 0x4a, 0x99, 0xa0, 0x0e, 0xd2, + 0xad, 0x9a, 0x0e, 0x3d, 0x8f, 0xbc, 0x21, 0xda, 0x5b, 0xe9, 0x5a, + 0xc3, 0x5a, 0xd3, 0x3b, 0x3d, 0x9c, 0x3f, 0x37, 0x19, 0xa1}; + EXPECT_TRUE(std::memcmp(message_digest, first_sig->signer->digest.data, 32) == 0); + ASSERT_TRUE(first_sig->signer->program_name); + ASSERT_STREQ(first_sig->signer->program_name, "Procexp"); + + //******************************************// + // Test all certificates of first signature // + ASSERT_TRUE(first_sig->certs); + ASSERT_TRUE(first_sig->certs->certs); + ASSERT_EQ(first_sig->certs->count, 2); + + //**************************// + // Check the 1. certificate // + const Certificate *cert = first_sig->certs->certs[0]; + ASSERT_TRUE(cert->sha1.data); + ASSERT_EQ(cert->sha1.len, 20); + unsigned char first_cert_sha1[20] = {0x92, 0xd7, 0x19, 0x2a, 0x7c, 0x31, 0x80, + 0x91, 0x2f, 0xf8, 0x41, 0x4f, 0x79, 0x09, + 0x73, 0xa0, 0x5c, 0x28, 0xf8, 0xb0}; + EXPECT_TRUE(std::memcmp(first_cert_sha1, cert->sha1.data, 20) == 0); + EXPECT_EQ(cert->version, 2); + EXPECT_STREQ( + cert->subject, + "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Windows Hardware " + "Compatibility Publisher"); + EXPECT_STREQ( + cert->issuer, + "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Windows Third Party " + "Component CA 2012"); + + //**************************// + // Check the 2. certificate // + cert = first_sig->certs->certs[1]; + ASSERT_TRUE(cert->sha1.data); + ASSERT_EQ(cert->sha1.len, 20); + unsigned char second_cert_sha1[20] = {0x77, 0xa1, 0x0e, 0xbf, 0x07, 0x54, 0x27, + 0x25, 0x21, 0x8c, 0xd8, 0x3a, 0x01, 0xb5, + 0x21, 0xc5, 0x7b, 0xc6, 0x7f, 0x73}; + EXPECT_TRUE(std::memcmp(second_cert_sha1, cert->sha1.data, 20) == 0); + + //**************************// + // Check the Counter signature // + const Countersignature *countersig = first_sig->countersigs->counters[0]; + + EXPECT_EQ(countersig->verify_flags, COUNTERSIGNATURE_VFY_VALID); + EXPECT_STREQ(countersig->digest_alg, "sha256"); + EXPECT_EQ(countersig->sign_time, 1629165693); + unsigned char first_countersig_digest[32] = {0xed, 0xdf, 0x8a, 0x45, 0x34, 0x0e, 0x16, 0xb3, + 0x55, 0x6a, 0x8e, 0x52, 0xb3, 0xfc, 0xd2, 0xe7, + 0x3c, 0x5c, 0x47, 0xd3, 0x6a, 0xa6, 0x71, 0x4f, + 0xfe, 0xef, 0x2c, 0x19, 0x60, 0x37, 0x67, 0x6f}; + ASSERT_TRUE(countersig->digest.data); + ASSERT_EQ(countersig->digest.len, 32); + EXPECT_TRUE(std::memcmp(first_countersig_digest, countersig->digest.data, 32) == 0); + + ASSERT_TRUE(countersig->chain); + EXPECT_EQ(countersig->chain->count, 2); + + //**************************// + // Check the 1. certificate // + cert = countersig->chain->certs[0]; + ASSERT_TRUE(cert->sha1.data); + ASSERT_EQ(cert->sha1.len, 20); + unsigned char first_countercert_sha1[20] = {0x9a, 0xb3, 0xfa, 0x0a, 0x1a, 0xdb, 0xcf, + 0x46, 0xb1, 0xee, 0xce, 0x7b, 0x9f, 0x93, + 0xe8, 0xa7, 0x75, 0x42, 0xf2, 0x0c}; + EXPECT_TRUE(std::memcmp(first_countercert_sha1, cert->sha1.data, 20) == 0); + ASSERT_EQ(cert->sha256.len, 32); + unsigned char first_countercert_sha256[32] = {0x8a, 0xaa, 0x18, 0x95, 0xfb, 0x3c, 0x0d, 0x0e, + 0xba, 0x54, 0xec, 0x34, 0x41, 0xec, 0xc8, 0xb9, + 0xef, 0x18, 0xba, 0x18, 0x13, 0x58, 0xb0, 0x68, + 0xe0, 0x66, 0xaa, 0xb6, 0xa9, 0x53, 0x0a, 0x32}; + EXPECT_TRUE(std::memcmp(first_countercert_sha256, cert->sha256.data, 32) == 0); + + EXPECT_EQ(cert->version, 2); + EXPECT_STREQ( + cert->subject, + "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/OU=Microsoft Operations Puerto " + "Rico/OU=Thales TSS ESN:32BD-E3D5-3B1D/CN=Microsoft Time-Stamp Service"); + EXPECT_STREQ( + cert->issuer, + "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Time-Stamp PCA 2010"); + EXPECT_EQ(cert->not_after, 1649703742); + EXPECT_EQ(cert->not_before, 1610650942); + EXPECT_STREQ( + cert->key, + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA74ah1Pa5wvcyvYNCy/" + "YQs1tK8rIGlh1Qq1QFaJmYVXLXykb+m5yCStzmL227wJjsalZX8JA2YcbaZV5Icwm9vAJz8AC/sk/" + "dsUK3pmDvkhtVI04YDV6otuZCILpQB9Ipcs3d0e1Dl2KKFvdibOk0/0rRxU9l+/" + "Yxeb5lVTRERLxzI+Rd6Xv5QQYT6Sp2IE0N1vzIFd3yyO773T5XifNgL5lZbtIUnYUVmUBKlVoemO/" + "54aiFeVBpIG+" + "YzhDTF7cuHNAzxWIbP1wt4VIqAV9JjuqLMvvBSD56pi8NTKM9fxrERAeaTS2HbfBYfmnRZ27Czjeo0ijQ5DSZGi0Er" + "vWfKQIDAQAB"); + EXPECT_STREQ(cert->serial, "33:00:00:01:62:d0:fe:02:f3:01:e5:cd:49:00:00:00:00:01:62"); + EXPECT_STREQ(cert->sig_alg, "sha256WithRSAEncryption"); + EXPECT_STREQ(cert->key_alg, "rsaEncryption"); + + //**************************// + // Check the 2. certificate // + cert = countersig->chain->certs[1]; + ASSERT_TRUE(cert->sha1.data); + ASSERT_EQ(cert->sha1.len, 20); + unsigned char second_countercert_sha1[20] = {0x2a, 0xa7, 0x52, 0xfe, 0x64, 0xc4, 0x9a, + 0xbe, 0x82, 0x91, 0x3c, 0x46, 0x35, 0x29, + 0xcf, 0x10, 0xff, 0x2f, 0x04, 0xee}; + EXPECT_TRUE(std::memcmp(second_countercert_sha1, cert->sha1.data, 20) == 0); + + ASSERT_TRUE(cert->sha256.data); + ASSERT_EQ(cert->sha256.len, 32); + unsigned char second_countercert_sha256[32] = {0x86, 0xec, 0x11, 0x8d, 0x1e, 0xe6, 0x96, 0x70, + 0xa4, 0x6e, 0x2b, 0xe2, 0x9c, 0x4b, 0x42, 0x08, + 0xbe, 0x04, 0x3e, 0x36, 0x60, 0x0d, 0x4e, 0x1d, + 0xd3, 0xf3, 0xd5, 0x15, 0xca, 0x11, 0x90, 0x20}; + EXPECT_TRUE(std::memcmp(second_countercert_sha256, cert->sha256.data, 32) == 0); + + EXPECT_EQ(cert->version, 2); + EXPECT_STREQ( + cert->subject, "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Time-Stamp PCA 2010"); + EXPECT_STREQ( + cert->issuer, + "/C=US/ST=Washington/L=Redmond/O=Microsoft Corporation/CN=Microsoft Root Certificate Authority 2010"); + EXPECT_EQ(cert->not_after, 1751406415); + EXPECT_EQ(cert->not_before, 1278020215); + EXPECT_STREQ( + cert->key, + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqR0NvHcRijog7PwTl/X6f2mUa3RUENWlCgCChfvtfGhLLF/Fw+Vhwna3PmYrW/AVUycEMR9BGxqVHc4JE458YTBZsTBED/FgiIRUQwzXTbg4CLNC3ZOs1nMwVyaCo0UN0Or1R4HNvyRgMlhgRvJYR4YyhB50YWeRX4FUsc+TTJLBxKZd0WETbijGGvmGgLvfYfxGwScdJGcSchohiq9LZIlQYrFd/XcfPfBXday9ikJNQFHRD5wGPmd/9WbAA5ZEfu/QS/1u5ZrKsajyeioKMfDaTgaRtogINeh4HLDpmc085y9Euqf03GS9pAHBIAmTeM38vMDJRF1eFpwBBU8iTQIDAQAB"); + EXPECT_STREQ(cert->serial, "61:09:81:2a:00:00:00:00:00:02"); + EXPECT_STREQ(cert->sig_alg, "sha256WithRSAEncryption"); + EXPECT_STREQ(cert->key_alg, "rsaEncryption"); + + authenticode_array_free(auth); +} diff --git a/tests/integration/test.cpp b/tests/integration/test_non_microsoft.cpp similarity index 100% rename from tests/integration/test.cpp rename to tests/integration/test_non_microsoft.cpp From 01b1de1647556d44ab138bd286f02b7576e7c2df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20H=C3=A1jek?= Date: Fri, 26 Jan 2024 02:23:43 +0100 Subject: [PATCH 13/13] Update CI to use expected OpenSSL versions --- .github/workflows/cmake.yml | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 8deec35..65950bb 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -14,7 +14,7 @@ jobs: test-openssl1-1: strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [macos-latest, windows-latest] # Stops killing other jobs when one fails fail-fast: false @@ -25,19 +25,17 @@ jobs: - name: Install OpenSSL on MacOS if: matrix.os == 'macos-latest' - # We want 1.1.1 version max as the 3.0 is default on MacOS otherwise - run: brew install openssl@1.1 - + run: | + brew uninstall openssl --ignore-dependencies openssl + brew install openssl@1.1 + - name: Install OpenSSL on Windows if: matrix.os == 'windows-latest' run: | rd -r "C:/Program Files/OpenSSL" - choco install openssl - Copy-Item -Path "C:/Program Files/OpenSSL/lib/VC/x64/MD/*" -Destination "C:/Program Files/OpenSSL/lib/VC" -Recurse + choco install openssl --version=1.1.1.2100 - - name: Configure Ubuntu CMake - if: matrix.os == 'ubuntu-latest' - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_TESTS=ON +# Copy-Item -Path "C:/Program Files/OpenSSL/lib/VC/x64/MD/*" -Destination "C:/Program Files/OpenSSL/lib/VC" -Recurse - name: Configure Windows CMake if: matrix.os == 'windows-latest' @@ -45,7 +43,7 @@ jobs: - name: Configure MacOS CMake if: matrix.os == 'macos-latest' - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_TESTS=ON -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_TESTS=ON -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl@1.1 - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} @@ -54,18 +52,30 @@ jobs: working-directory: ${{github.workspace}}/build run: ctest -C ${{env.BUILD_TYPE}} -VV - test-openssl3-0: - runs-on: macos-latest + test-openssl3: + strategy: + matrix: + os: [macos-latest, ubuntu-latest] + # Stops killing other jobs when one fails + fail-fast: false + + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - name: Install OpenSSL on MacOS + if: matrix.os == 'macos-latest' run: brew install openssl@3 - name: Configure MacOS CMake + if: matrix.os == 'macos-latest' run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_TESTS=ON -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl + - name: Configure Ubuntu CMake + if: matrix.os == 'ubuntu-latest' + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_TESTS=ON + - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}