Mono在进行https webrequest时出现“身份验证或解密失败”的错误。

72

我正在制作一个简单的REST客户端,用于我的C#应用程序。在Windows上的.NET中,它可以很好地处理http://和https://连接。但是,在Ubuntu 10.10上的mono2.6.7(也在2.8上测试过)只支持http://。通过request.GetResponse()方法发起https://连接会抛出异常:

Unhandled Exception: System.Net.WebException: Error getting response stream (Write: The authentication or decryption has failed.): SendFailure ---> System.IO.IOException: The authentication or decryption has failed. ---> Mono.Security.Protocol.Tls.TlsException: Invalid certificate received from server. Error code: 0xffffffff800b010a
  at Mono.Security.Protocol.Tls.Handshake.Client.TlsServerCertificate.validateCertificates (Mono.Security.X509.X509CertificateCollection certificates) [0x00000] in <filename unknown>:0 
  at Mono.Security.Protocol.Tls.Handshake.Client.TlsServerCertificate.ProcessAsTls1 () [0x00000] in <filename unknown>:0 
  at Mono.Security.Protocol.Tls.Handshake.HandshakeMessage.Process () [0x00000] in <filename unknown>:0 
  at (wrapper remoting-invoke-with-check) Mono.Security.Protocol.Tls.Handshake.HandshakeMessage:Process ()
  at Mono.Security.Protocol.Tls.ClientRecordProtocol.ProcessHandshakeMessage (Mono.Security.Protocol.Tls.TlsStream handMsg) [0x00000] in <filename unknown>:0 
  at Mono.Security.Protocol.Tls.RecordProtocol.InternalReceiveRecordCallback (IAsyncResult asyncResult) [0x00000] in <filename unknown>:0 
  --- End of inner exception stack trace ---
  at Mono.Security.Protocol.Tls.SslStreamBase.AsyncHandshakeCallback (IAsyncResult asyncResult) [0x00000] in <filename unknown>:0 
  --- End of inner exception stack trace ---
  at System.Net.HttpWebRequest.EndGetResponse (IAsyncResult asyncResult) [0x00000] in <filename unknown>:0 
  at System.Net.HttpWebRequest.GetResponse () [0x00000] in <filename unknown>:0 

我一直没有找到解决这个问题的方法。有人知道为什么会出现这种情况以及如何修复吗?

再次说明,这只在Mono中失败,.Net似乎没有任何问题建立连接。

以下是调用代码:

public JToken DoRequest(string path, params string[] parameters) {
    if(!path.StartsWith("/")) {
        path = "/" + path;
    }
    string fullUrl = url + path + ToQueryString(parameters);

    if(DebugUrls) Console.WriteLine("Requesting: {0}", fullUrl);

    WebRequest request = HttpWebRequest.CreateDefault(new Uri(fullUrl));
    using(WebResponse response = request.GetResponse())
    using(Stream responseStream = response.GetResponseStream()) {
        return ReadResponse(responseStream);
    }
}

你尝试过在Windows/Mono、Windows/.NET上运行吗? - abatishchev
正如我在问题中所解释的,我已经在Windows/.NET上尝试过了,但是我从未在Windows上尝试过Mono。不过,看看它是否可行会很有趣。然而,jpobst的帖子似乎是解决我的问题的方法。 - Joel
4
在这里挖掘过去的事情-我有所遗漏吗?jpobst的帖子在哪里? - Dave Lawrence
@daveL:由于人们可以更改他们的显示名称,所以它可能仍然存在(但由于我也只是这个问题的访客,我无法告诉您哪一个是)。 - pinkgothic
10个回答

52
我在Unity(也使用mono)中遇到了同样的问题,这篇帖子帮助我解决了它。
只需在发出请求之前添加以下行:
ServicePointManager.ServerCertificateValidationCallback = MyRemoteCertificateValidationCallback;

而这个方法:

