From c8862857f099ec120b346a9cc924a2f9fb4e5125 Mon Sep 17 00:00:00 2001 From: Victor Irzak Date: Fri, 11 Sep 2026 18:14:39 -0400 Subject: [PATCH] Replace hand-written interop with CsWin32 Generate the crypt32 and mssign32 P/Invokes with CsWin32 instead of declaring them by hand. Interop/crypt32.cs is removed. mssign32.cs keeps only SIGNER_SIGN_EX3_PARAMS, APPX_SIP_CLIENT_DATA and the managed sign callback delegate, which the Win32 metadata lacks. - Call the raw SignerSignEx3 extern and pin the ASCII timestamp OID once, so the same pointer reaches both the call and the Appx SIP parameters. SIGNER_SIGN_EX3_PARAMS must stay blittable: a managed field changes its in-memory layout and breaks every Appx/MSIX signature. - Use the generated V2 SIGNER_DIGEST_SIGN_INFO with cbSize = sizeof and dwDigestSignChoice = SIGNER_DIGEST_SIGN, which is also correct on x86. - Keep an HCERTSTORE in MemoryCertificateStore and suppress CA1416 there, since the library is Windows-only. - Add MSIX signing tests so the Appx SIP path has coverage. Generated with Claude Code --- Directory.Packages.props | 1 + .../AuthenticodeKeyVaultSigner.cs | 157 +++++++----- src/AzureSign.Core/AzureSign.Core.csproj | 1 + src/AzureSign.Core/Interop/crypt32.cs | 81 ------ src/AzureSign.Core/Interop/mssign32.cs | 234 +----------------- src/AzureSign.Core/MemoryCertificateStore.cs | 26 +- src/AzureSign.Core/NativeMethods.txt | 14 ++ .../AuthenticodeKeyVaultSignerTests.cs | 43 +++- .../AzureSign.Core.Tests.csproj | 3 + .../MemoryCertificateStoreTests.cs | 2 +- test/AzureSign.Core.Tests/signtarget.msix | Bin 0 -> 4436 bytes 11 files changed, 184 insertions(+), 378 deletions(-) delete mode 100644 src/AzureSign.Core/Interop/crypt32.cs create mode 100644 src/AzureSign.Core/NativeMethods.txt create mode 100644 test/AzureSign.Core.Tests/signtarget.msix diff --git a/Directory.Packages.props b/Directory.Packages.props index 80661ee..cc0f642 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,6 +7,7 @@ + diff --git a/src/AzureSign.Core/AuthenticodeKeyVaultSigner.cs b/src/AzureSign.Core/AuthenticodeKeyVaultSigner.cs index 7b2626e..418d946 100644 --- a/src/AzureSign.Core/AuthenticodeKeyVaultSigner.cs +++ b/src/AzureSign.Core/AuthenticodeKeyVaultSigner.cs @@ -1,3 +1,5 @@ +using static Windows.Win32.PInvoke; +using Windows.Win32.Foundation; using AzureSign.Core.Interop; using Microsoft.Extensions.Logging; using System; @@ -5,6 +7,7 @@ using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; +using Windows.Win32.Security.Cryptography; namespace AzureSign.Core { @@ -87,15 +90,15 @@ static char[] NullTerminate(ReadOnlySpan str) return result; } - SignerSignEx3Flags flags = SignerSignEx3Flags.SIGN_CALLBACK_UNDOCUMENTED; + SIGNER_SIGN_FLAGS flags = SIGNER_SIGN_FLAGS.SPC_DIGEST_SIGN_FLAG; if (pageHashing == true) { - flags |= SignerSignEx3Flags.SPC_INC_PE_PAGE_HASHES_FLAG; + flags |= SIGNER_SIGN_FLAGS.SPC_INC_PE_PAGE_HASHES_FLAG; } else if (pageHashing == false) { - flags |= SignerSignEx3Flags.SPC_EXC_PE_PAGE_HASHES_FLAG; + flags |= SIGNER_SIGN_FLAGS.SPC_EXC_PE_PAGE_HASHES_FLAG; } if (appendSignature) @@ -106,28 +109,26 @@ static char[] NullTerminate(ReadOnlySpan str) logger?.LogWarning("If you set the dwTimestampFlags parameter to SIGNER_TIMESTAMP_AUTHENTICODE, you cannot set the dwFlags parameter to SIG_APPEND."); } - flags |= SignerSignEx3Flags.SIG_APPEND; + flags |= SIGNER_SIGN_FLAGS.SIG_APPEND; } - SignerSignTimeStampFlags timeStampFlags; - ReadOnlySpan timestampAlgorithmOid; + SIGNER_TIMESTAMP_FLAGS timeStampFlags; + ReadOnlySpan timestampAlgorithmOid = default; string? timestampUrl; switch (_timeStampConfiguration.Type) { case TimeStampType.Authenticode: - timeStampFlags = SignerSignTimeStampFlags.SIGNER_TIMESTAMP_AUTHENTICODE; - timestampAlgorithmOid = default; + timeStampFlags = SIGNER_TIMESTAMP_FLAGS.SIGNER_TIMESTAMP_AUTHENTICODE; timestampUrl = _timeStampConfiguration.Url; break; case TimeStampType.RFC3161: - timeStampFlags = SignerSignTimeStampFlags.SIGNER_TIMESTAMP_RFC3161; + timeStampFlags = SIGNER_TIMESTAMP_FLAGS.SIGNER_TIMESTAMP_RFC3161; timestampAlgorithmOid = AlgorithmTranslator.HashAlgorithmToOidAsciiTerminated(_timeStampConfiguration.DigestAlgorithm); timestampUrl = _timeStampConfiguration.Url; break; default: timeStampFlags = default; - timestampAlgorithmOid = default; timestampUrl = null; break; } @@ -138,34 +139,79 @@ static char[] NullTerminate(ReadOnlySpan str) fixed (char* pDescription = NullTerminate(description)) fixed (char* pDescriptionUrl = NullTerminate(descriptionUrl)) { - var fileInfo = new SIGNER_FILE_INFO(pPath, default); + var fileInfo = new SIGNER_FILE_INFO() + { + cbSize = (uint)sizeof(SIGNER_FILE_INFO), + hFile = default, + pwszFileName = pPath, + }; var subjectIndex = 0u; - var signerSubjectInfoUnion = new SIGNER_SUBJECT_INFO_UNION(&fileInfo); - var subjectInfo = new SIGNER_SUBJECT_INFO(&subjectIndex, SignerSubjectInfoUnionChoice.SIGNER_SUBJECT_FILE, signerSubjectInfoUnion); - var authCodeStructure = new SIGNER_ATTR_AUTHCODE(pDescription, pDescriptionUrl); - var storeInfo = new SIGNER_CERT_STORE_INFO( - dwCertPolicy: SignerCertStoreInfoFlags.SIGNER_CERT_POLICY_CHAIN, - hCertStore: _certificateStore.Handle, - pSigningCert: _signingCertificate.Handle - ); - var signerCert = new SIGNER_CERT( - dwCertChoice: SignerCertChoice.SIGNER_CERT_STORE, - union: new SIGNER_CERT_UNION(&storeInfo) - ); - var signatureInfo = new SIGNER_SIGNATURE_INFO( - algidHash: AlgorithmTranslator.HashAlgorithmToAlgId(_fileDigestAlgorithm), - psAuthenticated: IntPtr.Zero, - psUnauthenticated: IntPtr.Zero, - dwAttrChoice: SignerSignatureInfoAttrChoice.SIGNER_AUTHCODE_ATTR, - attrAuthUnion: new SIGNER_SIGNATURE_INFO_UNION(&authCodeStructure) - ); + var subjectInfo = new SIGNER_SUBJECT_INFO + { + cbSize = (uint)sizeof(SIGNER_SUBJECT_INFO), + pdwIndex = &subjectIndex, + dwSubjectChoice = SIGNER_SUBJECT_CHOICE.SIGNER_SUBJECT_FILE, + Anonymous = new() { pSignerFileInfo = &fileInfo } + }; + var authCodeStructure = new SIGNER_ATTR_AUTHCODE + { + cbSize = (uint)sizeof(SIGNER_ATTR_AUTHCODE), + fCommercial = false, + fIndividual = false, + pwszName = pDescription, + pwszInfo = pDescriptionUrl, + + }; + + var storeInfo = new SIGNER_CERT_STORE_INFO + { + cbSize = (uint)sizeof(SIGNER_CERT_STORE_INFO), + dwCertPolicy = SIGNER_CERT_POLICY.SIGNER_CERT_POLICY_CHAIN, + hCertStore = _certificateStore.Handle, + pSigningCert = (CERT_CONTEXT*)_signingCertificate.Handle + }; + + var signerCert = new SIGNER_CERT() + { + cbSize = (uint)sizeof(SIGNER_CERT), + dwCertChoice = SIGNER_CERT_CHOICE.SIGNER_CERT_STORE, + Anonymous = new() { pCertStoreInfo = &storeInfo }, + }; + + var signatureInfo = new SIGNER_SIGNATURE_INFO + { + cbSize = (uint)sizeof(SIGNER_SIGNATURE_INFO), + algidHash = (ALG_ID)AlgorithmTranslator.HashAlgorithmToAlgId(_fileDigestAlgorithm), + psAuthenticated = null, + psUnauthenticated = null, + dwAttrChoice = SIGNER_SIGNATURE_ATTRIBUTE_CHOICE.SIGNER_AUTHCODE_ATTR, + Anonymous = new() { pAttrAuthcode = &authCodeStructure } + }; + var callbackPtr = Marshal.GetFunctionPointerForDelegate(_signCallback); - var signCallbackInfo = new SIGN_INFO(callbackPtr); + var signCallbackInfo = new SIGNER_DIGEST_SIGN_INFO + { + cbSize = (uint)sizeof(SIGNER_DIGEST_SIGN_INFO), + // SIGNER_DIGEST_SIGN from mssign32.h; the Win32 metadata does not define it. + dwDigestSignChoice = 1, + Anonymous = new() + { + pfnAuthenticodeDigestSign = + (delegate* unmanaged[Stdcall]< + CERT_CONTEXT*, + CRYPT_INTEGER_BLOB*, + ALG_ID, + byte*, + uint, + CRYPT_INTEGER_BLOB*, HRESULT>) + callbackPtr + }, + }; logger?.LogTrace("Getting SIP Data"); var sipKind = SipExtensionFactory.GetSipKind(path); void* sipData = (void*)0; - IntPtr context = IntPtr.Zero; + SIGNER_CONTEXT* pContext = null; switch (sipKind) { @@ -174,35 +220,35 @@ static char[] NullTerminate(ReadOnlySpan str) SIGNER_SIGN_EX3_PARAMS parameters; clientData.pSignerParams = ¶meters; sipData = &clientData; - flags &= ~SignerSignEx3Flags.SPC_INC_PE_PAGE_HASHES_FLAG; - flags |= SignerSignEx3Flags.SPC_EXC_PE_PAGE_HASHES_FLAG; - FillAppxExtension(ref clientData, flags, timeStampFlags, &subjectInfo, &signerCert, &signatureInfo, &context, pTimestampUrl, pTimestampAlgorithm, &signCallbackInfo); + flags &= ~SIGNER_SIGN_FLAGS.SPC_INC_PE_PAGE_HASHES_FLAG; + flags |= SIGNER_SIGN_FLAGS.SPC_EXC_PE_PAGE_HASHES_FLAG; + FillAppxExtension(ref clientData, flags, timeStampFlags, &subjectInfo, &signerCert, &signatureInfo, &pContext, pTimestampUrl, pTimestampAlgorithm, &signCallbackInfo); break; } logger?.LogTrace($"Calling SignerSignEx3 with flags: {flags}"); - var result = mssign32.SignerSignEx3 + var result = SignerSignEx3 ( flags, &subjectInfo, &signerCert, &signatureInfo, - IntPtr.Zero, + null, timeStampFlags, - pTimestampAlgorithm, - pTimestampUrl, - IntPtr.Zero, + new PCSTR(pTimestampAlgorithm), + new PCWSTR(pTimestampUrl), + null, sipData, - &context, - IntPtr.Zero, - &signCallbackInfo, - IntPtr.Zero + &pContext, + null, + new[] { signCallbackInfo }, + null ); - if (result == 0 && context != IntPtr.Zero) + if (result == HRESULT.S_OK && pContext != null) { - Debug.Assert(mssign32.SignerFreeSignerContext(context) == 0); + Debug.Assert(SignerFreeSignerContext(pContext) == HRESULT.S_OK); } - if (result == 0 && sipKind == SipKind.Appx) + if (result == HRESULT.S_OK && sipKind == SipKind.Appx) { var state = ((APPX_SIP_CLIENT_DATA*)sipData)->pAppxSipState; if (state != IntPtr.Zero) @@ -229,10 +275,9 @@ private unsafe int SignCallback( uint algId, byte[] pDigestToSign, uint dwDigestToSign, - ref CRYPTOAPI_BLOB blob + ref CRYPT_INTEGER_BLOB blob ) { - const int E_INVALIDARG = unchecked((int)0x80070057); byte[] digest; switch (_signingAlgorithm) { @@ -243,26 +288,26 @@ ref CRYPTOAPI_BLOB blob digest = ecdsa.SignHash(pDigestToSign); break; default: - return E_INVALIDARG; + return HRESULT.E_INVALIDARG; } var resultPtr = Marshal.AllocHGlobal(digest.Length); Marshal.Copy(digest, 0, resultPtr, digest.Length); - blob.pbData = resultPtr; + blob.pbData = (byte*)resultPtr; blob.cbData = (uint)digest.Length; - return 0; + return HRESULT.S_OK; } private static unsafe void FillAppxExtension( ref APPX_SIP_CLIENT_DATA clientData, - SignerSignEx3Flags flags, - SignerSignTimeStampFlags timestampFlags, + SIGNER_SIGN_FLAGS flags, + SIGNER_TIMESTAMP_FLAGS timestampFlags, SIGNER_SUBJECT_INFO* signerSubjectInfo, SIGNER_CERT* signerCert, SIGNER_SIGNATURE_INFO* signatureInfo, - IntPtr* signerContext, + SIGNER_CONTEXT** signerContext, char* timestampUrl, byte* timestampOid, - SIGN_INFO* signInfo + SIGNER_DIGEST_SIGN_INFO* signInfo ) { clientData.pSignerParams->dwFlags = flags; diff --git a/src/AzureSign.Core/AzureSign.Core.csproj b/src/AzureSign.Core/AzureSign.Core.csproj index a686c2a..da2ca14 100644 --- a/src/AzureSign.Core/AzureSign.Core.csproj +++ b/src/AzureSign.Core/AzureSign.Core.csproj @@ -15,6 +15,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/AzureSign.Core/Interop/crypt32.cs b/src/AzureSign.Core/Interop/crypt32.cs deleted file mode 100644 index 77c57c6..0000000 --- a/src/AzureSign.Core/Interop/crypt32.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace AzureSign.Core.Interop -{ - internal static class crypt32 - { - [return: MarshalAs(UnmanagedType.Bool)] - [method: DllImport(nameof(crypt32), CallingConvention = CallingConvention.Winapi, SetLastError = true)] - public static extern bool CertCloseStore - ( - [In, MarshalAs(UnmanagedType.SysInt)] IntPtr hCertStore, - [In, MarshalAs(UnmanagedType.U4)] CertCloreStoreFlags dwFlags - ); - - [return: MarshalAs(UnmanagedType.SysInt)] - [method: DllImport(nameof(crypt32), CallingConvention = CallingConvention.Winapi, SetLastError = true)] - public static extern IntPtr CertOpenStore - ( - [In, MarshalAs(UnmanagedType.LPStr)] string lpszStoreProvider, - [In, MarshalAs(UnmanagedType.U4)] CertEncodingType CertEncodingType, - [In, MarshalAs(UnmanagedType.SysInt)] IntPtr hCryptProv, - [In, MarshalAs(UnmanagedType.U4)] CertOpenStoreFlags dwFlags, - [In, MarshalAs(UnmanagedType.SysInt)] IntPtr pvPara - ); - } - - [type: StructLayout(LayoutKind.Sequential)] - internal struct SIGNER_CERT_STORE_INFO(IntPtr pSigningCert, SignerCertStoreInfoFlags dwCertPolicy, IntPtr hCertStore) - { - public uint cbSize = (uint)Marshal.SizeOf(); - public IntPtr pSigningCert = pSigningCert; - public SignerCertStoreInfoFlags dwCertPolicy = dwCertPolicy; - public IntPtr hCertStore = hCertStore; - } - - [type: Flags] - internal enum SignerCertStoreInfoFlags - { - - SIGNER_CERT_POLICY_CHAIN = 0x02, - SIGNER_CERT_POLICY_CHAIN_NO_ROOT = 0x08, - SIGNER_CERT_POLICY_STORE = 0x01 - } - - [type: Flags] - internal enum CertOpenStoreFlags : uint - { - NONE = 0, - CERT_STORE_NO_CRYPT_RELEASE_FLAG = 0x00000001, - CERT_STORE_SET_LOCALIZED_NAME_FLAG = 0x00000002, - CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG = 0x00000004, - CERT_STORE_DELETE_FLAG = 0x00000010, - CERT_STORE_UNSAFE_PHYSICAL_FLAG = 0x00000020, - CERT_STORE_SHARE_STORE_FLAG = 0x00000040, - CERT_STORE_SHARE_CONTEXT_FLAG = 0x00000080, - CERT_STORE_MANIFOLD_FLAG = 0x00000100, - CERT_STORE_ENUM_ARCHIVED_FLAG = 0x00000200, - CERT_STORE_UPDATE_KEYID_FLAG = 0x00000400, - CERT_STORE_BACKUP_RESTORE_FLAG = 0x00000800, - CERT_STORE_READONLY_FLAG = 0x00008000, - CERT_STORE_OPEN_EXISTING_FLAG = 0x00004000, - CERT_STORE_CREATE_NEW_FLAG = 0x00002000, - CERT_STORE_MAXIMUM_ALLOWED_FLAG = 0x00001000, - } - - [type: Flags] - internal enum CertCloreStoreFlags : uint - { - NONE = 0, - CERT_CLOSE_STORE_FORCE_FLAG = 0x00000001, - CERT_CLOSE_STORE_CHECK_FLAG = 0x00000002, - } - - internal enum CertEncodingType : uint - { - NONE = 0, - X509_ASN_ENCODING = 0x1, - PKCS_7_ASN_ENCODING = 0x10000 - } -} diff --git a/src/AzureSign.Core/Interop/mssign32.cs b/src/AzureSign.Core/Interop/mssign32.cs index 1ee30d7..4d62f76 100644 --- a/src/AzureSign.Core/Interop/mssign32.cs +++ b/src/AzureSign.Core/Interop/mssign32.cs @@ -1,244 +1,24 @@ using System; using System.Runtime.InteropServices; +using Windows.Win32.Security.Cryptography; namespace AzureSign.Core.Interop { - internal static class mssign32 - { - [method: DllImport(nameof(mssign32), EntryPoint = "SignerSignEx3", CallingConvention = CallingConvention.Winapi)] - public static unsafe extern int SignerSignEx3 - ( - [param: In, MarshalAs(UnmanagedType.U4)] SignerSignEx3Flags dwFlags, - [param: In] SIGNER_SUBJECT_INFO* pSubjectInfo, - [param: In] SIGNER_CERT* pSignerCert, - [param: In] SIGNER_SIGNATURE_INFO* pSignatureInfo, - [param: In] IntPtr pProviderInfo, - [param: In] SignerSignTimeStampFlags dwTimestampFlags, - [param: In] byte* pszTimestampAlgorithmOid, - [param: In] char* pwszHttpTimeStamp, - [param: In] IntPtr psRequest, - [param: In] void* pSipData, - [param: In] IntPtr* ppSignerContext, - [param: In] IntPtr pCryptoPolicy, - [param: In] SIGN_INFO* pSignInfo, - [param: In] IntPtr pReserved - ); - - [method: DllImport(nameof(mssign32), CallingConvention = CallingConvention.Winapi)] - public static extern int SignerFreeSignerContext( - [param: In] IntPtr pSignerContext - ); - } - - [type: StructLayout(LayoutKind.Sequential)] - internal struct SIGNER_SIGNATURE_INFO - { - public uint cbSize; - public uint algidHash; - public SignerSignatureInfoAttrChoice dwAttrChoice; - public SIGNER_SIGNATURE_INFO_UNION attrAuthUnion; - public IntPtr psAuthenticated; - public IntPtr psUnauthenticated; - - public SIGNER_SIGNATURE_INFO(uint algidHash, - SignerSignatureInfoAttrChoice dwAttrChoice, - SIGNER_SIGNATURE_INFO_UNION attrAuthUnion, - IntPtr psAuthenticated, - IntPtr psUnauthenticated - ) - { - cbSize = (uint)Marshal.SizeOf(); - this.algidHash = algidHash; - this.dwAttrChoice = dwAttrChoice; - this.attrAuthUnion = attrAuthUnion; - this.psAuthenticated = psAuthenticated; - this.psUnauthenticated = psUnauthenticated; - } - } - - [type: StructLayout(LayoutKind.Explicit)] - internal unsafe struct SIGNER_SIGNATURE_INFO_UNION - { - public SIGNER_SIGNATURE_INFO_UNION(SIGNER_ATTR_AUTHCODE* pAttrAuthcode) - { - this.pAttrAuthcode = pAttrAuthcode; - } - - [field: FieldOffset(0)] - public SIGNER_ATTR_AUTHCODE* pAttrAuthcode; - } - - internal enum SignerSignTimeStampFlags : uint - { - SIGNER_TIMESTAMP_AUTHENTICODE = 1, - SIGNER_TIMESTAMP_RFC3161 = 2, - } - - internal enum SignerSignatureInfoAttrChoice : uint - { - SIGNER_AUTHCODE_ATTR = 1, - SIGNER_NO_ATTR = 0 - } - - - [type: StructLayout(LayoutKind.Sequential)] - internal struct SIGNER_CERT - { - public uint cbSize; - public SignerCertChoice dwCertChoice; - public SIGNER_CERT_UNION union; - public IntPtr hwnd; - - public SIGNER_CERT(SignerCertChoice dwCertChoice, SIGNER_CERT_UNION union) - { - this.dwCertChoice = dwCertChoice; - this.union = union; - hwnd = default; - cbSize = (uint)Marshal.SizeOf(); - } - } - - [type: StructLayout(LayoutKind.Explicit)] - internal unsafe struct SIGNER_CERT_UNION - { - public SIGNER_CERT_UNION(SIGNER_CERT_STORE_INFO* certStoreInfo) - { - pSpcChainInfo = certStoreInfo; - } - - [field: FieldOffset(0)] - public SIGNER_CERT_STORE_INFO* pSpcChainInfo; - } - - internal enum SignerCertChoice : uint - { - SIGNER_CERT_SPC_FILE = 1, - SIGNER_CERT_STORE = 2, - SIGNER_CERT_SPC_CHAIN = 3 - } - - [type: Flags] - internal enum SignerSignEx3Flags : uint - { - NONE = 0x0, - SPC_EXC_PE_PAGE_HASHES_FLAG = 0x010, - SPC_INC_PE_IMPORT_ADDR_TABLE_FLAG = 0x020, - SPC_INC_PE_DEBUG_INFO_FLAG = 0x040, - SPC_INC_PE_RESOURCES_FLAG = 0x080, - SPC_INC_PE_PAGE_HASHES_FLAG = 0x100, - SIGN_CALLBACK_UNDOCUMENTED = 0X400, - SIG_APPEND = 0x1000 - } - - [type: StructLayout(LayoutKind.Sequential)] - internal unsafe struct SIGNER_SUBJECT_INFO - { - public uint cbSize; - public uint* pdwIndex; - public SignerSubjectInfoUnionChoice dwSubjectChoice; - public SIGNER_SUBJECT_INFO_UNION unionInfo; - - public SIGNER_SUBJECT_INFO(uint* pdwIndex, SignerSubjectInfoUnionChoice dwSubjectChoice, SIGNER_SUBJECT_INFO_UNION unionInfo) - { - cbSize = (uint)Marshal.SizeOf(); - this.pdwIndex = pdwIndex; - this.dwSubjectChoice = dwSubjectChoice; - this.unionInfo = unionInfo; - } - } - - [type: StructLayout(LayoutKind.Explicit)] - internal unsafe struct SIGNER_SUBJECT_INFO_UNION - { - [FieldOffset(0)] - public SIGNER_FILE_INFO* file; - - public SIGNER_SUBJECT_INFO_UNION(SIGNER_FILE_INFO* file) - { - this.file = file; - } - } - - [type: StructLayout(LayoutKind.Sequential)] - internal unsafe struct SIGNER_FILE_INFO - { - public uint cbSize; - public char* pwszFileName; - public IntPtr hFile; - - public SIGNER_FILE_INFO(char* pwszFileName, IntPtr hFile) - { - cbSize = (uint)Marshal.SizeOf(); - this.pwszFileName = pwszFileName; - this.hFile = hFile; - } - } - - internal enum SignerSubjectInfoUnionChoice : uint - { - SIGNER_SUBJECT_BLOB = 0x02, - SIGNER_SUBJECT_FILE = 0x01 - } - - [type: StructLayout(LayoutKind.Sequential)] - internal struct SIGN_INFO - { - public uint cbSize; - - public IntPtr callback; - - public IntPtr pvOpaque; - - public SIGN_INFO(IntPtr callback) - { - cbSize = (uint)Marshal.SizeOf(); - this.callback = callback; - pvOpaque = default; - } - } - - - [type: StructLayout(LayoutKind.Sequential)] - internal struct CRYPTOAPI_BLOB - { - public uint cbData; - public IntPtr pbData; - } - - [type: StructLayout(LayoutKind.Sequential)] - internal unsafe struct SIGNER_ATTR_AUTHCODE - { - public uint cbSize; - public uint fCommercial; - public uint fIndividual; - - public char* pwszName; - public char* pwszInfo; - - public SIGNER_ATTR_AUTHCODE(char* pwszName, char* pwszInfo) - { - cbSize = (uint)Marshal.SizeOf(); - fCommercial = 0; - fIndividual = 0; - this.pwszName = pwszName; - this.pwszInfo = pwszInfo; - } - } - + [type: StructLayout(LayoutKind.Sequential)] internal unsafe struct SIGNER_SIGN_EX3_PARAMS { - public SignerSignEx3Flags dwFlags; + public SIGNER_SIGN_FLAGS dwFlags; public SIGNER_SUBJECT_INFO* pSubjectInfo; public SIGNER_CERT* pSignerCert; public SIGNER_SIGNATURE_INFO* pSignatureInfo; public IntPtr pProviderInfo; - public SignerSignTimeStampFlags dwTimestampFlags; + public SIGNER_TIMESTAMP_FLAGS dwTimestampFlags; public byte* pszTimestampAlgorithmOid; public char* pwszHttpTimeStamp; public IntPtr psRequest; - public SIGN_INFO* pSignCallBack; - public IntPtr* ppSignerContext; + public SIGNER_DIGEST_SIGN_INFO* pSignCallBack; + public SIGNER_CONTEXT** ppSignerContext; public IntPtr pCryptoPolicy; public IntPtr pReserved; } @@ -258,6 +38,6 @@ internal delegate int SignCallback( [param: In, MarshalAs(UnmanagedType.U4)] uint algId, [param: In, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 4)] byte[] pDigestToSign, [param: In, MarshalAs(UnmanagedType.U4)] uint dwDigestToSign, - [param: In, Out] ref CRYPTOAPI_BLOB blob + [param: In, Out] ref CRYPT_INTEGER_BLOB blob ); } diff --git a/src/AzureSign.Core/MemoryCertificateStore.cs b/src/AzureSign.Core/MemoryCertificateStore.cs index 2111abb..51700ad 100644 --- a/src/AzureSign.Core/MemoryCertificateStore.cs +++ b/src/AzureSign.Core/MemoryCertificateStore.cs @@ -1,16 +1,22 @@ -using AzureSign.Core.Interop; +using static Windows.Win32.PInvoke; using System; using System.Diagnostics; using System.Security.Cryptography.X509Certificates; +using Windows.Win32.Security.Cryptography; + +// CsWin32 marks the crypt32 store APIs as Windows-only. This library is Windows-only by design, +// so the platform compatibility analyzer's reachability warnings do not apply here. +#pragma warning disable CA1416 + namespace AzureSign.Core { internal sealed class MemoryCertificateStore : IDisposable { - private IntPtr _handle; + private HCERTSTORE _handle; private readonly X509Store _store; - private MemoryCertificateStore(IntPtr handle) + private MemoryCertificateStore(HCERTSTORE handle) { _handle = handle; try @@ -27,11 +33,11 @@ private MemoryCertificateStore(IntPtr handle) } } - public static MemoryCertificateStore Create() + public unsafe static MemoryCertificateStore Create() { const string STORE_TYPE = "Memory"; - var handle = crypt32.CertOpenStore(STORE_TYPE, CertEncodingType.NONE, IntPtr.Zero, CertOpenStoreFlags.NONE, IntPtr.Zero); - if (handle == IntPtr.Zero) + var handle = CertOpenStore(STORE_TYPE, 0, 0, null); + if (handle.IsNull) { throw new InvalidOperationException("Failed to create a memory certificate store."); } @@ -42,7 +48,7 @@ public static MemoryCertificateStore Create() void IDisposable.Dispose() => Dispose(true); ~MemoryCertificateStore() => Dispose(false); - public IntPtr Handle => _store.StoreHandle; + public HCERTSTORE Handle => (HCERTSTORE)_store.StoreHandle; public void Add(X509Certificate2 certificate) => _store.Add(certificate); public void Add(X509Certificate2Collection collection) => _store.AddRange(collection); public X509Certificate2Collection Certificates => _store.Certificates; @@ -61,10 +67,10 @@ private void Dispose(bool disposing) private void FreeHandle() { - if (_handle != IntPtr.Zero) + if (!_handle.IsNull) { - var closed = crypt32.CertCloseStore(_handle, CertCloreStoreFlags.NONE); - _handle = IntPtr.Zero; + var closed = CertCloseStore(_handle, 0); + _handle = HCERTSTORE.Null; Debug.Assert(closed); } } diff --git a/src/AzureSign.Core/NativeMethods.txt b/src/AzureSign.Core/NativeMethods.txt new file mode 100644 index 0000000..6084df6 --- /dev/null +++ b/src/AzureSign.Core/NativeMethods.txt @@ -0,0 +1,14 @@ +CertOpenStore +CertCloseStore +SignerFreeSignerContext +SignerSignEx3 +E_INVALIDARG +S_OK +CRYPT_INTEGER_BLOB +HRESULT +SIGNER_CERT_STORE_INFO + +// Not yet documented: + +// APPX_SIP_CLIENT_DATA +// SIGNER_SIGN_EX3_PARAMS diff --git a/test/AzureSign.Core.Tests/AuthenticodeKeyVaultSignerTests.cs b/test/AzureSign.Core.Tests/AuthenticodeKeyVaultSignerTests.cs index f50c329..cabf73f 100644 --- a/test/AzureSign.Core.Tests/AuthenticodeKeyVaultSignerTests.cs +++ b/test/AzureSign.Core.Tests/AuthenticodeKeyVaultSignerTests.cs @@ -112,11 +112,48 @@ public void ShouldSignExeWithRSASigningCertificates_Sha256FileDigest_WithTimesta } } - private string GetFileToSign() + // The .msix goes through the Appx SIP, which takes the SIGNER_SIGN_EX3_PARAMS + // and APPX_SIP_CLIENT_DATA structures instead of the plain PE path. + [Theory] + [MemberData(nameof(RsaCertificates))] + public void ShouldSignMsixWithRSASigningCertificates_Sha256FileDigest(string certificate) + { + var signingCert = X509CertificateLoader.LoadPkcs12FromFile(certificate, "test", X509KeyStorageFlags.EphemeralKeySet); + var signer = new AuthenticodeKeyVaultSigner(signingCert.GetRSAPrivateKey(), signingCert, HashAlgorithmName.SHA256, TimeStampConfiguration.None); + var fileToSign = GetFileToSign("signtarget.msix"); + var result = signer.SignFile(fileToSign, null, null, null); + Assert.Equal(0, result); + } + + [Theory] + [MemberData(nameof(RsaCertificates))] + public void ShouldSignMsixWithRSASigningCertificates_Sha256FileDigest_WithTimestamps(string certificate) + { + var signingCert = X509CertificateLoader.LoadPkcs12FromFile(certificate, "test", X509KeyStorageFlags.EphemeralKeySet); + var timestampConfig = new TimeStampConfiguration("http://timestamp.digicert.com", HashAlgorithmName.SHA256, TimeStampType.RFC3161); + var signer = new AuthenticodeKeyVaultSigner(signingCert.GetRSAPrivateKey(), signingCert, HashAlgorithmName.SHA256, timestampConfig); + var fileToSign = GetFileToSign("signtarget.msix"); + var result = signer.SignFile(fileToSign, null, null, null); + Assert.Equal(0, result); + } + + [Theory] + [MemberData(nameof(ECDsaCertificates))] + public void ShouldSignMsixWithECDsaSigningCertificates_Sha256FileDigest_WithTimestamps(string certificate) + { + var signingCert = X509CertificateLoader.LoadPkcs12FromFile(certificate, "test", X509KeyStorageFlags.EphemeralKeySet); + var timestampConfig = new TimeStampConfiguration("http://timestamp.digicert.com", HashAlgorithmName.SHA256, TimeStampType.RFC3161); + var signer = new AuthenticodeKeyVaultSigner(signingCert.GetECDsaPrivateKey(), signingCert, HashAlgorithmName.SHA256, timestampConfig); + var fileToSign = GetFileToSign("signtarget.msix"); + var result = signer.SignFile(fileToSign, null, null, null); + Assert.Equal(0, result); + } + + private string GetFileToSign(string source = "signtarget.exe") { var guid = Guid.NewGuid(); - var path = Path.Combine(_scratchDirectory.FullName, $"{guid}.exe"); - File.Copy("signtarget.exe", path); + var path = Path.Combine(_scratchDirectory.FullName, $"{guid}{Path.GetExtension(source)}"); + File.Copy(source, path); return path; } diff --git a/test/AzureSign.Core.Tests/AzureSign.Core.Tests.csproj b/test/AzureSign.Core.Tests/AzureSign.Core.Tests.csproj index e2dbff8..a91100c 100644 --- a/test/AzureSign.Core.Tests/AzureSign.Core.Tests.csproj +++ b/test/AzureSign.Core.Tests/AzureSign.Core.Tests.csproj @@ -30,5 +30,8 @@ Always + + Always + diff --git a/test/AzureSign.Core.Tests/MemoryCertificateStoreTests.cs b/test/AzureSign.Core.Tests/MemoryCertificateStoreTests.cs index 5776d30..21879de 100644 --- a/test/AzureSign.Core.Tests/MemoryCertificateStoreTests.cs +++ b/test/AzureSign.Core.Tests/MemoryCertificateStoreTests.cs @@ -11,7 +11,7 @@ public class MemoryCertificateStoreTests public void ShouldCreateAndDisposeAMemoryCertificateStore() { var store = MemoryCertificateStore.Create(); - Assert.NotEqual(IntPtr.Zero, store.Handle); + Assert.NotEqual(default, store.Handle); Assert.Empty(store.Certificates); store.Close(); } diff --git a/test/AzureSign.Core.Tests/signtarget.msix b/test/AzureSign.Core.Tests/signtarget.msix new file mode 100644 index 0000000000000000000000000000000000000000..c32eee835fe0eb357d82959dbe71c2fce7fc1f87 GIT binary patch literal 4436 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