在.NET的HttpWebRequest / Response中使用自签名证书


80

我正在尝试连接到使用自签名SSL证书的API。我这样做是使用.NET的HttpWebRequest和HttpWebResponse对象。我得到一个例外:

基础连接已关闭:无法建立SSL / TLS安全通道的信任关系。

我明白这意味着什么。而且我明白为什么.NET认为它应该警告我并关闭连接。但是在这种情况下,无论如何,我还是想连接到API,以防中间人攻击。

那么,如何为该自签名证书添加例外?还是告诉HttpWebRequest / Response根本不验证证书的方法?我该怎么办?

Answers:


81

@Domster:可以,但是您可能想通过检查证书哈希是否符合您的期望来加强安全性。因此,扩展版本看起来像这样(基于我们正在使用的一些实时代码):

static readonly byte[] apiCertHash = { 0xZZ, 0xYY, ....};

/// <summary>
/// Somewhere in your application's startup/init sequence...
/// </summary>
void InitPhase()
{
    // Override automatic validation of SSL server certificates.
    ServicePointManager.ServerCertificateValidationCallback =
           ValidateServerCertficate;
}

/// <summary>
/// Validates the SSL server certificate.
/// </summary>
/// <param name="sender">An object that contains state information for this
/// validation.</param>
/// <param name="cert">The certificate used to authenticate the remote party.</param>
/// <param name="chain">The chain of certificate authorities associated with the
/// remote certificate.</param>
/// <param name="sslPolicyErrors">One or more errors associated with the remote
/// certificate.</param>
/// <returns>Returns a boolean value that determines whether the specified
/// certificate is accepted for authentication; true to accept or false to
/// reject.</returns>
private static bool ValidateServerCertficate(
        object sender,
        X509Certificate cert,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors)
{
    if (sslPolicyErrors == SslPolicyErrors.None)
    {
        // Good certificate.
        return true;
    }

    log.DebugFormat("SSL certificate error: {0}", sslPolicyErrors);

    bool certMatch = false; // Assume failure
    byte[] certHash = cert.GetCertHash();
    if (certHash.Length == apiCertHash.Length)
    {
        certMatch = true; // Now assume success.
        for (int idx = 0; idx < certHash.Length; idx++)
        {
            if (certHash[idx] != apiCertHash[idx])
            {
                certMatch = false; // No match
                break;
            }
        }
    }

    // Return true => allow unauthenticated server,
    //        false => disallow unauthenticated server.
    return certMatch;
}

可能有人喜欢下面的正确方法。无论如何,这种黑客手段确实很有效,但是您可能不应该在...中编码此类异常。要么只是完全禁用检查(通过下面的建议),要么实际上是指示您的计算机信任证书。 。
BrainSlugs83

3
@ BrainSlugs83:当然也可以选择禁用,但是将证书添加到计算机级别的根颁发机构存储只能由管理员完成。我的解决方案无论哪种方式都有效。
devstuff,2011年

我完全理解这一点,但是您问过,对于为什么有人拒绝您的回答,这仍然是我的猜测。而且,尽管有更多工作要做,但恕我直言,wgthom在下面的回答仍然是最正确的。
BrainSlugs83 2011年

顺便说一句,要小心,我认为ServerCertificateValidationCallback是STATIC,甚至不是threadlocal。如果我没有记错,那么一旦设置,它就会保持设置,直到您清除它。如果你想使用它的,而不是在所有其他一个连接,非常小心并行请求..
羽蛇神

3
这是执行此操作的最佳方法。如果取消对sslPolicyErrors的检查,则实际上可以确保API证书始终是期望的证书。需要注意的一件事是,上面代码中的证书指纹是一个const字节数组。这将不会按照书面形式进行编译。请尝试使用静态只读字节数组。编译器对此感到窒息,因为它需要new()运算符。
Centijo 2014年

92

事实证明,如果您只想完全禁用证书验证,则可以在ServicePointManager上更改ServerCertificateValidationCallback,如下所示:

ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

这将验证所有证书(包括无效,过期或自签名的证书)。


2
非常适合对开发机器进行一些快速测试。谢谢。
奈特

2
这会影响什么范围-appdomain中的所有内容?apppool上的所有内容?机器上的所有东西?
codeulike

