0

更改日志

  • 重命名问题:AppContext Switch Failing In Unit Test (CryptographicException: Invalid Algorithm Specified)
  • 在进行更多调查后,在底部添加了进一步详细说明问题的部分
  • 在底部添加了解释我已实施的解决方法的部分

我有一段代码,我正在编写用于签署一些 xml 的单元测试。

我注意到在开发 .NET 4.7.1+ 将抛出的代码时CryptographicException: Invalid algorithm specified。为了解决这个问题,我使用了以下AppContext开关(来源:wiktor zychla为什么我指定了无效的算法......)。

我已将以下代码放入任何调用我的 xml 签名代码的应用程序中:

AppContext.SetSwitch("Switch.System.Security.Cryptography.Xml.UseInsecureHashAlgorithms", true);
AppContext.SetSwitch("Switch.System.Security.Cryptography.Pkcs.UseInsecureHashAlgorithms", true);

在野外运行时,此代码运行正常,并且我的 xml 正在按预期进行签名。

我已经为这段代码编写了一些单元测试,这是我第一次真正尝试编写单元测试,我遇到了一些奇怪的边缘情况。

try
{
    // Compute the signature.
    signedXml.ComputeSignature();
}
catch (CryptographicException cex)
{
    //Contains instead of == since this message contains newline characters...
    if (cex.Message.Contains("Invalid algorithm specified"))
    {
        string code = Environment.NewLine + "AppContext.SetSwitch(\"Switch.System.Security.Cryptography.Xml.UseInsecureHashAlgorithms\", true);" +
                        Environment.NewLine + "AppContext.SetSwitch(\"Switch.System.Security.Cryptography.Pkcs.UseInsecureHashAlgorithms\", true); ";

        throw new Exception($"Cryptographic exception was thrown, ensure that the following switches are set in the executing assembly: {code}", cex);
    }
    else
    {
        throw cex;
    }
}

因此,如果抛出异常,我会显示一条很好的消息,说请在调用此方法之前添加以下代码。

我的测试类看起来像这样:

[TestFixture]    
public class XMLSigningTests
{
    protected IXMLSigner _xmlSigner;
    protected byte[] _publicKey;
    protected string _password;

    [SetUp]
    public void Init()
    {
        _xmlSigner = new XMLSigner();
        _password = "test";
        _publicKey = GenerateTestCert(_password);
    }

    [TearDown]
    public void CleanUp()
    {
        _xmlSigner = null;
        _publicKey = null;
        _password = null;
    }

    [Test]
    public void SignXML_AppContextSwitchNotSet_Fail()
    {
        XMLDocument xmlDoc = GenerateXML();

        Assert.Throws<Exception>(() => { _xmlSigner.SignXML(xmlDoc, _publicKey, _password); });
    }

    [Test]
    public void SignXML_AppContextSwitchSet_Success()
    {
        AppContext.SetSwitch("Switch.System.Security.Cryptography.Xml.UseInsecureHashAlgorithms", true);
        AppContext.SetSwitch("Switch.System.Security.Cryptography.Pkcs.UseInsecureHashAlgorithms", true);

        XMLDocument xmlDoc = GenerateXML();

        var signedXml = _xmlSigner.SignXML(xmlDoc, _publicKey, _password); 

        Assert.IsFalse(signedXML == null);
        //More validation here
    }

    private XMLDocument GenerateXML()
    {
        return new XMLDocument("Some fancy xml here...");
    }

    private byte[] GenerateTestCert(string password)
    {
        //Use Pluralsight.Crypto to generate a self signed cert & export as .pfx
        return cert.Export(X509ContentType.Pfx, password);
    }
}

当我运行SignXML_AppContextSwitchSet_Success我希望自己通过的时候,测试每次都通过(只是我反复运行它)。如果我清理我的解决方案并运行所有测试,测试总是失败。

我的 xml 生成类没有共享对象,它实际上可能是静态的。

