diff --git a/src/Files.App.CsWin32/NativeMethods.txt b/src/Files.App.CsWin32/NativeMethods.txt
index 294dcf68ab36..a758d0ef4c51 100644
--- a/src/Files.App.CsWin32/NativeMethods.txt
+++ b/src/Files.App.CsWin32/NativeMethods.txt
@@ -236,3 +236,36 @@ GetMenuItemCount
GetMenuItemInfo
IsWow64Process2
GetCurrentProcess
+CertFreeCertificateContext
+CryptMsgGetParam
+CryptMsgClose
+CryptMsgOpenToDecode
+CryptMsgUpdate
+CertOpenStore
+CryptDecodeObject
+CertFindCertificateInStore
+CertComparePublicKeyInfo
+CryptQueryObject
+CertCloseStore
+WinVerifyTrust
+FileTimeToSystemTime
+FileTimeToLocalFileTime
+SystemTimeToFileTime
+CRYPTOAPI_BLOB
+CMSG_SIGNER_INFO
+SignDataHandle
+CRYPT_ATTRIBUTE
+FILETIME
+CRYPT_BIT_BLOB
+CERT_ALT_NAME_INFO
+CERT_CONTEXT
+CERT_INFO
+CRYPT_ALGORITHM_IDENTIFIER
+CERT_PUBLIC_KEY_INFO
+CATALOG_INFO
+WINTRUST_FILE_INFO
+WINTRUST_DATA
+HCERTSTORE
+HCRYPTMSG
+CERT_QUERY_ENCODING_TYPE
+CertGetNameString
diff --git a/src/Files.App/Data/Enums/PropertiesNavigationViewItemType.cs b/src/Files.App/Data/Enums/PropertiesNavigationViewItemType.cs
index 4b33779e6c1d..811d70c3474e 100644
--- a/src/Files.App/Data/Enums/PropertiesNavigationViewItemType.cs
+++ b/src/Files.App/Data/Enums/PropertiesNavigationViewItemType.cs
@@ -47,5 +47,10 @@ public enum PropertiesNavigationViewItemType
/// Shortcut page type
///
Shortcut,
+
+ ///
+ /// Signatures page type
+ ///
+ Signatures,
}
}
diff --git a/src/Files.App/Data/Factories/PropertiesNavigationViewItemFactory.cs b/src/Files.App/Data/Factories/PropertiesNavigationViewItemFactory.cs
index f546301858e4..0723f321df0d 100644
--- a/src/Files.App/Data/Factories/PropertiesNavigationViewItemFactory.cs
+++ b/src/Files.App/Data/Factories/PropertiesNavigationViewItemFactory.cs
@@ -61,8 +61,15 @@ public static ObservableCollection Initialize
ItemType = PropertiesNavigationViewItemType.Compatibility,
ThemedIconStyle = (Style)Application.Current.Resources["App.ThemedIcons.Properties.Compatability"],
};
+ var signaturesItem = new NavigationViewItemButtonStyleItem()
+ {
+ Name = Strings.Signatures.GetLocalizedResource(),
+ ItemType = PropertiesNavigationViewItemType.Signatures,
+ ThemedIconStyle = (Style)Application.Current.Resources["App.ThemedIcons.Properties.Signatures"],
+ };
PropertiesNavigationViewItems.Add(generalItem);
+ PropertiesNavigationViewItems.Add(signaturesItem);
PropertiesNavigationViewItems.Add(securityItem);
PropertiesNavigationViewItems.Add(hashesItem);
PropertiesNavigationViewItems.Add(shortcutItem);
@@ -89,6 +96,7 @@ public static ObservableCollection Initialize
PropertiesNavigationViewItems.Remove(securityItem);
PropertiesNavigationViewItems.Remove(customizationItem);
PropertiesNavigationViewItems.Remove(hashesItem);
+ PropertiesNavigationViewItems.Remove(signaturesItem);
}
else if (item is ListedItem listedItem)
{
@@ -102,6 +110,11 @@ public static ObservableCollection Initialize
var detailsItemEnabled = !(isFolder && !listedItem.IsArchive) && !isLibrary && !listedItem.IsRecycleBinItem;
var customizationItemEnabled = !isLibrary && (isFolder && !listedItem.IsArchive || isShortcut && !listedItem.IsLinkItem);
var compatibilityItemEnabled = FileExtensionHelpers.IsExecutableFile(listedItem is IShortcutItem sht ? sht.TargetPath : fileExt, true);
+ var signaturesItemEnabled =
+ !isFolder &&
+ !isLibrary &&
+ !listedItem.IsRecycleBinItem &&
+ FileExtensionHelpers.IsSignableFile(fileExt, true);
if (!securityItemEnabled)
PropertiesNavigationViewItems.Remove(securityItem);
@@ -109,6 +122,9 @@ public static ObservableCollection Initialize
if (!hashItemEnabled)
PropertiesNavigationViewItems.Remove(hashesItem);
+ if (!signaturesItemEnabled)
+ PropertiesNavigationViewItems.Remove(signaturesItem);
+
if (!isShortcut)
PropertiesNavigationViewItems.Remove(shortcutItem);
@@ -132,6 +148,7 @@ public static ObservableCollection Initialize
PropertiesNavigationViewItems.Remove(detailsItem);
PropertiesNavigationViewItems.Remove(customizationItem);
PropertiesNavigationViewItems.Remove(compatibilityItem);
+ PropertiesNavigationViewItems.Remove(signaturesItem);
}
return PropertiesNavigationViewItems;
diff --git a/src/Files.App/Data/Items/CertNodeInfoItem.cs b/src/Files.App/Data/Items/CertNodeInfoItem.cs
new file mode 100644
index 000000000000..abed64927c7a
--- /dev/null
+++ b/src/Files.App/Data/Items/CertNodeInfoItem.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+namespace Files.App.Data.Items
+{
+ public class CertNodeInfoItem
+ {
+ public string IssuedTo { get; set; } = string.Empty;
+
+ public string IssuedBy { get; set; } = string.Empty;
+
+ public string Version { get; set; } = string.Empty;
+
+ public string ValidFrom { get; set; } = string.Empty;
+
+ public string ValidTo { get; set; } = string.Empty;
+ }
+}
diff --git a/src/Files.App/Data/Models/SignatureInfoItem.cs b/src/Files.App/Data/Models/SignatureInfoItem.cs
new file mode 100644
index 000000000000..6b5e8ffc5022
--- /dev/null
+++ b/src/Files.App/Data/Models/SignatureInfoItem.cs
@@ -0,0 +1,92 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using Files.App.Utils.Signatures;
+using System.Windows.Input;
+using Windows.Win32.Foundation;
+
+namespace Files.App.Data.Models
+{
+ public sealed partial class SignatureInfoItem : ObservableObject
+ {
+ private readonly string _fileName;
+
+ private readonly HWND _hwndParent;
+
+ private readonly int _index;
+
+ private string _Version = string.Empty;
+ public string Version
+ {
+ get => _Version;
+ set => SetProperty(ref _Version, value);
+ }
+
+ private string _IssuedBy = string.Empty;
+ public string IssuedBy
+ {
+ get => _IssuedBy;
+ set => SetProperty(ref _IssuedBy, value);
+ }
+
+ private string _IssuedTo = string.Empty;
+ public string IssuedTo
+ {
+ get => _IssuedTo;
+ set => SetProperty(ref _IssuedTo, value);
+ }
+
+ private string _ValidFromTimestamp = string.Empty;
+ public string ValidFromTimestamp
+ {
+ get => _ValidFromTimestamp;
+ set => SetProperty(ref _ValidFromTimestamp, value);
+ }
+
+ private string _ValidToTimestamp = string.Empty;
+ public string ValidToTimestamp
+ {
+ get => _ValidToTimestamp;
+ set => SetProperty(ref _ValidToTimestamp, value);
+ }
+
+ private string _VerifiedTimestamp = string.Empty;
+ public string VerifiedTimestamp
+ {
+ get => _VerifiedTimestamp;
+ set => SetProperty(ref _VerifiedTimestamp, value);
+ }
+
+ private bool _Verified = false;
+ public bool Verified
+ {
+ get => _Verified;
+ set
+ {
+ if (SetProperty(ref _Verified, value))
+ OnPropertyChanged(nameof(Glyph));
+ }
+ }
+
+ public List SignChain { get; }
+
+ public string Glyph => Verified ? "\uE930" : "\uEA39";
+
+ public ICommand OpenDetailsCommand { get; }
+
+ public SignatureInfoItem(string fileName, int index, HWND hWnd, List chain)
+ {
+ _fileName = fileName;
+ _hwndParent = hWnd;
+ _index = index;
+ SignChain = chain ?? new List();
+ OpenDetailsCommand = new AsyncRelayCommand(DoOpenDetails);
+ }
+
+ private Task DoOpenDetails()
+ {
+ DigitalSignaturesUtil.DisplaySignerInfoDialog(_fileName, _hwndParent, _index);
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/Files.App/Helpers/Win32/Win32Helper.Storage.cs b/src/Files.App/Helpers/Win32/Win32Helper.Storage.cs
index 93b28ad04429..51bc82a35a73 100644
--- a/src/Files.App/Helpers/Win32/Win32Helper.Storage.cs
+++ b/src/Files.App/Helpers/Win32/Win32Helper.Storage.cs
@@ -902,13 +902,13 @@ public static SafeFileHandle OpenFileForRead(string filePath, bool readWrite = f
(uint)FILE_ACCESS_RIGHTS.FILE_GENERIC_READ | (uint)(readWrite ? FILE_ACCESS_RIGHTS.FILE_GENERIC_WRITE : 0u), (uint)(Win32PInvoke.FILE_SHARE_READ | (readWrite ? 0 : Win32PInvoke.FILE_SHARE_WRITE)), IntPtr.Zero, Win32PInvoke.OPEN_EXISTING, (uint)Win32PInvoke.File_Attributes.BackupSemantics | flags, IntPtr.Zero), true);
}
- public static bool GetFileDateModified(string filePath, out FILETIME dateModified)
+ public static bool GetFileDateModified(string filePath, out System.Runtime.InteropServices.ComTypes.FILETIME dateModified)
{
using var hFile = new SafeFileHandle(Win32PInvoke.CreateFileFromApp(filePath, (uint)FILE_ACCESS_RIGHTS.FILE_GENERIC_READ, Win32PInvoke.FILE_SHARE_READ, IntPtr.Zero, Win32PInvoke.OPEN_EXISTING, (uint)Win32PInvoke.File_Attributes.BackupSemantics, IntPtr.Zero), true);
return Win32PInvoke.GetFileTime(hFile.DangerousGetHandle(), out _, out _, out dateModified);
}
- public static bool SetFileDateModified(string filePath, FILETIME dateModified)
+ public static bool SetFileDateModified(string filePath, System.Runtime.InteropServices.ComTypes.FILETIME dateModified)
{
using var hFile = new SafeFileHandle(Win32PInvoke.CreateFileFromApp(filePath, (uint)FILE_ACCESS_RIGHTS.FILE_WRITE_ATTRIBUTES, 0, IntPtr.Zero, Win32PInvoke.OPEN_EXISTING, (uint)Win32PInvoke.File_Attributes.BackupSemantics, IntPtr.Zero), true);
return Win32PInvoke.SetFileTime(hFile.DangerousGetHandle(), new(), new(), dateModified);
diff --git a/src/Files.App/Helpers/Win32/Win32PInvoke.Methods.cs b/src/Files.App/Helpers/Win32/Win32PInvoke.Methods.cs
index 5d700736a2fa..e52a08638811 100644
--- a/src/Files.App/Helpers/Win32/Win32PInvoke.Methods.cs
+++ b/src/Files.App/Helpers/Win32/Win32PInvoke.Methods.cs
@@ -1,9 +1,8 @@
-// Copyright (c) 2024 Files Community
-// Licensed under the MIT License. See the LICENSE.
+// Copyright (c) Files Community
+// Licensed under the MIT License.
using System.IO;
using System.Runtime.InteropServices;
-using System.Runtime.InteropServices.ComTypes;
using System.Text;
using Windows.Win32.Foundation;
using Windows.Win32.System.Com;
@@ -232,17 +231,17 @@ public static extern bool WriteFileEx(
[DllImport("api-ms-win-core-file-l1-2-1.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
public static extern bool GetFileTime(
[In] IntPtr hFile,
- out FILETIME lpCreationTime,
- out FILETIME lpLastAccessTime,
- out FILETIME lpLastWriteTime
+ out System.Runtime.InteropServices.ComTypes.FILETIME lpCreationTime,
+ out System.Runtime.InteropServices.ComTypes.FILETIME lpLastAccessTime,
+ out System.Runtime.InteropServices.ComTypes.FILETIME lpLastWriteTime
);
[DllImport("api-ms-win-core-file-l1-2-1.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
public static extern bool SetFileTime(
[In] IntPtr hFile,
- in FILETIME lpCreationTime,
- in FILETIME lpLastAccessTime,
- in FILETIME lpLastWriteTime
+ in System.Runtime.InteropServices.ComTypes.FILETIME lpCreationTime,
+ in System.Runtime.InteropServices.ComTypes.FILETIME lpLastAccessTime,
+ in System.Runtime.InteropServices.ComTypes.FILETIME lpLastWriteTime
);
[DllImport("api-ms-win-core-file-l2-1-1.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
@@ -288,7 +287,7 @@ IntPtr hFindFile
[DllImport("api-ms-win-core-timezone-l1-1-0.dll", SetLastError = true)]
public static extern bool FileTimeToSystemTime(
- ref FILETIME lpFileTime,
+ ref System.Runtime.InteropServices.ComTypes.FILETIME lpFileTime,
out SYSTEMTIME lpSystemTime
);
@@ -347,5 +346,9 @@ public static extern int SHGetKnownFolderPath(
IntPtr hToken,
out IntPtr pszPath
);
+
+ // cryptui.dll
+ [DllImport("cryptui.dll", SetLastError = true, CharSet = CharSet.Auto)]
+ public unsafe static extern bool CryptUIDlgViewSignerInfo(CRYPTUI_VIEWSIGNERINFO_STRUCT* pViewInfo);
}
}
diff --git a/src/Files.App/Helpers/Win32/Win32PInvoke.Structs.cs b/src/Files.App/Helpers/Win32/Win32PInvoke.Structs.cs
index d54e6ee441e7..d1453d5cd285 100644
--- a/src/Files.App/Helpers/Win32/Win32PInvoke.Structs.cs
+++ b/src/Files.App/Helpers/Win32/Win32PInvoke.Structs.cs
@@ -3,7 +3,8 @@
using System.IO;
using System.Runtime.InteropServices;
-using System.Runtime.InteropServices.ComTypes;
+using Windows.Win32.Foundation;
+using Windows.Win32.Security.Cryptography;
namespace Files.App.Helpers
{
@@ -90,9 +91,9 @@ public struct REPARSE_DATA_BUFFER
public struct WIN32_FILE_ATTRIBUTE_DATA
{
public FileAttributes dwFileAttributes;
- public FILETIME ftCreationTime;
- public FILETIME ftLastAccessTime;
- public FILETIME ftLastWriteTime;
+ public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
+ public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
+ public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
public uint nFileSizeHigh;
public uint nFileSizeLow;
}
@@ -183,9 +184,9 @@ public struct WIN32_FIND_DATA
{
public uint dwFileAttributes;
- public FILETIME ftCreationTime;
- public FILETIME ftLastAccessTime;
- public FILETIME ftLastWriteTime;
+ public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
+ public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
+ public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
public uint nFileSizeHigh;
public uint nFileSizeLow;
@@ -198,5 +199,37 @@ public struct WIN32_FIND_DATA
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public string cAlternateFileName;
}
+
+ [StructLayout(LayoutKind.Sequential)]
+ public unsafe struct SignDataHandle
+ {
+ public uint dwObjSize;
+ public CMSG_SIGNER_INFO* pSignerInfo;
+ public HCERTSTORE hCertStoreHandle;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public unsafe struct CRYPTOAPI_BLOB
+ {
+ public uint cbData;
+ public void* pbData;
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
+ public unsafe struct CRYPTUI_VIEWSIGNERINFO_STRUCT
+ {
+ public uint dwSize;
+ public HWND hwndParent;
+ public uint dwFlags;
+ public PCSTR szTitle;
+ public CMSG_SIGNER_INFO* pSignerInfo;
+ public void* hMsg;
+ public PCSTR pszOID;
+ public uint? dwReserved;
+ public uint cStores;
+ public HCERTSTORE* rghStores;
+ public uint cPropPages;
+ public void* rgPropPages;
+ }
}
}
diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw
index fe935a3293a4..7c07b9676f9b 100644
--- a/src/Files.App/Strings/en-US/Resources.resw
+++ b/src/Files.App/Strings/en-US/Resources.resw
@@ -4273,6 +4273,27 @@
Filename
+
+ Signatures
+
+
+ Signature list
+
+
+ Issued by:
+
+
+ Issued to:
+
+
+ Valid from:
+
+
+ Valid to:
+
+
+ No signature was found.
+
Show option to open folders in Windows Terminal
diff --git a/src/Files.App/Utils/Signatures/DigitalSignaturesUtil.cs b/src/Files.App/Utils/Signatures/DigitalSignaturesUtil.cs
new file mode 100644
index 000000000000..2c6a15b19bb1
--- /dev/null
+++ b/src/Files.App/Utils/Signatures/DigitalSignaturesUtil.cs
@@ -0,0 +1,990 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using System.Runtime.InteropServices;
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.Security.Cryptography;
+using Windows.Win32.Security.WinTrust;
+using static Files.App.Helpers.Win32PInvoke;
+
+namespace Files.App.Utils.Signatures
+{
+ public static class DigitalSignaturesUtil
+ {
+ // OIDs
+ private const string szOID_NESTED_SIGNATURE = "1.3.6.1.4.1.311.2.4.1";
+ private const string szOID_RSA_counterSign = "1.2.840.113549.1.9.6";
+ private const string szOID_RSA_signingTime = "1.2.840.113549.1.9.5";
+ private const string szOID_RFC3161_counterSign = "1.3.6.1.4.1.311.3.3.1";
+ private const string szOID_OIWSEC_sha1 = "1.3.14.3.2.26";
+ private const string szOID_RSA_MD5 = "1.2.840.113549.2.5";
+ private const string szOID_NIST_sha256 = "2.16.840.1.101.3.4.2.1";
+
+ // Flags
+ private const uint CERT_NAME_SIMPLE_DISPLAY_TYPE = 4;
+ private const uint CERT_SYSTEM_STORE_CURRENT_USER = 0x00010000;
+ private const uint PKCS_7_ASN_ENCODING = 0x00010000;
+ private const uint CRYPT_ASN_ENCODING = 0x00000001;
+ private const CERT_QUERY_ENCODING_TYPE ENCODING =
+ CERT_QUERY_ENCODING_TYPE.X509_ASN_ENCODING | CERT_QUERY_ENCODING_TYPE.PKCS_7_ASN_ENCODING;
+
+ private const uint CMSG_SIGNER_INFO_PARAM = 6;
+
+ // Version numbers
+ private const uint CERT_V1 = 0;
+ private const uint CERT_V2 = 1;
+ private const uint CERT_V3 = 2;
+
+ private static readonly byte[] SG_ProtoCoded = [
+ 0x30, 0x82
+ ];
+
+ private static readonly byte[] SG_SignedData = [
+ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x02
+ ];
+
+ private static readonly IDateTimeFormatter formatter = Ioc.Default.GetRequiredService();
+
+ public static void LoadItemSignatures(
+ string filePath,
+ ObservableCollection signatures,
+ HWND hWnd,
+ CancellationToken ct)
+ {
+ var signChain = new List();
+ GetSignerCertificateInfo(filePath, signChain, ct);
+
+ foreach (var signNode in signChain)
+ {
+ if (signNode.CertChain.Count == 0)
+ continue;
+
+ var signatureInfo = new SignatureInfoItem(filePath, signNode.Index, hWnd, signNode.CertChain)
+ {
+ Version = signNode.Version,
+ IssuedBy = signNode.CertChain[0].IssuedBy,
+ IssuedTo = signNode.CertChain[0].IssuedTo,
+ ValidFromTimestamp = signNode.CertChain[0].ValidFrom,
+ ValidToTimestamp = signNode.CertChain[0].ValidTo,
+ VerifiedTimestamp = signNode.CounterSign.TimeStamp,
+ Verified = signNode.IsValid,
+ };
+ signatures.Add(signatureInfo);
+ }
+ }
+
+ public unsafe static void DisplaySignerInfoDialog(string filePath, HWND hwndParent, int index)
+ {
+ if (string.IsNullOrEmpty(filePath))
+ return;
+
+ void* hAuthCryptMsg = null;
+ var signHandle = new SignDataHandle();
+ var signDataChain = new List();
+
+ try
+ {
+ var result = TryGetSignerInfo(
+ filePath,
+ out hAuthCryptMsg,
+ out signHandle.hCertStoreHandle,
+ out signHandle.pSignerInfo,
+ out signHandle.dwObjSize
+ );
+ if (!result || signHandle.pSignerInfo is null)
+ return;
+
+ signDataChain.Add(signHandle);
+ GetNestedSignerInfo(ref signHandle, signDataChain);
+ if (index >= signDataChain.Count)
+ return;
+
+ signHandle = signDataChain[index];
+ var issuer = signHandle.pSignerInfo->Issuer;
+ var pCertContext = PInvoke.CertFindCertificateInStore(
+ signHandle.hCertStoreHandle,
+ ENCODING,
+ 0,
+ CERT_FIND_FLAGS.CERT_FIND_ISSUER_NAME,
+ &issuer,
+ null
+ );
+ if (pCertContext is null)
+ return;
+
+ var viewInfo = new CRYPTUI_VIEWSIGNERINFO_STRUCT
+ {
+ dwSize = (uint)Marshal.SizeOf(),
+ hwndParent = hwndParent,
+ dwFlags = 0,
+ szTitle = (PCSTR)null,
+ pSignerInfo = signHandle.pSignerInfo,
+ hMsg = hAuthCryptMsg,
+ pszOID = (PCSTR)null,
+ dwReserved = null,
+ cStores = 1,
+ rghStores = (HCERTSTORE*)NativeMemory.Alloc((uint)sizeof(void*)),
+ cPropPages = 0,
+ rgPropPages = null
+ };
+ *(viewInfo.rghStores) = signHandle.hCertStoreHandle;
+
+ result = CryptUIDlgViewSignerInfo(&viewInfo);
+
+ PInvoke.CertFreeCertificateContext(pCertContext);
+ }
+ finally
+ {
+ // Since signDataChain contains nested signatures,
+ // you must release them starting from the last one.
+ for (int i = signDataChain.Count - 1; i >= 0; i--)
+ {
+ if (signDataChain[i].pSignerInfo is not null)
+ NativeMemory.Free(signDataChain[i].pSignerInfo);
+
+ if (!signDataChain[i].hCertStoreHandle.IsNull)
+ PInvoke.CertCloseStore(signDataChain[i].hCertStoreHandle, 0);
+ }
+
+ if (hAuthCryptMsg is not null)
+ PInvoke.CryptMsgClose(hAuthCryptMsg);
+ }
+ }
+
+ private unsafe static bool GetSignerSignatureInfo(
+ HCERTSTORE hSystemStore,
+ HCERTSTORE hCertStore,
+ CERT_CONTEXT* pOrigContext,
+ ref CERT_CONTEXT* pCurrContext,
+ SignNodeInfo signNode)
+ {
+ var pCertInfo = pCurrContext->pCertInfo;
+ var certNode = new CertNodeInfoItem();
+
+ (_, certNode.Version) = CalculateSignVersion(pCertInfo->dwVersion);
+ GetStringFromCertContext(pCurrContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, certNode);
+ GetStringFromCertContext(pCurrContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 1, certNode);
+
+ var pft = &(pCertInfo->NotBefore);
+ certNode.ValidFrom = TimeToString(pft);
+ pft = &(pCertInfo->NotAfter);
+ certNode.ValidTo = TimeToString(pft);
+
+ signNode.CertChain.Add(certNode);
+
+ pCurrContext = PInvoke.CertFindCertificateInStore(
+ hCertStore,
+ ENCODING,
+ 0,
+ CERT_FIND_FLAGS.CERT_FIND_SUBJECT_NAME,
+ &(pCertInfo->Issuer),
+ null
+ );
+
+ if (pCurrContext is null)
+ {
+ pCurrContext = PInvoke.CertFindCertificateInStore(
+ hSystemStore,
+ ENCODING,
+ 0,
+ CERT_FIND_FLAGS.CERT_FIND_SUBJECT_NAME,
+ &(pCertInfo->Issuer),
+ null
+ );
+ }
+
+ if (pCurrContext is null)
+ return false;
+
+ var result = PInvoke.CertComparePublicKeyInfo(
+ ENCODING,
+ &pCurrContext->pCertInfo->SubjectPublicKeyInfo,
+ &pOrigContext->pCertInfo->SubjectPublicKeyInfo
+ );
+
+ return !result;
+ }
+
+ private unsafe static bool GetSignerCertificateInfo(string fileName, List signChain, CancellationToken ct)
+ {
+ var succeded = false;
+ var authSignData = new SignDataHandle() { dwObjSize = 0, hCertStoreHandle = HCERTSTORE.Null, pSignerInfo = null };
+ var signDataChain = new List();
+ signChain.Clear();
+
+ var cert_store_prov_system = (PCSTR)(byte*)10;
+ var root = "Root";
+ var pRoot = &root;
+ var hSystemStore = PInvoke.CertOpenStore(
+ cert_store_prov_system,
+ ENCODING,
+ HCRYPTPROV_LEGACY.Null,
+ (CERT_OPEN_STORE_FLAGS)CERT_SYSTEM_STORE_CURRENT_USER,
+ pRoot
+ );
+ if (hSystemStore == IntPtr.Zero)
+ return false;
+
+ void* hAuthCryptMsg = null;
+ var result = TryGetSignerInfo(
+ fileName,
+ out hAuthCryptMsg,
+ out authSignData.hCertStoreHandle,
+ out authSignData.pSignerInfo,
+ out authSignData.dwObjSize
+ );
+
+ if (hAuthCryptMsg is not null)
+ {
+ PInvoke.CryptMsgClose(hAuthCryptMsg);
+ hAuthCryptMsg = null;
+ }
+
+ if (!result)
+ {
+ if (authSignData.hCertStoreHandle != IntPtr.Zero)
+ PInvoke.CertCloseStore(authSignData.hCertStoreHandle, 0);
+
+ PInvoke.CertCloseStore(hSystemStore, 0);
+ return false;
+ }
+
+ signDataChain.Add(authSignData);
+ GetNestedSignerInfo(ref authSignData, signDataChain);
+
+ for (var i = 0; i < signDataChain.Count; i++)
+ {
+ if (ct.IsCancellationRequested)
+ {
+ PInvoke.CertCloseStore(hSystemStore, 0);
+ return false;
+ }
+
+ CERT_CONTEXT* pCurrContext = null;
+ CMSG_SIGNER_INFO* pCounterSigner = null;
+ var signNode = new SignNodeInfo();
+
+ GetCounterSignerInfo(signDataChain[i].pSignerInfo, &pCounterSigner);
+ if (pCounterSigner is not null)
+ GetCounterSignerData(pCounterSigner, signNode.CounterSign);
+ else
+ GetGeneralizedTimeStamp(signDataChain[i].pSignerInfo, signNode.CounterSign);
+
+ var pszObjId = signDataChain[i].pSignerInfo->HashAlgorithm.pszObjId;
+ var szObjId = new string((sbyte*)(byte*)pszObjId);
+ CalculateDigestAlgorithm(szObjId, signNode);
+ (_, signNode.Version) = CalculateSignVersion(signDataChain[i].pSignerInfo->dwVersion);
+
+
+ var pIssuer = &(signDataChain[i].pSignerInfo->Issuer);
+ pCurrContext = PInvoke.CertFindCertificateInStore(
+ signDataChain[i].hCertStoreHandle,
+ ENCODING,
+ 0,
+ CERT_FIND_FLAGS.CERT_FIND_ISSUER_NAME,
+ pIssuer,
+ null
+ );
+
+ result = pCurrContext is not null;
+ while (result)
+ {
+ var pOrigContext = pCurrContext;
+ result = GetSignerSignatureInfo(
+ hSystemStore,
+ signDataChain[i].hCertStoreHandle,
+ pOrigContext,
+ ref pCurrContext,
+ signNode
+ );
+ PInvoke.CertFreeCertificateContext(pOrigContext);
+ }
+
+ if (pCurrContext is not null)
+ PInvoke.CertFreeCertificateContext(pCurrContext);
+
+ if (pCounterSigner is not null)
+ NativeMemory.Free(pCounterSigner);
+
+ if (signDataChain[i].pSignerInfo is not null)
+ NativeMemory.Free(signDataChain[i].pSignerInfo);
+
+ if (!signDataChain[i].hCertStoreHandle.IsNull)
+ PInvoke.CertCloseStore(signDataChain[i].hCertStoreHandle, 0);
+
+ succeded = true;
+ signNode.IsValid = VerifyySignature(fileName);
+ signNode.Index = i;
+ signChain.Add(signNode);
+ }
+
+ PInvoke.CertCloseStore(hSystemStore, 0);
+ return succeded;
+ }
+
+ private unsafe static bool VerifyySignature(string certPath)
+ {
+ int res = 1;
+ var sFileInfo = (uint)Marshal.SizeOf();
+ var sData = (uint)Marshal.SizeOf();
+ var actionGuid = new Guid("{00AAC56B-CD44-11D0-8CC2-00C04FC295EE}");
+
+ fixed (char* pCertPath = certPath)
+ {
+ var fileInfo = new WINTRUST_FILE_INFO
+ {
+ cbStruct = sFileInfo,
+ pcwszFilePath = (PCWSTR)pCertPath,
+ hFile = (HANDLE)null,
+ pgKnownSubject = null
+ };
+
+ var wintrustData = new WINTRUST_DATA
+ {
+ cbStruct = sData,
+ pPolicyCallbackData = null,
+ pSIPClientData = null,
+ dwUIChoice = WINTRUST_DATA_UICHOICE.WTD_UI_NONE,
+ fdwRevocationChecks = 0, // No revocation checking
+ dwUnionChoice = WINTRUST_DATA_UNION_CHOICE.WTD_CHOICE_FILE,
+ dwStateAction = WINTRUST_DATA_STATE_ACTION.WTD_STATEACTION_VERIFY,
+ hWVTStateData = (HANDLE)null,
+ pwszURLReference = null,
+ dwUIContext = 0,
+ Anonymous = new WINTRUST_DATA._Anonymous_e__Union
+ {
+ pFile = &fileInfo,
+ },
+ };
+
+ res = PInvoke.WinVerifyTrust((HWND)null, ref actionGuid, &wintrustData);
+
+ // Release hWVTStateData
+ wintrustData.dwStateAction = WINTRUST_DATA_STATE_ACTION.WTD_STATEACTION_CLOSE;
+ PInvoke.WinVerifyTrust((HWND)null, ref actionGuid, &wintrustData);
+ }
+
+ return res == 0;
+ }
+
+ private unsafe static bool TryGetSignerInfo(
+ string fileName,
+ out void* hMsg,
+ out HCERTSTORE hCertStore,
+ out CMSG_SIGNER_INFO* pSignerInfo,
+ out uint signerSize,
+ uint index = 0)
+ {
+ CERT_QUERY_ENCODING_TYPE encoding = 0;
+ CERT_QUERY_CONTENT_TYPE dummy = 0;
+ CERT_QUERY_FORMAT_TYPE dummy2 = 0;
+ void* pDummy = null;
+ BOOL result = false;
+
+ HCERTSTORE hCertStoreTmp = HCERTSTORE.Null;
+ void* hMsgTmp = null;
+
+ fixed (char* pFileName = fileName)
+ {
+ result = PInvoke.CryptQueryObject(
+ CERT_QUERY_OBJECT_TYPE.CERT_QUERY_OBJECT_FILE,
+ pFileName,
+ CERT_QUERY_CONTENT_TYPE_FLAGS.CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
+ CERT_QUERY_FORMAT_TYPE_FLAGS.CERT_QUERY_FORMAT_FLAG_BINARY,
+ 0,
+ &encoding,
+ &dummy,
+ &dummy2,
+ &hCertStoreTmp,
+ &hMsgTmp,
+ &pDummy
+ );
+ }
+
+ hCertStore = hCertStoreTmp;
+ hMsg = hMsgTmp;
+ pSignerInfo = null;
+ signerSize = 0;
+
+ if (!result)
+ return false;
+
+ var vpSignerInfo = (void*)pSignerInfo;
+ result = CustomCryptMsgGetParam(
+ hMsg,
+ CMSG_SIGNER_INFO_PARAM,
+ index,
+ ref vpSignerInfo,
+ ref signerSize
+ );
+ pSignerInfo = (CMSG_SIGNER_INFO*)vpSignerInfo;
+
+ return result;
+ }
+
+ private unsafe static bool GetCounterSignerInfo(
+ CMSG_SIGNER_INFO* pSignerInfo,
+ CMSG_SIGNER_INFO** pTargetSigner)
+ {
+ uint objSize = 0;
+ if (pSignerInfo is null || pTargetSigner is null)
+ return false;
+
+ try
+ {
+ *pTargetSigner = null;
+ CRYPT_ATTRIBUTE* attr = null;
+ var res = TryGetUnauthAttr(pSignerInfo, szOID_RSA_counterSign, ref attr);
+ if (!res || attr is null)
+ return false;
+
+ var pkcs7_signer_info = (PCSTR)(byte*)500;
+ var result = PInvoke.CryptDecodeObject(
+ ENCODING,
+ pkcs7_signer_info,
+ attr->rgValue[0].pbData,
+ attr->rgValue[0].cbData,
+ 0,
+ null,
+ &objSize
+ );
+ if (!result)
+ return false;
+
+ *pTargetSigner = (CMSG_SIGNER_INFO*)NativeMemory.Alloc(objSize);
+ if (*pTargetSigner is null)
+ return false;
+
+ result = PInvoke.CryptDecodeObject(
+ ENCODING,
+ pkcs7_signer_info,
+ attr->rgValue[0].pbData,
+ attr->rgValue[0].cbData,
+ 0,
+ *pTargetSigner,
+ &objSize
+ );
+ if (!result)
+ return false;
+ }
+ finally
+ {
+ }
+
+ return true;
+ }
+
+ private unsafe static bool GetCounterSignerData(CMSG_SIGNER_INFO* pSignerInfo, SignCounterSign counterSign)
+ {
+ CRYPT_ATTRIBUTE* attr = null;
+ var res = TryGetAuthAttr(pSignerInfo, szOID_RSA_signingTime, ref attr);
+ if (!res || attr is null)
+ return false;
+
+ var data = (uint)Marshal.SizeOf();
+ var ft = (System.Runtime.InteropServices.ComTypes.FILETIME*)NativeMemory.Alloc(data);
+ try
+ {
+ var pkcs_utc_time = (PCSTR)(byte*)17;
+ var result = PInvoke.CryptDecodeObject(
+ ENCODING,
+ pkcs_utc_time,
+ attr->rgValue[0].pbData,
+ attr->rgValue[0].cbData,
+ 0,
+ ft,
+ &data
+ );
+ if (!result)
+ return false;
+
+ PInvoke.FileTimeToLocalFileTime(*ft, out var lft);
+ PInvoke.FileTimeToSystemTime(lft, out var st);
+ counterSign.TimeStamp = TimeToString(null, &st);
+
+ return true;
+ }
+ finally
+ {
+ NativeMemory.Free(ft);
+ }
+ }
+
+ private unsafe static bool ParseDERFindType(
+ int typeSearch,
+ byte* pbSignature,
+ uint size,
+ ref uint positionFound,
+ ref uint lengthFound)
+ {
+ uint position = 0;
+ uint sizeFound = 0;
+ uint bytesParsed = 0;
+ var iType = 0;
+ var iClass = 0;
+ positionFound = 0;
+ lengthFound = 0;
+ if (pbSignature is null)
+ return false;
+
+ while (size > position)
+ {
+ if (!SafeToReadNBytes(size, position, 2))
+ return false;
+
+ ParseDERType(pbSignature[position], ref iType, ref iClass);
+ switch (iType)
+ {
+ case 0x05: // Null
+ ++position;
+ if (pbSignature[position] != 0x00)
+ return false;
+
+ ++position;
+ break;
+
+ case 0x06: // Object Identifier
+ ++position;
+ if (!SafeToReadNBytes(size - position, 1, pbSignature[position]))
+ return false;
+
+ position += 1u + pbSignature[position];
+ break;
+
+ case 0x00: // ?
+ case 0x01: // Boolean
+ case 0x02: // Integer
+ case 0x03: // Bit String
+ case 0x04: // Octet String
+ case 0x0A: // enumerated
+ case 0x0C: // UTF8string
+ case 0x13: // printable string
+ case 0x14: // T61 string
+ case 0x16: // IA5String
+ case 0x17: // UTC time
+ case 0x18: // Generalized time
+ case 0x1E: // BMPstring
+ ++position;
+ if (!ParseDERSize(
+ pbSignature + position,
+ size - position,
+ ref sizeFound,
+ ref bytesParsed))
+ {
+ return false;
+ }
+
+ position += bytesParsed;
+ if (!SafeToReadNBytes(size - position, 0, sizeFound))
+ return false;
+
+ if (typeSearch == iType)
+ {
+ positionFound = position;
+ lengthFound = sizeFound;
+
+ return true;
+ }
+
+ position += sizeFound;
+ break;
+
+ case 0x20: // context specific
+ case 0x21: // context specific
+ case 0x23: // context specific
+ case 0x24: // context specific
+ case 0x30: // sequence
+ case 0x31: // set
+ position++;
+ if (!ParseDERSize(
+ pbSignature + position,
+ size - position,
+ ref sizeFound,
+ ref bytesParsed))
+ {
+ return false;
+ }
+
+ position += bytesParsed;
+ break;
+
+ case 0x22: // ?
+ position += 2;
+ break;
+
+ default:
+ return false;
+ }
+ }
+
+ return false;
+ }
+
+ private unsafe static bool GetGeneralizedTimeStamp(
+ CMSG_SIGNER_INFO* pSignerInfo,
+ SignCounterSign counter)
+ {
+ uint positionFound = 0;
+ uint lengthFound = 0;
+ CRYPT_ATTRIBUTE* attr = null;
+ var res = TryGetUnauthAttr(pSignerInfo, szOID_RFC3161_counterSign, ref attr);
+ if (!res || attr is null)
+ return false;
+
+ var result = ParseDERFindType(
+ 0x04,
+ attr->rgValue[0].pbData,
+ attr->rgValue[0].cbData,
+ ref positionFound,
+ ref lengthFound);
+ if (!result)
+ return false;
+
+ // Counter Signer Timstamp
+ var pbOctetString = attr->rgValue[0].pbData + positionFound;
+ counter.TimeStamp = GetTimeStampFromDER(pbOctetString, lengthFound, ref positionFound);
+
+ return true;
+ }
+
+ private unsafe static string GetTimeStampFromDER(byte* pbOctetString, uint lengthFound, ref uint positionFound)
+ {
+ var result = ParseDERFindType(
+ 0x18,
+ pbOctetString,
+ lengthFound,
+ ref positionFound,
+ ref lengthFound
+ );
+ if (!result)
+ return string.Empty;
+
+ var st = new Windows.Win32.Foundation.SYSTEMTIME();
+ var buffer = new string((sbyte*)(pbOctetString + positionFound));
+
+ _ = ushort.TryParse(buffer.AsSpan(0, 4), out st.wYear);
+ _ = ushort.TryParse(buffer.AsSpan(4, 2), out st.wMonth);
+ _ = ushort.TryParse(buffer.AsSpan(6, 2), out st.wDay);
+ _ = ushort.TryParse(buffer.AsSpan(8, 2), out st.wHour);
+ _ = ushort.TryParse(buffer.AsSpan(10, 2), out st.wMinute);
+ _ = ushort.TryParse(buffer.AsSpan(12, 2), out st.wSecond);
+ _ = ushort.TryParse(buffer.AsSpan(15, 3), out st.wMilliseconds);
+
+ PInvoke.SystemTimeToFileTime(st, out var fft);
+ PInvoke.FileTimeToLocalFileTime(fft, out var lft);
+ PInvoke.FileTimeToSystemTime(lft, out var lst);
+ var timestamp = TimeToString(null, &lst);
+
+ return timestamp;
+ }
+
+ private unsafe static bool GetStringFromCertContext(CERT_CONTEXT* pCertContext, uint dwType, uint flag, CertNodeInfoItem info)
+ {
+ var data = PInvoke.CertGetNameString(pCertContext, dwType, flag, null, (PWSTR)null, 0);
+ if (data == 0)
+ {
+ PInvoke.CertFreeCertificateContext(pCertContext);
+ return false;
+ }
+
+ var pszTempName = (PWSTR)NativeMemory.Alloc(data * sizeof(char));
+ if (pszTempName.Value is null)
+ {
+ PInvoke.CertFreeCertificateContext(pCertContext);
+ NativeMemory.Free(pszTempName);
+ return false;
+ }
+
+ data = PInvoke.CertGetNameString(pCertContext, dwType, flag, null, pszTempName, data);
+ if (data == 0)
+ {
+ NativeMemory.Free(pszTempName);
+ return false;
+ }
+
+ var name = pszTempName.AsSpan().ToString();
+ NativeMemory.Free(pszTempName);
+ if (flag == 0)
+ info.IssuedTo = StripString(name);
+ else
+ info.IssuedBy = StripString(name);
+
+ return true;
+ }
+
+ private unsafe static bool TryGetUnauthAttr(CMSG_SIGNER_INFO* pSignerInfo, string oid, ref CRYPT_ATTRIBUTE* attr)
+ {
+ int n = 0;
+ attr = null;
+ for (; n < pSignerInfo->UnauthAttrs.cAttr; n++)
+ {
+ attr = &pSignerInfo->UnauthAttrs.rgAttr[n];
+ var objId = new string((sbyte*)(byte*)attr->pszObjId);
+ if (objId == oid)
+ break;
+ }
+
+ return n < pSignerInfo->UnauthAttrs.cAttr;
+ }
+
+ private unsafe static bool TryGetAuthAttr(CMSG_SIGNER_INFO* pSignerInfo, string oid, ref CRYPT_ATTRIBUTE* attr)
+ {
+ int n = 0;
+ attr = null;
+ for (; n < pSignerInfo->AuthAttrs.cAttr; n++)
+ {
+ attr = &pSignerInfo->AuthAttrs.rgAttr[n];
+ var objId = new string((sbyte*)(byte*)attr->pszObjId);
+ if (objId == oid)
+ break;
+ }
+
+ return n < pSignerInfo->AuthAttrs.cAttr;
+ }
+
+ private unsafe static bool GetNestedSignerInfo(ref SignDataHandle AuthSignData, List NestedChain)
+ {
+ var succeded = false;
+ void* hNestedMsg = null;
+ if (AuthSignData.pSignerInfo is null)
+ return false;
+
+ try
+ {
+ CRYPT_ATTRIBUTE* attr = null;
+ var res = TryGetUnauthAttr(AuthSignData.pSignerInfo, szOID_NESTED_SIGNATURE, ref attr);
+ if (!res || attr is null)
+ return false;
+
+ var cbCurrData = attr->rgValue[0].cbData;
+ var pbCurrData = attr->rgValue[0].pbData;
+ var upperBound = AuthSignData.pSignerInfo + AuthSignData.dwObjSize;
+ while (pbCurrData > AuthSignData.pSignerInfo && pbCurrData < upperBound)
+ {
+ var nestedHandle = new SignDataHandle() { dwObjSize = 0, pSignerInfo = null, hCertStoreHandle = HCERTSTORE.Null };
+ if (!Memcmp(pbCurrData, SG_ProtoCoded) ||
+ !Memcmp(pbCurrData + 6, SG_SignedData))
+ {
+ break;
+ }
+
+ hNestedMsg = PInvoke.CryptMsgOpenToDecode(
+ PKCS_7_ASN_ENCODING | CRYPT_ASN_ENCODING,
+ 0,
+ 0,
+ HCRYPTPROV_LEGACY.Null,
+ null,
+ null
+ );
+ if (hNestedMsg is null)
+ return false;
+
+ cbCurrData = XCHWordLitend(*(ushort*)(pbCurrData + 2)) + 4u;
+ var pbNextData = pbCurrData;
+ pbNextData += EightByteAlign(cbCurrData, (long)pbCurrData);
+ var result = PInvoke.CryptMsgUpdate(hNestedMsg, pbCurrData, cbCurrData, true);
+ pbCurrData = pbNextData;
+ if (!result)
+ continue;
+
+ var pSignerInfo = (void*)nestedHandle.pSignerInfo;
+ result = CustomCryptMsgGetParam(
+ hNestedMsg,
+ CMSG_SIGNER_INFO_PARAM,
+ 0,
+ ref pSignerInfo,
+ ref nestedHandle.dwObjSize
+ );
+ nestedHandle.pSignerInfo = (CMSG_SIGNER_INFO*)pSignerInfo;
+ if (!result)
+ continue;
+
+ var cert_store_prov_msg = (PCSTR)(byte*)1;
+ nestedHandle.hCertStoreHandle = PInvoke.CertOpenStore(
+ cert_store_prov_msg,
+ ENCODING,
+ HCRYPTPROV_LEGACY.Null,
+ 0,
+ hNestedMsg
+ );
+
+ succeded = true;
+ NestedChain.Add(nestedHandle);
+ }
+ }
+ finally
+ {
+ if (hNestedMsg is not null)
+ PInvoke.CryptMsgClose(hNestedMsg);
+ }
+
+ return succeded;
+ }
+
+ private unsafe static bool CustomCryptMsgGetParam(
+ void* hCryptMsg,
+ uint paramType,
+ uint index,
+ ref void* pParam,
+ ref uint outSize)
+ {
+ bool result;
+ uint size = 0;
+
+ result = PInvoke.CryptMsgGetParam(
+ hCryptMsg,
+ paramType,
+ index,
+ null,
+ ref size
+ );
+ if (!result)
+ return false;
+
+ pParam = NativeMemory.Alloc(size);
+ if (pParam is null)
+ return false;
+
+ result = PInvoke.CryptMsgGetParam(
+ hCryptMsg,
+ paramType,
+ index,
+ pParam,
+ ref size
+ );
+ if (!result)
+ return false;
+
+ outSize = size;
+ return true;
+ }
+
+ private static ushort XCHWordLitend(uint num)
+ => (ushort)(((((ushort)num) & 0xFF00) >> 8) | (((ushort)num) & 0x00FF) << 8);
+
+ private static long EightByteAlign(long offset, long b)
+ => ((offset + b + 7) & 0xFFFFFFF8L) - (b & 0xFFFFFFF8L);
+
+ private unsafe static bool Memcmp(byte* ptr1, byte[] arr)
+ {
+ for (var i = 0; i < arr.Length; i++)
+ {
+ if (ptr1[i] != arr[i])
+ return false;
+ }
+
+ return true;
+ }
+
+ private static (bool, string) CalculateSignVersion(uint versionNumber)
+ {
+ var res = versionNumber switch
+ {
+ CERT_V1 => "V1",
+ CERT_V2 => "V2",
+ CERT_V3 => "V3",
+ _ => "Unknown",
+ };
+ return (true, res);
+ }
+
+ private static bool CalculateDigestAlgorithm(string pszObjId, SignNodeInfo info)
+ {
+ if (string.IsNullOrWhiteSpace(pszObjId))
+ info.DigestAlgorithm = "Unknown";
+ else if (pszObjId == szOID_OIWSEC_sha1)
+ info.DigestAlgorithm = "SHA1";
+ else if (pszObjId == szOID_RSA_MD5)
+ info.DigestAlgorithm = "MD5";
+ else if (pszObjId == szOID_NIST_sha256)
+ info.DigestAlgorithm = "SHA256";
+ else
+ info.DigestAlgorithm = StripString(pszObjId);
+
+ return true;
+ }
+
+ private static bool SafeToReadNBytes(uint size, uint start, uint requestSize)
+ => size - start >= requestSize;
+
+ private static void ParseDERType(byte bIn, ref int iType, ref int iClass)
+ {
+ iType = bIn & 0x3F;
+ iClass = bIn >> 6;
+ }
+
+ private unsafe static uint ReadNumberFromNBytes(byte* pbSignature, uint start, uint requestSize)
+ {
+ uint number = 0;
+ for (var i = 0; i < requestSize; i++)
+ number = number * 0x100 + pbSignature[start + i];
+
+ return number;
+ }
+
+ private unsafe static bool ParseDERSize(byte* pbSignature, uint size, ref uint sizeFound, ref uint bytesParsed)
+ {
+ if (pbSignature[0] > 0x80 && !SafeToReadNBytes(size, 1, pbSignature[0] - 0x80u))
+ return false;
+
+ if (pbSignature[0] <= 0x80)
+ {
+ sizeFound = pbSignature[0];
+ bytesParsed = 1;
+ }
+ else
+ {
+ sizeFound = ReadNumberFromNBytes(pbSignature, 1, pbSignature[0] - 0x80u);
+ bytesParsed = pbSignature[0] - 0x80u + 1;
+ }
+
+ return true;
+ }
+
+ private static string StripString(string? str)
+ {
+ return str?
+ .Replace("\t", "")?
+ .Replace("\n", "")?
+ .Replace("\r", "")?
+ .Replace(((char)0).ToString(), "") ?? string.Empty;
+ }
+
+ private unsafe static string TimeToString(
+ System.Runtime.InteropServices.ComTypes.FILETIME* pftIn,
+ Windows.Win32.Foundation.SYSTEMTIME* pstIn = null)
+ {
+ if (pstIn is null)
+ {
+ if (pftIn is null)
+ return string.Empty;
+
+ PInvoke.FileTimeToSystemTime(*pftIn, out var sysTime);
+ pstIn = &sysTime;
+ }
+
+ var date = new DateTime(
+ pstIn->wYear, pstIn->wMonth, pstIn->wDay,
+ pstIn->wHour, pstIn->wMinute, pstIn->wSecond
+ );
+
+ return formatter.ToLongLabel(date);
+ }
+
+ class SignCounterSign
+ {
+ public string TimeStamp { get; set; } = string.Empty;
+ }
+
+ class SignNodeInfo
+ {
+ public bool IsValid { get; set; } = false;
+ public string DigestAlgorithm { get; set; } = string.Empty;
+ public string Version { get; set; } = string.Empty;
+ public int Index { get; set; } = 0;
+ public SignCounterSign CounterSign { get; set; } = new();
+ public List CertChain { get; set; } = [];
+ }
+ }
+}
diff --git a/src/Files.App/ViewModels/Properties/MainPropertiesViewModel.cs b/src/Files.App/ViewModels/Properties/MainPropertiesViewModel.cs
index c95b9b60b056..c08207483881 100644
--- a/src/Files.App/ViewModels/Properties/MainPropertiesViewModel.cs
+++ b/src/Files.App/ViewModels/Properties/MainPropertiesViewModel.cs
@@ -40,7 +40,8 @@ public NavigationViewItemButtonStyleItem SelectedNavigationViewItem
PropertiesNavigationViewItemType.Security => typeof(SecurityPage),
PropertiesNavigationViewItemType.Customization => typeof(CustomizationPage),
PropertiesNavigationViewItemType.Compatibility => typeof(CompatibilityPage),
- PropertiesNavigationViewItemType.Hashes => typeof(HashesPage),
+ PropertiesNavigationViewItemType.Hashes => typeof(HashesPage),
+ PropertiesNavigationViewItemType.Signatures => typeof(SignaturesPage),
_ => typeof(GeneralPage),
};
diff --git a/src/Files.App/ViewModels/Properties/SignaturesViewModel.cs b/src/Files.App/ViewModels/Properties/SignaturesViewModel.cs
new file mode 100644
index 000000000000..4dc1200a76e0
--- /dev/null
+++ b/src/Files.App/ViewModels/Properties/SignaturesViewModel.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using Files.App.Utils.Signatures;
+using Microsoft.UI.Windowing;
+using Windows.Win32.Foundation;
+
+namespace Files.App.ViewModels.Properties
+{
+ public sealed partial class SignaturesViewModel : ObservableObject, IDisposable
+ {
+ private CancellationTokenSource _cancellationTokenSource;
+
+ public ObservableCollection Signatures { get; set; }
+
+ public bool NoSignatureFound => Signatures.Count == 0;
+
+ public SignaturesViewModel(ListedItem item, AppWindow appWindow)
+ {
+ _cancellationTokenSource = new();
+ Signatures = new();
+ var hWnd = new HWND(Microsoft.UI.Win32Interop.GetWindowFromWindowId(appWindow.Id));
+ Signatures.CollectionChanged += (s, e) => OnPropertyChanged(nameof(NoSignatureFound));
+ DigitalSignaturesUtil.LoadItemSignatures(
+ item.ItemPath,
+ Signatures,
+ hWnd,
+ _cancellationTokenSource.Token
+ );
+ }
+
+ public void Dispose()
+ {
+ _cancellationTokenSource.Cancel();
+ }
+ }
+}
diff --git a/src/Files.App/ViewModels/Settings/AboutViewModel.cs b/src/Files.App/ViewModels/Settings/AboutViewModel.cs
index 13ae41d4f00b..e84cc061182a 100644
--- a/src/Files.App/ViewModels/Settings/AboutViewModel.cs
+++ b/src/Files.App/ViewModels/Settings/AboutViewModel.cs
@@ -79,6 +79,7 @@ public AboutViewModel()
new ("https://github.com/microsoft/CsWinRT", "CsWinRT"),
new ("https://github.com/GihanSoft/NaturalStringComparer", "NaturalStringComparer"),
new ("https://github.com/dongle-the-gadget/GuidRVAGen", "Dongle.GuidRVAGen"),
+ new ("https://github.com/leeqwind/PESignAnalyzer", "PESignAnalyzer"),
];
CopyAppVersionCommand = new RelayCommand(CopyAppVersion);
diff --git a/src/Files.App/Views/Properties/SignaturesPage.xaml b/src/Files.App/Views/Properties/SignaturesPage.xaml
new file mode 100644
index 000000000000..37c51a6ac4ea
--- /dev/null
+++ b/src/Files.App/Views/Properties/SignaturesPage.xaml
@@ -0,0 +1,210 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Files.App/Views/Properties/SignaturesPage.xaml.cs b/src/Files.App/Views/Properties/SignaturesPage.xaml.cs
new file mode 100644
index 000000000000..74e30800dcd2
--- /dev/null
+++ b/src/Files.App/Views/Properties/SignaturesPage.xaml.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using Files.App.ViewModels.Properties;
+using Microsoft.UI.Xaml.Navigation;
+
+namespace Files.App.Views.Properties
+{
+ public sealed partial class SignaturesPage : BasePropertiesPage
+ {
+ private SignaturesViewModel SignaturesViewModel { get; set; }
+
+ public SignaturesPage()
+ {
+ InitializeComponent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ var np = (PropertiesPageNavigationParameter)e.Parameter;
+ if (np.Parameter is ListedItem listedItem)
+ SignaturesViewModel = new(listedItem, np.Window.AppWindow);
+
+ base.OnNavigatedTo(e);
+ }
+
+ protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
+ => Dispose();
+
+ public override Task SaveChangesAsync()
+ {
+ return Task.FromResult(true);
+ }
+
+ public override void Dispose()
+ {
+ SignaturesViewModel.Dispose();
+ }
+ }
+}
diff --git a/src/Files.App/Views/Settings/AboutPage.xaml b/src/Files.App/Views/Settings/AboutPage.xaml
index 52a8b3eb841e..aa9f30a7ec96 100644
--- a/src/Files.App/Views/Settings/AboutPage.xaml
+++ b/src/Files.App/Views/Settings/AboutPage.xaml
@@ -188,7 +188,7 @@
public static class FileExtensionHelpers
{
+ private static readonly FrozenSet _signableTypes = new HashSet()
+ {
+ ".aab", ".apk", ".application", ".appx", ".appxbundle", ".arx", ".cab", ".cat", ".cbx",
+ ".cpl", ".crx", ".dbx", ".deploy", ".dll", ".doc", ".docm", ".dot", ".dotm", ".drx",
+ ".ear", ".efi", ".exe", ".jar", ".js", ".manifest", ".mpp", ".mpt", ".msi", ".msix",
+ ".msixbundle", ".msm", ".msp", ".nupkg", ".ocx", ".pot", ".potm", ".ppa", ".ppam", ".pps",
+ ".ppsm", ".ppt", ".pptm", ".ps1", ".psm1", ".psi", ".pub", ".sar", ".stl", ".sys", ".vbs",
+ ".vdw", ".vdx", ".vsd", ".vsdm", ".vss", ".vssm", ".vst", ".vstm", ".vsto", ".vsix", ".vsx", ".vtx",
+ ".vxd", ".war", ".wiz", ".wsf", ".xap", ".xla", ".xlam", ".xls", ".xlsb", ".xlsm", ".xlt",
+ ".xltm", ".xlsm", ".xsn"
+ }.ToFrozenSet();
+
///
/// Check if the file extension matches one of the specified extensions.
///
@@ -273,5 +287,20 @@ public static bool IsSystemFile(string? filePathToCheck)
return HasExtension(filePathToCheck, ".dll", ".exe", ".sys", ".inf");
}
+ ///
+ /// Check if the file is signable.
+ ///
+ ///
+ /// true if the filePathToCheck is a signable file; otherwise, false.
+ public static bool IsSignableFile(string? filePathToCheck, bool isExtension = false)
+ {
+ if (string.IsNullOrWhiteSpace(filePathToCheck))
+ return false;
+
+ if (!isExtension)
+ filePathToCheck = Path.GetExtension(filePathToCheck);
+
+ return _signableTypes.Contains(filePathToCheck);
+ }
}
}