29
但是要小心!RL经验表明,这种开发黑客通常使它进入发布产品:世界上最危险的代码
Doomjunky 2012年

4
这在开发中很有用,因此在其中添加#if DEBUG #endif语句至少是使该代码更安全并停止在生产中使用的最少方法。
AndyD 2013年

3
除非这个人删除了这个答案,否则我们将看到一个有趣的事实,那就是一个错误的答案所获得的选票远远多于正确的答案。
Lex Li

47

请注意,在.NET 4.5中,您可以按HttpWebRequest本身覆盖SSL验证(而不是通过影响所有请求的全局委托):

http://msdn.microsoft.com/zh-CN/library/system.net.httpwebrequest.servercertificatevalidationcallback.aspx

HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
request.ServerCertificateValidationCallback = delegate { return true; };

1
请对此进行投票;这是值得升级到4.5的!
Lynn摇摇

1
@FlorianWinter是的,您必须采用用户开发人员的逻辑
Summer-Time

43

将自签名证书添加到本地计算机受信任的根证书颁发机构

您可以通过以管理员身份运行MMC来导入证书。

如何:使用MMC管理单元查看证书


4
恕我直言,这是最正确的方法;人们只是太懒了,所以他们在特殊异常中编写了可能不应该编写的代码。
BrainSlugs83 2011年

4
该方法适用于Windows Mobile 6.5吗?7点呢?就我而言,我不想在计划运行其开发版本的每个移动设备上添加本地证书。在这种情况下,一个很好的例外使部署变得更加容易。懒惰或效率,你告诉我。
多米尼克·谢林克

3
@domster您使用SSL证书是有原因的-验证端点。如果您开发的代码可以专门解决此问题,那么您将无法对其进行正确的测试,并有可能将该代码泄漏到实际环境中。如果在客户端上安装证书真的很繁琐,为什么不只从所有设备信任的发行者那里购买证书呢?
基本

1
@Basic如果我还记得这个特定的案例,那我将需要几个通配符证书(有六个连接的TLD都在我们的控制之下)。对于开发环境来说,这是难以估量的成本。在这种情况下,唯一“变通”且未经测试的代码是不会在其他情况下引发异常。无论是否使用此替代方法,都应测试该特定的异常路径。最后,如果您无法将开发代码拒之门外,那么您会遇到比SSL验证更大的问题。
Dominic Sc​​heirlinck

对于webapp,请确保回收您的apppool或重新启动网站。就个人而言,我只是重新编译,然后就可以了。对于我们的wsdl内容,证书验证似乎在初始化和缓存时进行。
sonjz 2015年

34

使用委托上的sender参数,可以将Domster答案中使用的验证回调的范围限制为特定的请求ServerCertificateValidationCallback。以下简单的作用域类使用此技术临时连接仅对给定请求对象执行的验证回调。

public class ServerCertificateValidationScope : IDisposable
{
    private readonly RemoteCertificateValidationCallback _callback;

    public ServerCertificateValidationScope(object request,
        RemoteCertificateValidationCallback callback)
    {
        var previous = ServicePointManager.ServerCertificateValidationCallback;
        _callback = (sender, certificate, chain, errors) =>
            {
                if (sender == request)
                {
                    return callback(sender, certificate, chain, errors);
                }
                if (previous != null)
                {
                    return previous(sender, certificate, chain, errors);
                }
                return errors == SslPolicyErrors.None;
            };
        ServicePointManager.ServerCertificateValidationCallback += _callback;
    }

    public void Dispose()
    {
        ServicePointManager.ServerCertificateValidationCallback -= _callback;
    }
}

上面的类可用于忽略特定请求的所有证书错误,如下所示:

var request = WebRequest.Create(uri);
using (new ServerCertificateValidationScope(request, delegate { return true; }))
{
    request.GetResponse();
}

6
这个答案需要更多的投票:)这是跳过使用HttpWebRequest对象的单个请求的证书验证的最合理答案。
MikeJansen,2012年

我添加了此消息,但仍在获取该请求已中止:无法创建SSL / TLS安全通道。
vikingben 2014年

7
在多线程环境中,这并不能真正解决问题。
汉斯(Hans)2014年

1
maaan !!!,一个5岁的帖子保存了我的一天,我在使用无效证书连接旧卫星调制解调器设备时遇到问题!谢谢!!
WindyHen

3