public bool MyRemoteCertificateValidationCallback(System.Object sender,
    X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
    bool isOk = true;
    // If there are errors in the certificate chain,
    // look at each error to determine the cause.
    if (sslPolicyErrors != SslPolicyErrors.None) {
        for (int i=0; i<chain.ChainStatus.Length; i++) {
            if (chain.ChainStatus[i].Status == X509ChainStatusFlags.RevocationStatusUnknown) {
                continue;
            }
            chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
            chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
            chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan (0, 1, 0);
            chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllFlags;
            bool chainIsValid = chain.Build ((X509Certificate2)certificate);
            if (!chainIsValid) {
                isOk = false;
                break;
            }
        }
    }
    return isOk;
}

这应该是有效的,但在我的 swagger 自动生成的客户端中似乎不起作用。请参见 https://github.com/swagger-api/swagger-editor/issues/1034 - loretoparisi
3
运作良好,非常顺利。(Y) - Emy
在Mono中存在一个潜在的问题,即chain.Build(cert)始终返回true,即使请求到了bad ssl - sakiM
3
抱歉,但你的解决方案是使用在互联网上找到的随机代码自己制作SSL证书验证?如果你不在意SSL提供的安全性,可以让你的函数“返回真”。这只是“手势式安全”,实际上是完全不可靠的,并且将欣然接受破损或伪造的证书。 - Søren Løvborg

29

在 Windows 上,.NET Framework 使用 Windows 证书存储(mmc、添加/删除快捷方式、证书)来确定是否接受来自远程站点的 SSL 证书。Windows 随附了一堆根和中间证书颁发机构(CA),并且它们会定期由 Windows Update 进行更新。因此,您的 .NET 代码通常会信任一个证书,前提是该证书是由证书存储中的 CA 或 CA 的后代颁发的(包括大多数知名商业 CA)。

而在 Mono 中,则没有 Windows 证书存储。Mono 有自己的存储。默认情况下,它为空(没有受信任的默认 CA)。您需要自己管理条目。

可以在这里查看:

mozroots.exe 点将导致您的 mono 安装信任 Firefox 在默认安装后信任的所有内容。


