对于我的复杂系统,我正在寻找一种方法来签署 powershell 脚本,而不是使用 powershell 而是使用 C# / .NetCore
这里是 Powershell 版本:
$cert=Get-ChildItem -Path Cert:\CurrentUser\My -CodeSigningCert
Set-AuthenticodeSignature -FilePath PsTestInternet2.ps1 -Certificate $cert
我当前的解决方案使用 启动 Powershell 控制台
process.start
为什么我想使用 C# 而不是 Powershell 进行签名?
这一切都是一个不错的系统,但如果我不必先保存脚本来签名,那就更好了
进程启动=> powershell
正如您在上面所读到的,我不是在找人来做我的作业,而是在寻找有足够经验的人来告诉我如何在不使用 powershellpipe 的情况下使用 C# 签署脚本。
非常感谢
最好的问候
现在我投入了更多时间并弄清楚了底层技术,我在 GitHub 上的 Microsoft 代码中找到了解决方案。
到目前为止,stackoverflow 上也声称这是不可能的。
在此文件中,您可以找到具有以下功能的签名帮助器类:
internal static Signature SignFile(SigningOption option,
string fileName,
X509Certificate2 certificate,
string timeStampServerUrl,
string hashAlgorithm)
抱歉,如果有人介意,我很快就找到了解决方案。至少现在我已经分享了。
编辑:
由于底层功能的原因,文件还是要保存的 第一的。所以优势微乎其微。
根据 @RoXTar 的答案,我得出以下结论:
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
// https://stackoverflow.com/a/63408137/2911871
// Adapted from https://github.com/PowerShell/PowerShell/blob/d8f8f0a8bcbadb357f9eaafbb797278ebe07d7cc/src/System.Management.Automation/security/Authenticode.cs
internal static partial class SignatureHelper
{
private const string CODE_SIGNING_OID = @"1.3.6.1.5.5.7.3.3";
private const int CRYPT_OID_INFO_NAME_KEY = 2;
private const uint CRYPTUI_WIZ_NO_UI = 0x0001;
private const uint CRYPTUI_WIZ_DIGITAL_SIGN_SUBJECT_FILE = 0x01;
private const uint CRYPTUI_WIZ_DIGITAL_SIGN_CERT = 0x01;
/// <summary>
/// Defines the options that control what data is embedded in the
/// signature blob.
/// </summary>
public enum SigningOption
{
/// <summary>
/// Embeds only the signer's certificate.
/// </summary>
AddOnlyCertificate,
/// <summary>
/// Embeds the entire certificate chain.
/// </summary>
AddFullCertificateChain,
/// <summary>
/// Embeds the entire certificate chain, except for the root
/// certificate.
/// </summary>
AddFullCertificateChainExceptRoot,
/// <summary>
/// Default: Embeds the entire certificate chain, except for the
/// root certificate.
/// </summary>
Default = AddFullCertificateChainExceptRoot
}
/// <summary>
/// Sign a file.
/// </summary>
/// <param name="fileName">Name of file to sign.</param>
/// <param name="certificate">Signing cert.</param>
/// <param name="option">Option that controls what gets embedded in the signature blob.</param>
/// <param name="timeStampServerUrl">URL of time stamping server.</param>
/// <param name="hashAlgorithm">The name of the hash algorithm to use.</param>
/// <exception cref="System.ArgumentNullException">Thrown if argument fileName or certificate is null.</exception>
/// <exception cref="System.IO.FileNotFoundException">Thrown if the file specified by argument fileName is not found</exception>
/// <exception cref="System.ArgumentException">Thrown if
/// -- argument fileName is empty OR
/// -- the specified certificate is not suitable for
/// signing code OR
/// -- hash algorithm was not valid</exception>
/// <exception cref="System.Security.Cryptography.CryptographicException">This exception can be thrown if any cryptographic error occurs.
/// It is not possible to know exactly what went wrong.
/// This is because of the way CryptographicException is designed.
/// Possible reasons:
/// -- certificate is invalid
/// -- certificate has no private key
/// -- certificate password mismatch
/// -- etc</exception>
public static void SignFile(string fileName, X509Certificate2 certificate, SigningOption option = SigningOption.Default, string? timeStampServerUrl = null, string? hashAlgorithm = null)
{
ArgumentException.ThrowIfNullOrEmpty(fileName, nameof(fileName));
ArgumentNullException.ThrowIfNull(certificate, nameof(certificate));
if (!File.Exists(fileName))
{
throw new FileNotFoundException("File not found", fileName);
}
if (!IsCodeSigningCertificate(certificate))
{
throw new ArgumentException("This certificate is not suitable for code signing", nameof(certificate));
}
IntPtr pSignInfo = IntPtr.Zero;
string? hashOid = null;
// Validate that the hash algorithm is valid
if (!string.IsNullOrEmpty(hashAlgorithm))
{
IntPtr intptrAlgorithm = Marshal.StringToHGlobalUni(hashAlgorithm);
IntPtr oidPtr = CryptFindOIDInfo(CRYPT_OID_INFO_NAME_KEY, intptrAlgorithm, 0);
// If we couldn't find an OID for the hash
// algorithm, it was invalid.
if (oidPtr == IntPtr.Zero)
{
throw new ArgumentException($"Invalid hash algorithm '{hashAlgorithm}'");
}
else
{
hashOid = Marshal.PtrToStructure<CRYPT_OID_INFO>(oidPtr).pszOID;
}
}
try
{
// CryptUI is not documented either way, but does not
// support empty strings for the timestamp server URL.
// It expects null, only. Instead, it randomly AVs if you
// try.
if (string.IsNullOrEmpty(timeStampServerUrl))
{
timeStampServerUrl = null;
}
// First initialize the struct to pass to
// CryptUIWizDigitalSign() function
CRYPTUI_WIZ_DIGITAL_SIGN_INFO si = InitSignInfoStruct(fileName, certificate, timeStampServerUrl, hashOid, option);
pSignInfo = Marshal.AllocCoTaskMem(Marshal.SizeOf(si));
Marshal.StructureToPtr(si, pSignInfo, false);
// Sign the file
var result = CryptUIWizDigitalSign(CRYPTUI_WIZ_NO_UI, IntPtr.Zero, IntPtr.Zero, pSignInfo, IntPtr.Zero);
if (si.pSignExtInfo != IntPtr.Zero)
{
Marshal.DestroyStructure<CRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO>(si.pSignExtInfo);
Marshal.FreeCoTaskMem(si.pSignExtInfo);
}
if (!result)
{
throw new CryptographicException($"Signing operation failed with error code {Marshal.GetLastWin32Error()}");
}
}
finally
{
Marshal.DestroyStructure<CRYPTUI_WIZ_DIGITAL_SIGN_INFO>(pSignInfo);
Marshal.FreeCoTaskMem(pSignInfo);
}
}
/// <summary>
/// Returns a value indicating whether or not the enhanced key usage list for this certificate
/// contains the 'Code Signing' OID.
/// </summary>
/// <param name="certificate">The certificate.</param>
/// <returns>
/// <c>true</c> if [is code signing certificate]; otherwise, <c>false</c>.
/// </returns>
public static bool IsCodeSigningCertificate(X509Certificate2 certificate)
{
foreach (var extension in certificate.Extensions.OfType<X509EnhancedKeyUsageExtension>())
{
if (extension.EnhancedKeyUsages.Cast<Oid>().Any(oid => string.Equals(oid.Value, CODE_SIGNING_OID)))
{
return true;
}
}
return false;
}
private static CRYPTUI_WIZ_DIGITAL_SIGN_INFO InitSignInfoStruct(string fileName, X509Certificate2 signingCert, string? timeStampServerUrl, string? hashAlgorithm, SigningOption option)
{
CRYPTUI_WIZ_DIGITAL_SIGN_INFO si = new();
si.dwSize = (uint)Marshal.SizeOf(si);
si.dwSubjectChoice = CRYPTUI_WIZ_DIGITAL_SIGN_SUBJECT_FILE;
si.pwszFileName = fileName;
si.dwSigningCertChoice = CRYPTUI_WIZ_DIGITAL_SIGN_CERT;
si.pSigningCertContext = signingCert.Handle;
si.pwszTimestampURL = timeStampServerUrl;
si.dwAdditionalCertChoice = GetCertChoiceFromSigningOption(option);
CRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO siex = InitSignInfoExtendedStruct(hashAlgorithm);
IntPtr pSiexBuffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(siex));
Marshal.StructureToPtr(siex, pSiexBuffer, false);
si.pSignExtInfo = pSiexBuffer;
return si;
}
private static CRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO InitSignInfoExtendedStruct(string? hashAlgorithm)
{
CRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO siex = new();
siex.dwSize = (uint)Marshal.SizeOf(siex);
siex.dwAttrFlagsNotUsed = 0;
siex.pwszDescription = string.Empty;
siex.pwszMoreInfoLocation = string.Empty;
siex.pszHashAlg = null;
siex.pwszSigningCertDisplayStringNotUsed = IntPtr.Zero;
siex.hAdditionalCertStoreNotUsed = IntPtr.Zero;
siex.psAuthenticatedNotUsed = IntPtr.Zero;
siex.psUnauthenticatedNotUsed = IntPtr.Zero;
if (hashAlgorithm != null)
{
siex.pszHashAlg = hashAlgorithm;
}
return siex;
}
private static uint GetCertChoiceFromSigningOption(SigningOption option) => option switch
{
SigningOption.AddOnlyCertificate => 0,
SigningOption.AddFullCertificateChain => 1,
SigningOption.AddFullCertificateChainExceptRoot => 2,
_ => 2,
};
[StructLayout(LayoutKind.Sequential)]
private struct CRYPTUI_WIZ_DIGITAL_SIGN_INFO
{
internal uint dwSize;
internal uint dwSubjectChoice;
[MarshalAs(UnmanagedType.LPWStr)]
internal string pwszFileName;
internal uint dwSigningCertChoice;
internal IntPtr pSigningCertContext; // PCCERT_CONTEXT
[MarshalAs(UnmanagedType.LPWStr)]
internal string? pwszTimestampURL;
internal uint dwAdditionalCertChoice;
internal IntPtr pSignExtInfo; // PCCRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO
}
[StructLayout(LayoutKind.Sequential)]
private struct CRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO
{
internal uint dwSize;
internal uint dwAttrFlagsNotUsed;
[MarshalAs(UnmanagedType.LPWStr)]
internal string pwszDescription;
[MarshalAs(UnmanagedType.LPWStr)]
internal string pwszMoreInfoLocation;
[MarshalAs(UnmanagedType.LPStr)]
internal string? pszHashAlg;
internal IntPtr pwszSigningCertDisplayStringNotUsed; // LPCWSTR
internal IntPtr hAdditionalCertStoreNotUsed; // HCERTSTORE
internal IntPtr psAuthenticatedNotUsed; // PCRYPT_ATTRIBUTES
internal IntPtr psUnauthenticatedNotUsed; // PCRYPT_ATTRIBUTES
}
[StructLayout(LayoutKind.Sequential)]
private struct CRYPT_OID_INFO
{
/// System.UInt32->unsigned int
public uint cbSize;
/// LPCSTR->CHAR*
[MarshalAs(UnmanagedType.LPStr)]
public string pszOID;
/// LPCWSTR->WCHAR*
[MarshalAs(UnmanagedType.LPWStr)]
public string pwszName;
/// System.UInt32->unsigned int
public uint dwGroupId;
}
[LibraryImport("cryptUI.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool CryptUIWizDigitalSign(uint dwFlags, IntPtr hwndParentNotUsed, IntPtr pwszWizardTitleNotUsed, IntPtr pDigitalSignInfo, IntPtr ppSignContextNotUsed);
[LibraryImport("crypt32.dll", EntryPoint = "CryptFindOIDInfo")]
private static partial IntPtr CryptFindOIDInfo(uint dwKeyType, IntPtr pvKey, uint dwGroupId);
}
根据顶部的评论 - 这本质上是 PowerShell 实现的(经过大量改编)版本。在我自己的粗略测试中,它似乎可以完成这项工作,但显然不言而喻,它绝对没有保修,应该谨慎对待。我希望下一个人遇到这个帖子时可以付出一些努力。