只是在devstuff的答案基础上,包括主题和发行者...欢迎发表评论...

public class SelfSignedCertificateValidator
{
    private class CertificateAttributes
    {
        public string Subject { get; private set; }
        public string Issuer { get; private set; }
        public string Thumbprint { get; private set; }

        public CertificateAttributes(string subject, string issuer, string thumbprint)
        {
            Subject = subject;
            Issuer = issuer;                
            Thumbprint = thumbprint.Trim(
                new char[] { '\u200e', '\u200f' } // strip any lrt and rlt markers from copy/paste
                ); 
        }

        public bool IsMatch(X509Certificate cert)
        {
            bool subjectMatches = Subject.Replace(" ", "").Equals(cert.Subject.Replace(" ", ""), StringComparison.InvariantCulture);
            bool issuerMatches = Issuer.Replace(" ", "").Equals(cert.Issuer.Replace(" ", ""), StringComparison.InvariantCulture);
            bool thumbprintMatches = Thumbprint == String.Join(" ", cert.GetCertHash().Select(h => h.ToString("x2")));
            return subjectMatches && issuerMatches && thumbprintMatches; 
        }
    }

    private readonly List<CertificateAttributes> __knownSelfSignedCertificates = new List<CertificateAttributes> {
        new CertificateAttributes(  // can paste values from "view cert" dialog
            "CN = subject.company.int", 
            "CN = issuer.company.int", 
            "f6 23 16 3d 5a d8 e5 1e 13 58 85 0a 34 9f d6 d3 c8 23 a8 f4") 
    };       

    private static bool __createdSingleton = false;

    public SelfSignedCertificateValidator()
    {
        lock (this)
        {
            if (__createdSingleton)
                throw new Exception("Only a single instance can be instanciated.");

            // Hook in validation of SSL server certificates.  
            ServicePointManager.ServerCertificateValidationCallback += ValidateServerCertficate;

            __createdSingleton = true;
        }
    }

    /// <summary>
    /// Validates the SSL server certificate.
    /// </summary>
    /// <param name="sender">An object that contains state information for this
    /// validation.</param>
    /// <param name="cert">The certificate used to authenticate the remote party.</param>
    /// <param name="chain">The chain of certificate authorities associated with the
    /// remote certificate.</param>
    /// <param name="sslPolicyErrors">One or more errors associated with the remote
    /// certificate.</param>
    /// <returns>Returns a boolean value that determines whether the specified
    /// certificate is accepted for authentication; true to accept or false to
    /// reject.</returns>
    private bool ValidateServerCertficate(
        object sender,
        X509Certificate cert,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors)
    {
        if (sslPolicyErrors == SslPolicyErrors.None)
            return true;   // Good certificate.

        Dbg.WriteLine("SSL certificate error: {0}", sslPolicyErrors);
        return __knownSelfSignedCertificates.Any(c => c.IsMatch(cert));            
    }
}

3

要向其他人添加可能的帮助...如果希望它提示用户安装自签名证书,则可以使用此代码(从上面进行修改)。

