在 InstallShield 安装期间导入 SSL 证书后,无法以编程方式将其与 IIS 中的绑定相关联

Posted

技术标签:

【中文标题】在 InstallShield 安装期间导入 SSL 证书后,无法以编程方式将其与 IIS 中的绑定相关联【英文标题】:Unable to associate an SSL certificate with a binding in IIS programmatically after importing it during an InstallShield installation 【发布时间】:2021-12-09 12:00:37 【问题描述】:

我有一个 InstallShield 安装,可将网站部署到 Windows 机器上的 IIS。它始终使用 HTTP 绑定部署网站。我正在添加对使用 HTTPS 部署网站的支持,但在所有情况下都很难让它工作。我允许用户从本地计算机的“个人”或“虚拟主机”存储中选择已安装的证书,或者从文件系统中选择 PFX 文件并提供密码。无论哪种方式,我都获得了证书的指纹。只要用户选择商店中已有的 SSL 证书,它就可以按预期工作。但是,如果用户选择选择 PFX 文件并输入密码,我可以成功地将证书导入“Web Hosting”存储,但是当我尝试添加 https 绑定并将证书关联到它时,我收到提交更改时出现以下异常:

“指定的登录会话不存在。它可能已经终止。(来自 HRESULT 的异常:0x80070520)”

https 绑定已成功添加,即使我尝试使用 IIS 关联证书,我也会收到相同的消息,这告诉我它可能与导入过程有关,而不是与绑定过程有关。 我在 C# 中的托管库中实现了一些方法,并将库包含在安装程序项目中,并在 InstallShield 项目中创建了 CustomAction 来调用这些方法。所有 CustomAction 在系统上下文中以延迟模式运行,因此它具有管理员权限。我有大量的日志输出用于故障排除,并且可以看到一切都没有问题,直到在将 https 绑定添加到网站时调用 CommitChanges() 方法为止。 这是我的代码。我已经尝试了大约 10 种变体,因为我在网上找到了不同的潜在解决方案,但没有一个是 100% 成功的。当前状态是我迄今为止最成功的状态(FindCertificate() 方法返回一个 X509Certificate2 对象和找到它的商店的名称,并且工作正常):

/// <summary>
/// Imports a SSL certificate from a file with a password to the specified store on the Local Machine
/// </summary>
/// <param name="customActionData">Semi-colon delimited list containing the certificate path/filename, password, and store name</param>
/// <returns>
/// 1=Success; 0=Invalid arguments; -1=Cert file not found; -100=Exception occurred
/// </returns>
public static Int32 ImportCertificateFromFile(string customActionData)

    var args = customActionData.Split(new char[]  ';' );
    if (args.Length != 3)
        return 0;

    var certFile = args[0].Replace("\\", "\\\\");
    var password = args[1];
    var storeName = args[2];

    if (string.IsNullOrEmpty(certFile) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(storeName))
        return 0;

    if (System.IO.File.Exists(certFile))
    
        try
        
            using (var store = new X509Store(storeName, StoreLocation.LocalMachine))
            
                store.Open(OpenFlags.ReadWrite);
                var certificate = new X509Certificate2(certFile, password);
                store.Add(certificate);
                return 1;
            
        
        catch (Exception ex)
        
            return -100;
        
    
    return -1;


/// <summary>
/// Adds a binding to a website
/// </summary>
/// <param name="customActionData">Semi-colon delimited list containing the website name, IP address, port, host header, protocol, and certificate thumbprint for the binding to add</param>
/// <returns>
/// 1=Success; 0=Invalid arguments; -1=Website not found; -100=Exception occurred
/// </returns>
public static Int32 AddWebsiteBinding(string customActionData)

    var args = customActionData.Split(new char[]  ';' );
    if (args.Length != 6)
        return 0;

    var siteName = args[0];
    var ipAddress = args[1];
    var port = args[2];
    var hostHeader = args[3];
    var protocol = args[4].ToLower();
    var thumbprint = args[5];

    var ret = 1;
    var serverManager = new ServerManager();
    var webSite = serverManager.Sites.FirstOrDefault(s => s.Name == siteName);
    if (!(webSite is null))
    
        try
        
            var binding = webSite.Bindings.CreateElement("binding");
            binding.Protocol = protocol;
            binding.BindingInformation = $"ipAddress:port:hostHeader";

            // add SSL certificate if thumbprint was provided
            if (!string.IsNullOrEmpty(thumbprint))
            
                var (cert, storeName) = FindCertificate(thumbprint);

                if (!(cert is null))
                
                    binding.CertificateHash = cert.GetCertHash();
                    binding.CertificateStoreName = storeName;
                
            

            webSite.Bindings.Add(binding);
            serverManager.CommitChanges();
        
        catch (Exception ex)
        
            ret = -100;
        
    
    else
        ret = -1;

    return ret;

我用于测试的证书是在进行测试的机器上使用 IIS 生成的自签名证书,然后将证书导出到 .pfx 文件并提供密码。我曾尝试在“个人”和“虚拟主机”商店中创建证书,但在导出到 .pfx 文件并导入“虚拟主机”商店后,我看不出有任何区别。

【问题讨论】:

【参考方案1】:

我不确定这是否能解决您的问题,但我在一些代码中遇到了同样的错误,我们通过应用程序使用证书设置本地开发环境的网站以帮助我们快速开始。这似乎是因为我们的证书在创建网站时没有正确设置。在我的情况下,我必须先通过 certlm 删除证书,以确保新证书正确导入代码。

然后,在我们导入证书的代码中,我们按以下方式设置密钥存储标志(假设您的证书是使用允许导出的选项创建的):

// Certificate needs to be exportable with the private key to import into IIS
var keyStorageFlags = X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet;

// Store location and key storage flag need to match to import into IIS - this is for our developer environments, probably not for your installshield scenario
if (storeLocation == StoreLocation.LocalMachine)

    keyStorageFlags |= X509KeyStorageFlags.MachineKeySet;

它似乎会针对您的场景调查这些 X509KeyStorageFlags 枚举选项...这适用于我们的 pfx 文件,但您的可能不是使用相同的设置创建的。

【讨论】:

以上是关于在 InstallShield 安装期间导入 SSL 证书后,无法以编程方式将其与 IIS 中的绑定相关联的主要内容,如果未能解决你的问题,请参考以下文章

Wix - 如何创建一个临时文件以在安装期间使用

InstallShield 2011 未从服务列表中删除服务

InstallShield - 在升级期间防止修补程序覆盖注册表值?

InstallShield 先决条件(错误 -7067)

如何在MSI Installshield重新启动管理器对话框中禁用“不关闭应用程序”选项

MSI 未卸载 .dll 文件