using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.Pkcs;

namespace AppControlManager;

// https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-oshared/91755632-4b0d-44ca-89a9-9699afbbd268
// Rust implementation: https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/Security/WinTrust/struct.SPC_SP_OPUS_INFO.html
public struct OpusInfoObj
{
	// Declaring a public field CertOemID of type string with LPWStr marshaling
	[MarshalAs(UnmanagedType.LPWStr)]
	public string CertOemID;
	public IntPtr PublisherInfo;
	public IntPtr MoreInfo; // not always present
}

public static partial class Opus
{
	internal static partial class Crypt32
	{

		// More info: https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptdecodeobject
		[LibraryImport("crypt32.dll", StringMarshalling = StringMarshalling.Utf16, SetLastError = true)]
		[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
		[return: MarshalAs(UnmanagedType.Bool)]
		internal static partial bool CryptDecodeObject(
			uint dwCertEncodingType,        // Specifies the encoding type used in the encoded message
			IntPtr lpszStructType,          // Pointer to a null-terminated ANSI string that identifies the type of the structure to be decoded
			[In] byte[] pbEncoded,           // Pointer to a buffer that contains the encoded structure
			uint cbEncoded,                 // Size, in bytes, of the pbEncoded buffer
			uint dwFlags,                   // Flags that modify the behavior of the function
			IntPtr pvStructInto,            // Pointer to a buffer that receives the decoded structure
			ref uint pcbStructInfo          // Pointer to a variable that specifies the size, in bytes, of the pvStructInfo buffer
		);
	}

	// More info about this at the end of the code
	public const uint SPC_SP_OPUS_INFO_STRUCT = 2007;
	// for the SpcSpOpusInfo structure
	public const string SPC_SP_OPUS_INFO_OBJID = "1.3.6.1.4.1.311.2.1.12";

