X509Certificate构造函数异常

82
//cert is an EF Entity and 
//    cert.CertificatePKCS12 is a byte[] with the certificate.

var certificate = new X509Certificate(cert.CertificatePKCS12, "SomePassword");

当从我们的数据库加载证书时,在我们的测试服务器(Windows 2008 R2/IIS7.5)上,我们遇到了以下异常:

System.Security.Cryptography.CryptographicException: An internal error occurred.

   at System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32 hr)
   at System.Security.Cryptography.X509Certificates.X509Utils._LoadCertFromBlob(Byte[] rawData, IntPtr password, UInt32 dwFlags, Boolean persistKeySet, SafeCertContextHandle& pCertCtx)
   at System.Security.Cryptography.X509Certificates.X509Certificate.LoadCertificateFromBlob(Byte[] rawData, Object password, X509KeyStorageFlags keyStorageFlags)

注意: 这个问题在本地环境下(Windows 7/Casini)并不会出现。

非常感谢任何有关此问题的见解。


请查看以下链接:https://dev59.com/NHM_5IYBdhLWcg3wgzhl 和 https://dev59.com/VlfUa4cB1Zd3GeqPIXpg - alan
1
我猜问题的根源在于cert.CertificatePKCS12中的byte[] data。如果没有数据,我们只能猜测异常“发生了内部错误”的原因。因此,我的建议是您创建测试证书,在您的环境中使用它来重现问题,将其保存在文件中,并提供链接和密码(如“SomePassword”)以解码证书。检查数据后,将更有机会找到原因并提出解决方案。 - Oleg
感谢您的回复,@Oleg - 如果字节数组有问题,那么在Win7和Win2k8上都会出错吧?当将字节数组写入文件时,它可以正确导入。 - lukiffer
@lukiffer:我的意思不是错误,而是证书、密钥等属性的组合。因此,必须分析问题。为了能够重现结果或分析它,需要使用作为字节数组cert.CertificatePKCS12的PFX文件。 - Oleg
@oleg - 什么属性会导致它在一个操作系统上失败而在另一个操作系统上不失败?由于明显的安全原因,我们无法发布证书本身。 - lukiffer
显示剩余2条评论
9个回答

140

原来在IIS应用程序池配置(应用程序池 > 高级设置)中有一个设置,可以加载应用程序池用户的用户配置文件。 当设置为false时,密钥容器是不可访问的。

因此,只需将 Load User Profile 选项设置为 True 即可。

应用程序池->高级设置屏幕


请注意,这是针对IIS7的;在IIS6中,我认为没有加载用户配置文件的选项。 - Randy Levy
2
我正在尝试在Azure上进行此操作,但我不知道是否可以更改“加载用户配置文件”选项。有什么建议吗?我对证书的世界仍然比较陌生... - Danimal111
3
为 Azure 添加应用设置:WEBSITE_LOAD_USER_PROFILE=1。 - emp
将“设置LoadUserProfile为True”仅适用于用户帐户,而不适用于ApplicationPoolIdentity和NetworkService。 - Thabiso Mofokeng
2
我不知道你是谁,但还是谢谢你!!!现在已经是凌晨3点40分了,你可以想象剩下的事情... - Frijey Labs

69

很可能,当您从Visual Studio/Cassini运行时,它正在访问您的用户证书存储,即使您是通过字节加载它。请尝试这个方法,看看是否解决了您的问题:

var certificate = new X509Certificate(
    cert.CertificatePKCS12, "SomePassword", X509KeyStorageFlags.MachineKeySet);

这将导致IIS(以ASP.NET用户身份运行,很可能没有访问用户存储的权限)使用Machine存储。 此页面更详细地解释了构造函数,而此页面则解释了X509KeyStorageFlags枚举。 编辑: 根据cyphr第二个链接,如果之前的解决方案不起作用,结合一些FlagsAttribute枚举值可能是一个好主意。
var certificate = new X509Certificate(
    cert.CertificatePKCS12, "SomePassword",
    X509KeyStorageFlags.MachineKeySet
    | X509KeyStorageFlags.PersistKeySet
    | X509KeyStorageFlags.Exportable);

此外,如果您有权限,可以尝试更改应用程序池设置为使用LocalService(然后重新启动AppPool)。如果这是问题所在,这可能会提升您的权限到适当的级别。
最后,您可以使用File.WriteAllBytesCertificatePKCS12内容写入pfx文件,并尝试手动使用MMC下的证书控制台导入它(成功导入后可以删除;这只是为了测试)。可能是您的数据被损坏,或者密码不正确。

谢谢您的回复,但是即使添加了X509KeyStorageFlags.MachineKeySet标志,仍然会抛出相同的异常,同时添加所有三个标志MachineKeySetPersistKeySetExportable也是如此。 - lukiffer
1
你能将你的AppPool身份更改为LocalService吗? - Chris Benard
1
好的。我更新了答案,包括那些选项,以防有用的选项。我正在努力赚取+50分。 :) - Chris Benard
1
请注意,如果您计划从ASP.NET项目调用SignTool.exe,则仍然需要为应用程序池启用“LoadUserProfile”。否则,SignTool.exe将失败并显示发生内部错误的错误信息。 - c00000fd
对于没有使用Windows的.NET Core开发人员来说,这将成为跨平台工作时的一个问题。例如,OS X似乎可以很好地处理打开它而不需要这些标志,即使从另一个位置加载证书。因此,您需要包括这些标志以覆盖特定于Windows的情况! - pseudoramble
显示剩余4条评论

