使用 C# / .NetCore 签署 Powershell 脚本

问题描述 投票:0回答:2

对于我的复杂系统,我正在寻找一种方法来签署 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 进行签名?

  1. 我的系统是一个内联网服务
  2. 用户可以从应用程序中的片段编译脚本并选择其他安装文件
  3. 现在脚本已签名并与安装文件一起编译为 exe(C# 控制台项目 - 使用 dotnet 在运行时编译)
  4. 这个exe可以由用户控制(exe有开关来检查更新)
  5. 现在此 exe(更新)通过 wsus 分发并到达用户计算机并使用脚本安装安装文件。

这一切都是一个不错的系统,但如果我不必先保存脚本来签名,那就更好了

进程启动=> powershell

正如您在上面所读到的,我不是在找人来做我的作业,而是在寻找有足够经验的人来告诉我如何在不使用 powershellpipe 的情况下使用 C# 签署脚本。

非常感谢
最好的问候

c# powershell .net-core
2个回答
2
投票

现在我投入了更多时间并弄清楚了底层技术,我在 GitHub 上的 Microsoft 代码中找到了解决方案。

到目前为止,stackoverflow 上也声称这是不可能的。

这就是他们是如何做到的 https://github.com/PowerShell/PowerShell/blob/d8f8f0a8bcbadb357f9eaafbb797278ebe07d7cc/src/System.Management.Automation/security/Authenticode.cs

在此文件中,您可以找到具有以下功能的签名帮助器类:

internal static Signature SignFile(SigningOption option,
                                           string fileName,
                                           X509Certificate2 certificate,
                                           string timeStampServerUrl,
                                           string hashAlgorithm)

抱歉,如果有人介意,我很快就找到了解决方案。至少现在我已经分享了。

编辑:

由于底层功能的原因,文件还是要保存的 第一的。所以优势微乎其微。


0
投票

根据 @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 实现的(经过大量改编)版本。在我自己的粗略测试中,它似乎可以完成这项工作,但显然不言而喻,它绝对没有保修,应该谨慎对待。我希望下一个人遇到这个帖子时可以付出一些努力。

© www.soinside.com 2019 - 2024. All rights reserved.