	// Declaring a public static method GetOpusData that returns a List of OpusInfoObj, taking a SignedCms parameter
	// https://learn.microsoft.com/en-us/windows/win32/seccrypto/example-c-program--verifying-the-signature-of-a-pe-file
	// https://view.officeapps.live.com/op/view.aspx?src=https%3A%2F%2Fdownload.microsoft.com%2Fdownload%2F9%2Fc%2F5%2F9c5b2167-8017-4bae-9fde-d599bac8184a%2FAuthenticode_PE.docx
	internal static List<OpusInfoObj> GetOpusData(SignedCms signature)
	{
		// Initializing a new List of OpusInfoObj to store the output data to return
		List<OpusInfoObj> OEMOpusData = [];

		// Iterating through each SignerInfo in the SignerInfos collection of the signature
		foreach (SignerInfo signerInfo in signature.SignerInfos)
		{
			// Iterating through each CryptographicAttributeObject in the SignedAttributes collection of the signerInfo
			foreach (CryptographicAttributeObject signedAttribute in signerInfo.SignedAttributes)
			{
				// Checking if the OID value of the signed attribute matches the Opus SPC_SP_OPUS_INFO_OBJID
				if (string.Equals(signedAttribute.Oid.Value, SPC_SP_OPUS_INFO_OBJID, StringComparison.OrdinalIgnoreCase))
				{
					// Initializing pcbStructInfo to 0
					uint pcbStructInfo = 0;
					// Initializing decodedDataPtr to IntPtr.Zero
					IntPtr decodedDataPtr = IntPtr.Zero;

					try
					{
						AsnEncodedData asnEncodedData = signedAttribute.Values[0];  // Retrieving the first value from the signed attribute's Values collection

						// Decoding ASN.1-encoded data using CryptDecodeObject
						if (!Crypt32.CryptDecodeObject(65537U, (IntPtr)(long)SPC_SP_OPUS_INFO_STRUCT, asnEncodedData.RawData, (uint)asnEncodedData.RawData.Length, 0U, IntPtr.Zero, ref pcbStructInfo))
						{
							// If CryptDecodeObject fails, ignore
						}
						else
						{
							// Allocating unmanaged memory to decodedDataPtr based on pcbStructInfo size
							decodedDataPtr = Marshal.AllocCoTaskMem((int)pcbStructInfo);

							// Decoding ASN.1-encoded data again into decodedDataPtr
							if (!Crypt32.CryptDecodeObject(65537U, (IntPtr)(long)SPC_SP_OPUS_INFO_STRUCT, asnEncodedData.RawData, (uint)asnEncodedData.RawData.Length, 0U, decodedDataPtr, ref pcbStructInfo))
							{
								// If CryptDecodeObject fails, ignore
							}
							else
							{
								// Converting the unmanaged memory block to OpusInfoObj structure
								OpusInfoObj structure = Marshal.PtrToStructure<OpusInfoObj>(decodedDataPtr)!;
								// Adding the structure to OEMOpusData list
								OEMOpusData.Add(structure);
							}
						}
					}
					finally
					{
						// Freeing the allocated unmanaged memory (decodedDataPtr)
						Marshal.FreeCoTaskMem(decodedDataPtr);
					}
				}
			}
		}
		return OEMOpusData;
	}
}

// Constants

// WINTRUST_MAX_HEADER_BYTES_TO_MAP_DEFAULT = $00A00000
// WINTRUST_MAX_HASH_BYTES_TO_MAP_DEFAULT = $00100000
// WTD_UI_ALL = 1
// WTD_UI_NONE = 2
// WTD_UI_NOBAD = 3
// WTD_UI_NOGOOD = 4
// WTD_REVOKE_NONE = $00000000
// WTD_REVOKE_WHOLECHAIN = $00000001
// WTD_CHOICE_FILE = 1
// WTD_CHOICE_CATALOG = 2
// WTD_CHOICE_BLOB = 3
// WTD_CHOICE_SIGNER = 4
// WTD_CHOICE_CERT = 5
// WTD_STATEACTION_IGNORE = $00000000
// WTD_STATEACTION_VERIFY = $00000001
// WTD_STATEACTION_CLOSE = $00000002
// WTD_STATEACTION_AUTO_CACHE = $00000003
// WTD_STATEACTION_AUTO_CACHE_FLUSH = $00000004
// WTD_PROV_FLAGS_MASK = $0000FFFF
// WTD_USE_IE4_TRUST_FLAG = $00000001
// WTD_NO_IE4_CHAIN_FLAG = $00000002
// WTD_NO_POLICY_USAGE_FLAG = $00000004
// WTD_REVOCATION_CHECK_NONE = $00000010
// WTD_REVOCATION_CHECK_END_CERT = $00000020
// WTD_REVOCATION_CHECK_CHAIN = $00000040
// WTD_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT = $00000080
// WTD_SAFER_FLAG = $00000100
// WTD_HASH_ONLY_FLAG = $00000200
// WTD_USE_DEFAULT_OSVER_CHECK = $00000400
// WTD_LIFETIME_SIGNING_FLAG = $00000800
// WTD_CACHE_ONLY_URL_RETRIEVAL = $00001000
// WTD_UICONTEXT_EXECUTE = 0
// WTD_UICONTEXT_INSTALL = 1
// WTCI_DONT_OPEN_STORES = $00000001
// WTCI_OPEN_ONLY_ROOT = $00000002
// WTCI_USE_LOCAL_MACHINE = $00000004
// WTPF_TRUSTTEST = $00000020
// WTPF_TESTCANBEVALID = $00000080
// WTPF_IGNOREEXPIRATION = $00000100
// WTPF_IGNOREREVOKATION = $00000200
// WTPF_OFFLINEOK_IND = $00000400
// WTPF_OFFLINEOK_COM = $00000800
// WTPF_OFFLINEOKNBU_IND = $00001000
// WTPF_OFFLINEOKNBU_COM = $00002000
// WTPF_VERIFY_V1_OFF = $00010000
// WTPF_IGNOREREVOCATIONONTS = $00020000
// WTPF_ALLOWONLYPERTRUST = $00040000
// TRUSTERROR_STEP_WVTPARAMS = 0
// TRUSTERROR_STEP_FILEIO = 2
// TRUSTERROR_STEP_SIP = 3
// TRUSTERROR_STEP_SIPSUBJINFO = 5
// TRUSTERROR_STEP_CATALOGFILE = 6
// TRUSTERROR_STEP_CERTSTORE = 7
// TRUSTERROR_STEP_MESSAGE = 8
// TRUSTERROR_STEP_MSG_SIGNERCOUNT = 9
// TRUSTERROR_STEP_MSG_INNERCNTTYPE = 10
// TRUSTERROR_STEP_MSG_INNERCNT = 11
// TRUSTERROR_STEP_MSG_STORE = 12
// TRUSTERROR_STEP_MSG_SIGNERINFO = 13
// TRUSTERROR_STEP_MSG_SIGNERCERT = 14
// TRUSTERROR_STEP_MSG_CERTCHAIN = 15
// TRUSTERROR_STEP_MSG_COUNTERSIGINFO = 16
// TRUSTERROR_STEP_MSG_COUNTERSIGCERT = 17
// TRUSTERROR_STEP_VERIFY_MSGHASH = 18
// TRUSTERROR_STEP_VERIFY_MSGINDIRECTDATA = 19
// TRUSTERROR_STEP_FINAL_WVTINIT = 30
// TRUSTERROR_STEP_FINAL_INITPROV = 31
// TRUSTERROR_STEP_FINAL_OBJPROV = 32
// TRUSTERROR_STEP_FINAL_SIGPROV = 33
// TRUSTERROR_STEP_FINAL_CERTPROV = 34
// TRUSTERROR_STEP_FINAL_CERTCHKPROV = 35
// TRUSTERROR_STEP_FINAL_POLICYPROV = 36
// TRUSTERROR_STEP_FINAL_UIPROV = 37
// TRUSTERROR_MAX_STEPS = 38
// CPD_USE_NT5_CHAIN_FLAG = $80000000
// CPD_REVOCATION_CHECK_NONE = $00010000
// CPD_REVOCATION_CHECK_END_CERT = $00020000
// CPD_REVOCATION_CHECK_CHAIN = $00040000
// CPD_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT = $00080000
// CPD_UISTATE_MODE_PROMPT = $00000000
// CPD_UISTATE_MODE_BLOCK = $00000001
// CPD_UISTATE_MODE_ALLOW = $00000002
// CPD_UISTATE_MODE_MASK = $00000003
// CERT_CONFIDENCE_SIG = $10000000
// CERT_CONFIDENCE_TIME = $01000000
// CERT_CONFIDENCE_TIMENEST = $00100000
// CERT_CONFIDENCE_AUTHIDEXT = $00010000
// CERT_CONFIDENCE_HYGIENE = $00001000
// CERT_CONFIDENCE_HIGHEST = $11111000
// DWACTION_ALLOCANDFILL = 1
// DWACTION_FREE = 2
// szOID_TRUSTED_CODESIGNING_CA_LIST = "1.3.6.1.4.1.311.2.2.1"
// szOID_TRUSTED_CLIENT_AUTH_CA_LIST = "1.3.6.1.4.1.311.2.2.2"
// szOID_TRUSTED_SERVER_AUTH_CA_LIST = "1.3.6.1.4.1.311.2.2.3"
// SPC_TIME_STAMP_REQUEST_OBJID = "1.3.6.1.4.1.311.3.2.1"
// SPC_INDIRECT_DATA_OBJID = "1.3.6.1.4.1.311.2.1.4"
// SPC_SP_AGENCY_INFO_OBJID = "1.3.6.1.4.1.311.2.1.10"
// SPC_STATEMENT_TYPE_OBJID = "1.3.6.1.4.1.311.2.1.11"
// SPC_SP_OPUS_INFO_OBJID = "1.3.6.1.4.1.311.2.1.12"
// SPC_CERT_EXTENSIONS_OBJID = "1.3.6.1.4.1.311.2.1.14"
// SPC_PE_IMAGE_DATA_OBJID = "1.3.6.1.4.1.311.2.1.15"
// SPC_RAW_FILE_DATA_OBJID = "1.3.6.1.4.1.311.2.1.18"
// SPC_STRUCTURED_STORAGE_DATA_OBJID = "1.3.6.1.4.1.311.2.1.19"
// SPC_JAVA_CLASS_DATA_OBJID = "1.3.6.1.4.1.311.2.1.20"
// SPC_INDIVIDUAL_SP_KEY_PURPOSE_OBJID = "1.3.6.1.4.1.311.2.1.21"
// SPC_COMMERCIAL_SP_KEY_PURPOSE_OBJID = "1.3.6.1.4.1.311.2.1.22"
// SPC_CAB_DATA_OBJID = "1.3.6.1.4.1.311.2.1.25"
// SPC_GLUE_RDN_OBJID = "1.3.6.1.4.1.311.2.1.25"
// SPC_MINIMAL_CRITERIA_OBJID = "1.3.6.1.4.1.311.2.1.26"
// SPC_FINANCIAL_CRITERIA_OBJID = "1.3.6.1.4.1.311.2.1.27"
// SPC_LINK_OBJID = "1.3.6.1.4.1.311.2.1.28"
// SPC_SIGINFO_OBJID = "1.3.6.1.4.1.311.2.1.30"
// SPC_PE_IMAGE_PAGE_HASHES_V1_OBJID = "1.3.6.1.4.1.311.2.3.1"
// SPC_PE_IMAGE_PAGE_HASHES_V2_OBJID = "1.3.6.1.4.1.311.2.3.2"
// CAT_NAMEVALUE_OBJID = "1.3.6.1.4.1.311.12.2.1"
// CAT_MEMBERINFO_OBJID = "1.3.6.1.4.1.311.12.2.2"
// SPC_SP_AGENCY_INFO_STRUCT =(2000)
// SPC_MINIMAL_CRITERIA_STRUCT =(2001)
// SPC_FINANCIAL_CRITERIA_STRUCT =(2002)
// SPC_INDIRECT_DATA_CONTENT_STRUCT =(2003)
// SPC_PE_IMAGE_DATA_STRUCT =(2004)
// SPC_LINK_STRUCT =(2005)
// SPC_STATEMENT_TYPE_STRUCT =(2006)
// SPC_SP_OPUS_INFO_STRUCT =(2007)
// SPC_CAB_DATA_STRUCT =(2008)
// SPC_JAVA_CLASS_DATA_STRUCT =(2009)
// SPC_SIGINFO_STRUCT =(2130)
// CAT_NAMEVALUE_STRUCT =(2221)
// CAT_MEMBERINFO_STRUCT =(2222)
// SPC_UUID_LENGTH = 16
// WIN_CERT_REVISION_1_0 =($0100)
// WIN_CERT_REVISION_2_0 =($0200)
// WIN_CERT_TYPE_X509 =($0001)
// WIN_CERT_TYPE_PKCS_SIGNED_DATA =($0002)
// WIN_CERT_TYPE_RESERVED_1 =($0003)
// WIN_CERT_TYPE_TS_STACK_SIGNED =($0004)
// WT_TRUSTDBDIALOG_NO_UI_FLAG = $00000001
// WT_TRUSTDBDIALOG_ONLY_PUB_TAB_FLAG = $00000002
// WT_TRUSTDBDIALOG_WRITE_LEGACY_REG_FLAG = $00000100
// WT_TRUSTDBDIALOG_WRITE_IEAK_STORE_FLAG = $00000200