1
这里简要提一下Entrust的G2根证书。目前Mozilla的CA存储库中还没有包含它,但他们计划在Firefox 38发布时将其添加进去,但这并不是确定的。目前,如果您使用Mozilla的CA存储库,则代码将无法验证使用G2根证书签名的证书。(Thumbprints位于http://www.entrust.net/developer/,Mozilla bug来源自2013年 https://bugzilla.mozilla.org/show_bug.cgi?id=849950) - Benjamin Nolan

7
在发起http请求之前,请写入这行代码。这应该可以正常工作。
ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback((sender, certificate, chain, policyErrors) => { return true; });


private static bool RemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
    {
        //Return true if the server certificate is ok
        if (sslPolicyErrors == SslPolicyErrors.None)
            return true;

        bool acceptCertificate = true;
        string msg = "The server could not be validated for the following reason(s):\r\n";

        //The server did not present a certificate
        if ((sslPolicyErrors &
             SslPolicyErrors.RemoteCertificateNotAvailable) == SslPolicyErrors.RemoteCertificateNotAvailable)
        {
            msg = msg + "\r\n    -The server did not present a certificate.\r\n";
            acceptCertificate = false;
        }
        else
        {
            //The certificate does not match the server name
            if ((sslPolicyErrors &
                 SslPolicyErrors.RemoteCertificateNameMismatch) == SslPolicyErrors.RemoteCertificateNameMismatch)
            {
                msg = msg + "\r\n    -The certificate name does not match the authenticated name.\r\n";
                acceptCertificate = false;
            }

            //There is some other problem with the certificate
            if ((sslPolicyErrors &
                 SslPolicyErrors.RemoteCertificateChainErrors) == SslPolicyErrors.RemoteCertificateChainErrors)
            {
                foreach (X509ChainStatus item in chain.ChainStatus)
                {
                    if (item.Status != X509ChainStatusFlags.RevocationStatusUnknown &&
                        item.Status != X509ChainStatusFlags.OfflineRevocation)
                        break;

                    if (item.Status != X509ChainStatusFlags.NoError)
                    {
                        msg = msg + "\r\n    -" + item.StatusInformation;
                        acceptCertificate = false;
                    }
                }
            }
        }

        //If Validation failed, present message box
        if (acceptCertificate == false)
        {
            msg = msg + "\r\nDo you wish to override the security check?";
//          if (MessageBox.Show(msg, "Security Alert: Server could not be validated",
//                       MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1) == DialogResult.Yes)
                acceptCertificate = true;
        }

        return acceptCertificate;
    }

7
请考虑在您的回答中包含一些信息,而不仅仅是发布代码。我们试图提供的不仅是“修复”,还包括帮助人们学习。您应该解释原始代码存在的问题,您做了什么不同,并说明为什么您的更改有效。 - Andrew Barber
4
这与合理的网络安全政策背道而驰:您只是在接受证书之前,不进行任何验证就接受它。请参考@Ludovic的答案,其中包含一个在接受证书之前进行验证的函数。 - ssamuel
2
这个操作拒绝了SSL错误的一个子类,但默许其他错误(例如自签名证书)。因此(就像其他答案一样),这有效地禁用了SSL的所有安全性。如果这正是你想要的,请只需返回“true”。 - Søren Løvborg

6

Mono默认不信任任何证书,如果你想导入Mozilla受信根机构,你可以在mozroots.exe所在的mono安装文件夹下运行mozroots --import --quiet命令。


1
最近的Mono版本似乎非常喜欢确认,因此对于那些不想多次输入“是”的人来说,这个方法可以解决问题:yes“是”|./mozroots --import --quiet。将其放置在Mono的bin文件夹中。在macOS上,它应该是/Library/Frameworks/Mono.framework/Versions/Current/bin - nover
这个能在没有 Firefox 的服务器上运行吗? - Alejandro

4

对于Unity的另一个解决方案是只需初始化ServicePointManager一次即可始终接受证书。这种方法可以运行,但显然不够安全。

System.Net.ServicePointManager.ServerCertificateValidationCallback +=
           delegate (object sender, System.Security.Cryptography.X509Certificates.X509Certificate certificate,
                                   System.Security.Cryptography.X509Certificates.X509Chain chain,
                                   System.Net.Security.SslPolicyErrors sslPolicyErrors)
           {
               return true; // **** Always accept
       };

5
将此答案简化为一行代码:ServicePointManager.ServerCertificateValidationCallback += (p1, p2, p3, p4) => true; - Andy

4

我有同样的问题。

当http响应引发此异常时,我执行以下操作:

System.Diagnostics.Process.Start("mozroots","--import --quiet");

这将导入缺失的证书,异常就不会再次发生。

3

我也遇到了这个错误。

我尝试了ServicePointManager.ServerCertificateValidationCallbackServicePointManager.CertificatePolicy,但仍然不起作用。

我很生气。于是我建立了一个cURL包装器。对于我的小项目来说它工作得很好。

/// <summary>
/// For MONO ssl decryption failed
/// </summary>
public static string PostString(string url, string data)
{
    Process p = null;
    try
    {
        var psi = new ProcessStartInfo
        {
            FileName = "curl",
            Arguments = string.Format("-k {0} --data \"{1}\"", url, data),
            RedirectStandardOutput = true,
            UseShellExecute = false,
            CreateNoWindow = false,
        };

        p = Process.Start(psi);

        return p.StandardOutput.ReadToEnd();
    }
    finally
    {
        if (p != null && p.HasExited == false)
            p.Kill();
    }
}

3

第一个回答已经说过了:除了Windows以外的其他任何系统上,Mono都没有带任何东西,因此最初它不信任任何证书。那么怎么办呢?

这是一篇关于开发者如何处理这个问题的不错的文章: http://www.mono-project.com/archived/usingtrustedrootsrespectfully/

简短的总结:

  • 忽略安全问题
  • 忽略这个问题
  • 让用户知道并中止操作
  • 让用户知道并让他/她自行决定是否继续风险操作

以上链接提供了每种情况的代码示例。


1
调用 mozroots --import --sync 应该可以为您解决问题,它会将 Mozilla 的默认证书添加到 Mono 证书存储中。 - ScottB

3

按照被接受的答案导入证书后,我仍然遇到了这个问题。

我发现 Mono 4.8.0 中添加了 TLS 1.2 的支持,它使用了 Google 的 BoringSSL。而我使用的 Mono 版本比这个要旧。我升级到了 Mono 5.10,现在可以连接而不会收到此异常。


0

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