34

使用此代码:

certificate = new X509Certificate2(System.IO.File.ReadAllBytes(p12File)
                                   , p12FilePassword
                                   , X509KeyStorageFlags.MachineKeySet |
                                     X509KeyStorageFlags.PersistKeySet | 
                                     X509KeyStorageFlags.Exportable);

1
只需添加最后一部分的3个或语句,我的代码就能正常工作了。这是仍然相关的好代码。 - Jobokai
1
在我尝试将证书作为嵌入式资源加载到IIS上时,这对我很有效。 - Robin van der Knaap
这是我在使用spire.pdf时遇到的问题。 - HaBo

9

我在Windows 2012 Server R2上遇到了麻烦,我的应用程序无法加载磁盘上的PFX证书。以管理员身份运行我的应用程序一切正常,异常显示访问被拒绝,因此必须是权限问题。我尝试了上述建议,但仍然存在问题。我发现,在证书构造函数的第三个参数中指定以下标志对我有用:

 X509KeyStorageFlags.UserKeySet | 
 X509KeyStorageFlags.PersistKeySet | 
 X509KeyStorageFlags.Exportable

2
为了真正解决您的问题,而不是猜测可能的原因,我们需要能够重现您的问题。如果您无法提供与您遇到的问题相同的测试PFX文件,则必须自行检查问题。第一个重要的问题是:异常“发生内部错误”的起源是PKCS12的私钥部分还是证书本身的公共部分?
因此,我建议您尝试使用没有私钥的相同证书(如.CER文件)重复相同的实验。请注意保留HTML标签。
var certificate = new X509Certificate(cert.CertificateCER);

或者

var certificate = new X509Certificate.CreateFromCertFile("My.cer");

它可以帮助验证您的问题源是否为私钥或证书属性。

如果您遇到CER文件的问题,可以安全地发布该文件的链接,因为它仅包含公共信息。或者,您至少可以执行:

CertUtil.exe -dump -v "My.cer"

或者

CertUtil.exe -dump -v -privatekey -p SomePassword "My.pfx"

您可以使用其他选项,并发布输出的一些部分(例如私钥的属性,不包括PRIVATEKEYBLOB本身)。


2

2
在运行IIS 10的应用程序中,我通过使用LocalSystem身份为应用程序池设置以下代码来解决访问被拒绝的错误:
new X509Certificate2(certificateBinaryData, "password"
                               , X509KeyStorageFlags.MachineKeySet |
                                 X509KeyStorageFlags.PersistKeySet);

启用“加载用户配置文件”对我没有起作用,虽然有很多建议要这样做,但它们并没有说明将“加载用户配置文件”设置为True仅适用于用户帐户而不是:

  1. ApplicationPoolIdentity
  2. NetworkService

1
你需要将一个 .cer 证书导入到本地机器密钥库中。不需要导入你的 .p12 证书 - 相反,使用由 Apple 发布给你账户的第二个证书。我认为这必须是一对有效的证书(一个在文件系统中,另一个在密钥库中)。当然,你将不得不设置 dll 中的所有 3 个标志。

0
以下代码将帮助您,您可以使用Bouncy Castle库生成算法:
private static ECDsa GetEllipticCurveAlgorithm(string privateKey)
{
    var keyParams = (ECPrivateKeyParameters)PrivateKeyFactory
        .CreateKey(Convert.FromBase64String(privateKey));

    var normalizedECPoint = keyParams.Parameters.G.Multiply(keyParams.D).Normalize();

    return ECDsa.Create(new ECParameters
    {
        Curve = ECCurve.CreateFromValue(keyParams.PublicKeyParamSet.Id),
        D = keyParams.D.ToByteArrayUnsigned(),
        Q =
    {
        X = normalizedECPoint.XCoord.GetEncoded(),
        Y = normalizedECPoint.YCoord.GetEncoded()
    }
    });
}

并以以下方式生成令牌:

var signatureAlgorithm = GetEllipticCurveAlgorithm(privateKey);

        ECDsaSecurityKey eCDsaSecurityKey = new ECDsaSecurityKey(signatureAlgorithm)
        {
            KeyId = settings.Apple.KeyId
        };

        var handler = new JwtSecurityTokenHandler();   
        var token = handler.CreateJwtSecurityToken(
            issuer: iss,
            audience: AUD,
            subject: new ClaimsIdentity(new List<Claim> { new Claim("sub", sub) }),
            expires: DateTime.UtcNow.AddMinutes(5), 
            issuedAt: DateTime.UtcNow,
            notBefore: DateTime.UtcNow,
            signingCredentials: new SigningCredentials(eCDsaSecurityKey, SecurityAlgorithms.EcdsaSha256));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接