#include "base/strings/utf_string_conversions.h"
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif#define WINCRYPT_USE_SYMBOL_PREFIX#include <Windows.h>#include <ImageHlp.h>#include "base/command_line.h"
#include "base/containers/heap_array.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"//
#include "third_party/boringssl/src/crypto/x509/internal.h"
#include "third_party/boringssl/src/include/openssl/asn1.h"
#include "third_party/boringssl/src/include/openssl/asn1t.h"
#include "third_party/boringssl/src/include/openssl/bytestring.h"
#include "third_party/boringssl/src/include/openssl/x509.h"#include "third_party/boringssl/src/pki/parser.h"namespace switches {
constexpr char kInput[] = "input";
}constexpr uint8_t kOidSpcIndirectDataContent[] = {0x2b, 0x06, 0x01, 0x04, 0x01,0x82, 0x37, 0x02, 0x01, 0x04}; // 1.3.6.1.4.1.311.2.1.4constexpr uint8_t kOidSpcPEImageData[] = {0x2b, 0x06, 0x01, 0x04, 0x01,0x82, 0x37, 0x02, 0x01, 0x0F}; // 1.3.6.1.4.1.311.2.1.15constexpr uint8_t kOidPKCS7SignedData[] = {0x2a, 0x86, 0x48, 0x86, 0xf7,0x0d, 0x01, 0x07, 0x02}; // 1.2.840.113549.1.7.2// SpcAttributeTypeAndOptionalValue ::= SEQUENCE {
// type ObjectID,
// value [0] EXPLICIT ANY OPTIONAL
// }// SpcPeImageData ::= SEQUENCE {
// flags SpcPeImageFlags DEFAULT { includeResources },
// file SpcLink
// } --#public--// SpcPeImageFlags ::= BIT STRING {
// includeResources (0),
// includeDebugInfo (1),
// includeImportAddressTable (2)
// }// SpcLink ::= CHOICE {
// url [0] IMPLICIT IA5STRING,
// moniker [1] IMPLICIT SpcSerializedObject,
// file [2] EXPLICIT SpcString
// } --#public--// SpcString ::= CHOICE {
// unicode [0] IMPLICIT BMPSTRING,
// ascii [1] IMPLICIT IA5STRING
// }bool Parse_SpcString(CBS* cbs) {if (CBS_peek_asn1_tag(cbs, CBS_ASN1_CONTEXT_SPECIFIC | 0)) { // unicodeCBS child;if (!CBS_get_asn1(cbs, &child, CBS_ASN1_CONTEXT_SPECIFIC | 0)) {LOG(ERROR) << "Failed to parse unicode SpcString";return false;}if (CBS_len(&child) == 0) {LOG(INFO) << "SpcString (unicode): <empty>";return true;}CBS str;if (!CBS_get_asn1(&child, &str, CBS_ASN1_BMPSTRING)) {LOG(ERROR) << "Failed to parse unicode SpcString";return false;}std::u16string u16_str(reinterpret_cast<const char16_t*>(CBS_data(&str)),CBS_len(&str) / sizeof(char16_t));std::string u8_str = base::UTF16ToUTF8(u16_str);LOG(INFO) << "SpcString (unicode): " << u8_str;} else if (CBS_peek_asn1_tag(cbs, CBS_ASN1_CONTEXT_SPECIFIC | 1)) { // asciiCBS child;CBS str;if (!CBS_get_asn1(cbs, &child, CBS_ASN1_CONTEXT_SPECIFIC | 1) ||!CBS_get_asn1(&child, &str, CBS_ASN1_IA5STRING)) {LOG(ERROR) << "Failed to parse ascii SpcString";return false;}std::string u8_str(reinterpret_cast<const char*>(CBS_data(&str)),CBS_len(&str));LOG(INFO) << "SpcString (ascii): " << u8_str;} else {LOG(ERROR) << "Failed to parse SpcString choice";return false;}if (CBS_len(cbs) != 0) {LOG(ERROR) << "Trailing data in SpcString";return false;}return true;
}bool Parse_Authenticode_SpcPeImageData(CBS* cbs) {// SpcPeImageData ::= SEQUENCE {// flags SpcPeImageFlags DEFAULT { includeResources },// file SpcLink// } --#public--//// flags SpcPeImageFlags DEFAULT { includeResources },//// SpcPeImageFlags ::= BIT STRING {// includeResources (0),// includeDebugInfo (1),// includeImportAddressTable (2)// }CBS flags;if (!CBS_get_asn1(cbs, &flags, CBS_ASN1_BITSTRING)) {LOG(ERROR) << "Failed to parse flags";return false;}if (CBS_len(&flags) > 1) {const uint8_t* flag_bytes = CBS_data(&flags) + 1;// includeResources is bit 0.if (flag_bytes[0] & 0x80) {LOG(INFO) << "Flag: includeResources";}// includeDebugInfo is bit 1.if (flag_bytes[0] & 0x40) {LOG(INFO) << "Flag: includeDebugInfo";}// includeImportAddressTable is bit 2.if (flag_bytes[0] & 0x20) {LOG(INFO) << "Flag: includeImportAddressTable";}} else {LOG(INFO) << "Flags: default (includeResources)";}// file SpcLink// SpcLink ::= CHOICE {// url [0] IMPLICIT IA5STRING,// moniker [1] IMPLICIT SpcSerializedObject,// file [2] EXPLICIT SpcString// } --#public--CBS spc_link;if (!CBS_get_asn1(cbs, &spc_link,CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0)) {LOG(ERROR) << "Failed to parse SpcLink wrapper";return false;}if (CBS_peek_asn1_tag(&spc_link, CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0)) { // urlCBS child;CBS url;if (!CBS_get_asn1(&spc_link, &child,CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) ||!CBS_get_asn1(&child, &url, CBS_ASN1_IA5STRING)) {LOG(ERROR) << "Failed to parse url";return false;} else {LOG(INFO) << "SpcLink (url): "<< std::string(reinterpret_cast<const char*>(CBS_data(&url)),CBS_len(&url));}} else if (CBS_peek_asn1_tag(&spc_link, CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 1)) {CBS child;CBS moniker;if (!CBS_get_asn1(&spc_link, &child,CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 1) ||!CBS_get_asn1(&child, &moniker, CBS_ASN1_SEQUENCE)) {LOG(ERROR) << "Failed to parse url";return false;} else {LOG(INFO) << "SpcLink (moniker): // TODO: implement";}} else if (CBS_peek_asn1_tag(&spc_link, CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 2)) {CBS file;if (!CBS_get_asn1(&spc_link, &file,CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 2)) {LOG(ERROR) << "Failed to parse url";return false;} else {Parse_SpcString(&file);}} else {LOG(ERROR) << "Failed to parse SpcLink choice";return false;}if (CBS_len(cbs) != 0) {LOG(ERROR) << "Trailing data in SpcLink";return false;}return true;
}// DigestInfo ::= SEQUENCE {
// digestAlgorithm AlgorithmIdentifier,
// digest OCTET STRING
// }
//
// AlgorithmIdentifier ::= SEQUENCE {
// algorithm OBJECT IDENTIFIER,
// parameters ANY DEFINED BY algorithm OPTIONAL
// }
bool Parse_DigestInfo(CBS* cbs) {const EVP_MD* md = EVP_parse_digest_algorithm(cbs);if (md != EVP_sha256() && md != EVP_sha1()) {LOG(ERROR) << "Failed to parse message digest algorithm";return false;}CBS digest;if (!CBS_get_asn1(cbs, &digest, CBS_ASN1_OCTETSTRING)) {LOG(ERROR) << "Failed to parse digest";return false;}if (CBS_len(cbs) != 0) {LOG(ERROR) << "Trailing data in message digest";return false;}return true;
}
// IndirectDataContent ::= SEQUENCE {
// data ContentInfo,
// messageDigest DigestInfo
// }
bool Parse_Authenticode_SpcIndirectDataContent(CBS* cbs) {// SpcAttributeTypeAndOptionalValue ::= SEQUENCE {// type ObjectID,// value [0] EXPLICIT ANY OPTIONAL// }CBS data;if (!CBS_get_asn1(cbs, &data, CBS_ASN1_SEQUENCE)) {LOG(ERROR) << "Failed to parse data";return false;}CBS data_oid;if (!CBS_get_asn1(&data, &data_oid, CBS_ASN1_OBJECT)) {LOG(ERROR) << "Failed to parse data content type";return false;}if (CBS_mem_equal(&data_oid, kOidSpcPEImageData,sizeof(kOidSpcPEImageData))) {CBS pe_image_data;if (!CBS_get_asn1(&data, &pe_image_data, CBS_ASN1_SEQUENCE)) {LOG(ERROR) << "Failed to parse SpcPeImageData wrapper";return false;}if (!Parse_Authenticode_SpcPeImageData(&pe_image_data)) {LOG(ERROR) << "Failed to parse SpcPeImageData";return false;}} else {LOG(ERROR) << "Unknown data content type";return false;}CBS message_digest;if (!CBS_get_asn1(cbs, &message_digest, CBS_ASN1_SEQUENCE)) {LOG(ERROR) << "Failed to parse message digest";return false;}if (!Parse_DigestInfo(&message_digest)) {LOG(ERROR) << "Failed to parse message digest";return false;}return true;
}bool Parse_Authenticode_SignedData(CBS* cbs) {CBS in = *cbs;uint64_t version_number;if (!CBS_get_asn1_uint64(&in, &version_number) || version_number != 1) {LOG(ERROR) << "Failed to parse version";return false;}CBS digest_algorithms;if (!CBS_get_asn1(&in, &digest_algorithms, CBS_ASN1_SET)) {LOG(ERROR) << "Failed to parse digest algorithms";return false;}// DigestAlgorithmIdentifiers SET OF DigestAlgorithmIdentifier// DigestAlgorithmIdentifier ::= SEQUENCE {// algorithm OBJECT IDENTIFIER,// parameters ANY DEFINED BY algorithm OPTIONAL// }while (CBS_len(&digest_algorithms) > 0) {const EVP_MD* md = EVP_parse_digest_algorithm(&digest_algorithms);if (md != EVP_sha256() && md != EVP_sha1()) {LOG(ERROR) << "Failed to parse digest algorithm";return false;}}// EncapsulatedContentInfo ::= SEQUENCE {// eContentType ContentType,// eContent [0] EXPLICIT OCTET STRING OPTIONAL }CBS content_info;if (!CBS_get_asn1(&in, &content_info, CBS_ASN1_SEQUENCE)) {LOG(ERROR) << "Failed to parse content info";return false;}CBS content_type;if (!CBS_get_asn1(&content_info, &content_type, CBS_ASN1_OBJECT)) {LOG(ERROR) << "Failed to parse content type";return false;}if (!CBS_mem_equal(&content_type, kOidSpcIndirectDataContent,sizeof(kOidSpcIndirectDataContent))) {LOG(ERROR) << "Unknown content type";return false;}CBS wrapped_indirect_data_content, indirect_data_content;if (!CBS_get_asn1(&content_info, &wrapped_indirect_data_content,CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) ||!CBS_get_asn1(&wrapped_indirect_data_content, &indirect_data_content,CBS_ASN1_SEQUENCE)) {LOG(ERROR) << "Failed to parse wrapped content";return false;}Parse_Authenticode_SpcIndirectDataContent(&indirect_data_content);return true;
}bool Parse_Authenticode_ContentInfo(base::span<const uint8_t> data) {CBS cbs;CBS_init(&cbs, data.data(), data.size());CBS content_info;CBS content_type;if (!CBS_get_asn1(&cbs, &content_info, CBS_ASN1_SEQUENCE) ||!CBS_get_asn1(&content_info, &content_type, CBS_ASN1_OBJECT)) {LOG(ERROR) << "Failed to parse PKCS#7 sequence";return false;}if (!CBS_mem_equal(&content_type, kOidPKCS7SignedData,sizeof(kOidPKCS7SignedData))) {LOG(ERROR) << "Not PKCS7SignedData";return false;}CBS wrapped_signed_data, signed_data;// See https://tools.ietf.org/html/rfc2315#section-9.1if (!CBS_get_asn1(&content_info, &wrapped_signed_data,CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) ||!CBS_get_asn1(&wrapped_signed_data, &signed_data, CBS_ASN1_SEQUENCE)) {LOG(ERROR) << "";return false;}if (!Parse_Authenticode_SignedData(&signed_data)) {LOG(ERROR) << "Failed to parse SignedData";return false;}return true;
}int main(int argc, char** argv) {base::CommandLine::Init(0, nullptr);base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();base::FilePath input = command_line->GetSwitchValuePath(switches::kInput);base::File pe_file(input, base::File::FLAG_OPEN | base::File::FLAG_READ);DWORD cert_count;if (!ImageEnumerateCertificates(pe_file.GetPlatformFile(),CERT_SECTION_TYPE_ANY, &cert_count, nullptr,0)) {PLOG(ERROR) << "ImageEnumerateCertificates";return -1;}std::vector<DWORD> cert_indices(cert_count);if (!ImageEnumerateCertificates(pe_file.GetPlatformFile(),CERT_SECTION_TYPE_ANY, &cert_count,&cert_indices[0], cert_indices.size())) {PLOG(ERROR) << "ImageEnumerateCertificates";}CHECK(cert_indices.size() == cert_count);for (auto idx : cert_indices) {WIN_CERTIFICATE cert = {};if (!ImageGetCertificateHeader(pe_file.GetPlatformFile(), idx, &cert)) {PLOG(ERROR) << "ImageGetCertificateHeader";return -1;}DWORD cert_length = sizeof(WIN_CERTIFICATE) + cert.dwLength;auto cert_data = base::HeapArray<uint8_t>::WithSize(cert_length);if (!ImageGetCertificateData(pe_file.GetPlatformFile(), idx,reinterpret_cast<WIN_CERTIFICATE*>(cert_data.data()),&cert_length)) {PLOG(ERROR) << "ImageGetCertificateData";return -1;}base::WriteFile(input.AddExtensionASCII(base::StringPrintf("%d.der", idx)),cert_data.subspan(offsetof(WIN_CERTIFICATE, bCertificate)));Parse_Authenticode_ContentInfo(cert_data.subspan(offsetof(WIN_CERTIFICATE, bCertificate)));}return 0;
}