不需要管理员权限,将可信任的配置文件安装到本地用户:

    private static bool ValidateServerCertficate(
        object sender,
        X509Certificate cert,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors)
    {
        if (sslPolicyErrors == SslPolicyErrors.None)
        {
            // Good certificate.
            return true;
        }

        Common.Helpers.Logger.Log.Error(string.Format("SSL certificate error: {0}", sslPolicyErrors));
        try
        {
            using (X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
            {
                store.Open(OpenFlags.ReadWrite);
                store.Add(new X509Certificate2(cert));
                store.Close();
            }
            return true;
        }
        catch (Exception ex)
        {
            Common.Helpers.Logger.Log.Error(string.Format("SSL certificate add Error: {0}", ex.Message));
        }

        return false;
    }

这对于我们的应用程序似乎效果很好,并且如果用户按no,则通信将无法进行。

更新:2015-12-11-将StoreName.Root更改为StoreName.My-我的我将安装到本地用户商店中,而不是Root。即使您“以管理员身份运行”,某些系统上的根也不起作用


如果它可以在Compact Framework winCE上运行,那就太棒了。store.Add(..)不可用。
戴维特

1

要记住的一件事是,拥有ServicePointManager.ServerCertificateValidationCallback似乎并不意味着未完成CRL检查和服务器名验证,它仅提供了一种覆盖其结果的方法。因此,您的服务可能仍需要一段时间才能获得CRL,之后您才知道它在某些检查中未通过。


1

我遇到了与OP相同的问题,其中Web请求将抛出该确切异常。我以为我已经正确设置了所有设置,安装了证书,可以在机器存储中找到它,并将其附加到Web请求,并且我已禁用了对请求上下文的证书验证。

原来,我使用用户帐户运行,并且证书已安装到计算机存储中。这导致Web请求引发此异常。为了解决该问题,我必须以管理员身份运行或将证书安装到用户存储中并从那里读取它。

即使C#不能与Web请求一起使用,似乎C#仍能够在计算机存储中找到证书,并且一旦Web请求发出,就会导致OP的异常被抛出。


对于Windows服务,您可以为每个服务设置单独的证书配置。如果您不是在编写桌面应用程序,而是在编写服务,则可以将CA证书导入MMC中,专门用于服务守护程序。用户帐户和计算机帐户有什么区别?我认为机器帐户中的所有内容都会自动应用于用户。
ArticIceJuice

1

首先-很抱歉,因为我使用了@devstuff描述的解决方案。但是,我发现了一些改进方法。

  • 添加自签名证书处理
  • 通过证书的原始数据进行比较
  • 实际的证书颁发机构验证
  • 一些其他评论和改进

这是我的修改:

private static X509Certificate2 caCertificate2 = null;

/// <summary>
/// Validates the SSL server certificate.
/// </summary>
/// <param name="sender">An object that contains state information for this validation.</param>
/// <param name="cert">The certificate used to authenticate the remote party.</param>
/// <param name="chain">The chain of certificate authorities associated with the remote certificate.</param>
/// <param name="sslPolicyErrors">One or more errors associated with the remote certificate.</param>
/// <returns>Returns a boolean value that determines whether the specified certificate is accepted for authentication; true to accept or false to reject.</returns>
private static bool ValidateServerCertficate(
        object sender,
        X509Certificate cert,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors)
{
    if (sslPolicyErrors == SslPolicyErrors.None)
    {
        // Good certificate.
        return true;
    }

    // If the following line is not added, then for the self-signed cert an error will be (not tested with let's encrypt!):
    // "A certificate chain processed, but terminated in a root certificate which is not trusted by the trust provider. (UntrustedRoot)"
    chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;

    // convert old-style cert to new-style cert
    var returnedServerCert2 = new X509Certificate2(cert);

    // This part is very important. Adding known root here. It doesn't have to be in the computer store at all. Neither do certificates.
    chain.ChainPolicy.ExtraStore.Add(caCertificate2);

    // 1. Checks if ff the certs are OK (not expired/revoked/etc) 
    // 2. X509VerificationFlags.AllowUnknownCertificateAuthority will make sure that untrusted certs are OK
    // 3. IMPORTANT: here, if the chain contains the wrong CA - the validation will fail, as the chain is wrong!
    bool isChainValid = chain.Build(returnedServerCert2);
    if (!isChainValid)
    {
        string[] errors = chain.ChainStatus
            .Select(x => String.Format("{0} ({1})", x.StatusInformation.Trim(), x.Status))
            .ToArray();

        string certificateErrorsString = "Unknown errors.";

        if (errors != null && errors.Length > 0)
        {
            certificateErrorsString = String.Join(", ", errors);
        }

        Log.Error("Trust chain did not complete to the known authority anchor. Errors: " + certificateErrorsString);
        return false;
    }

    // This piece makes sure it actually matches your known root
    bool isValid = chain.ChainElements
        .Cast<X509ChainElement>()
        .Any(x => x.Certificate.RawData.SequenceEqual(caCertificate2.GetRawCertData()));

    if (!isValid)
    {
        Log.Error("Trust chain did not complete to the known authority anchor. Thumbprints did not match.");
    }

    return isValid;
}

设置证书:

caCertificate2 = new X509Certificate2("auth/ca.crt", "");
var clientCertificate2 = new X509Certificate2("auth/client.pfx", "");

传递委托方法

ServerCertificateValidationCallback(ValidateServerCertficate)

client.pfx 使用KEY和CERT生成,如下所示:

openssl pkcs12 -export -in client.crt -inkey client.key -out client.pfx
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.