测试失败是因为抛出了加密异常(触发我的代码并抛出异常告诉开发人员(我)添加应用程序上下文切换)。

有没有办法验证配置开关是否得到尊重?

从我的控制台测试器应用程序和我内置的 wpf 应用程序运行代码时,永远不会抛出异常。只有当我背靠背运行所有单元测试时才会抛出它。我希望这些测试在 azure 管道上运行,因此它可以同时运行和单独运行很重要。

我对这个例外的想法是,也许当我一起运行所有测试时,AppContext开关没有得到尊重。我尝试在签署 xml 之前添加检查:

AppContext.TryGetSwitch("Switch.System.Security.Cryptography.Xml.UseInsecureHashAlgorithms", out bool xmlSwitch);
AppContext.TryGetSwitch("Switch.System.Security.Cryptography.Pkcs.UseInsecureHashAlgorithms", out bool pkcsSwitch);

if (!xmlSwitch || !pkcsSwitch)
{
    throw new NotImplementedException("THESE VALUES SHOULD BE SET WTF");
}

现在您希望此代码无法通过我的失败测试(未设置开关)并且确实如此,您可以NotImplementedException在测试资源管理器中看到我的粗略消息。然而,我期望成功的测试CryptographicException再次引发......

System.Exception : Cryptographic exception was thrown, ensure that the following switches are set in the executing assembly: 
    AppContext.SetSwitch("Switch.System.Security.Cryptography.Xml.UseInsecureHashAlgorithms", true);
    AppContext.SetSwitch("Switch.System.Security.Cryptography.Pkcs.UseInsecureHashAlgorithms", true); 
      ----> System.Security.Cryptography.CryptographicException : Invalid algorithm specified.

正如我之前所说,我对测试非常陌生,我不确定我的测试设计方法是否错误,如果我只是在测试一些底层代码,所以也许我注定要尝试测试它或我缺少一些东西来允许对这种情况进行正确测试。

我目前正在使用NUnit 3.12.0 + NUnit3TestAdapter 3.15.1编写测试,尽管我最初使用MSTest 1.3.2并体验了使用这两个框架的相同行为。


更新

我发现以下测试的执行顺序决定了成功:

  • SignXML_AppContextSwitchNotSet_Fail
  • SignXML_AppContextSwitchSet_Success

我通过阅读诸如(单元测试在全部运行时失败,但在单独运行时通过)在不单独运行时测试失败的帖子发现了这一点。

它让我想到了正在设置的值,我想要以下情况:

//Test I expect to fail
AppContext.SetSwitch("the switch..", false);

//Test I expect to pass
AppContext.SetSwitch("the switch..", true);

尽管我已经测试了这些值是否正确设置,但底层开关是否正在更新?如何清理这些底层对象?


解决方法/“讨厌”修复

我在NUnit 的 GitHub (appdomain per test (isolation per test), for handling static classes)上发现了以下问题。在这个线程中,他们讨论了AppDomain每个测试的新选项,这正是我所需要的。

沿着线程jnm2发布一个 util 类以单独运行代码AppDomain代码)(我不会在这里发布它,因为这篇文章已经很庞大了)。我采用了这个 util 类并调整了我的测试以使用它(你不能使用任何类变量或方法,这意味着我必须复制我的辅助方法代码)。

一旦我实现了 util 并调整了我的测试以符合它的标准,当我单独运行它们时测试开始通过,并且一次全部运行。

任务完成!

4

1 回答 1

0

我有完全相同的问题!我遇到了这篇文章,它激发了我的解决方案!

我没有在单独的 AppDomain 中运行测试,而是最终修改了调用加密库的生产代码。我在一个单独的 AppDomain 中运行有问题的调用,在继续之前我在其中设置了开关。

这修复了所有测试,并且它使我的库的客户端不会弄乱开关。

于 2020-07-27T06:55:12.440 回答