#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.4
constexpr uint8_t kOidSpcPEImageData[] = {
0x2b, 0x06, 0x01, 0x04, 0x01,
0x82, 0x37, 0x02, 0x01, 0x0F}; // 1.3.6.1.4.1.311.2.1.15
constexpr 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)) { // unicode
CBS 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)) { // ascii
CBS 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)) { // url
CBS 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.1
if (!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;
}