.NETのHttpWebRequest / Responseで自己署名証明書を使用する


80

自己署名SSL証明書を使用するAPIに接続しようとしています。私は.NETのHttpWebRequestオブジェクトとHttpWebResponseオブジェクトを使用してこれを行っています。そして、私は次のような例外を取得しています:

基になる接続が閉じられました:SSL / TLSのセキュリティで保護されたチャネルの信頼関係を確立できませんでした。

これが何を意味するのか理解しています。そして .NETが警告を発して接続を閉じる必要があると感じる理由を理解しています。しかし、この場合は、とにかくAPIに接続したいので、man-in-the-middle攻撃は気にしないでください。

では、この自己署名証明書の例外を追加するにはどうすればよいですか?または、HttpWebRequest / Responseに証明書をまったく検証しないように指示するアプローチですか?どうすればいいですか?

回答:


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

そして私はそれを完全に理解しています、しかしあなたは尋ねました、そしてそれは誰かがあなたの答えに反対票を投じた理由についての私の推測です。そして、それがより多くの仕事であるにもかかわらず、以下のIMHOwgthomの答えは依然として最も正しいものです。
BrainSlugs83 2011

ところで、注意してください。ServerCertificateValidationCallbackは静的であり、スレッドローカルではないと思います。私が間違っていなければ、一度設定すると、クリアするまで設定されたままになります。あなたが他のすべてに過ぎず、一つの接続のためにそれを使用したい場合は、並列要求に非常に注意してください...
ケツァルコアトル

3
これがこれを行うための最良の方法です。sslPolicyErrorsに対するチェックを削除すると、API証明書が常に期待されるものであることを実際に確認できます。上記のコードの証明書フィンガープリントはconstバイト配列であることに注意してください。これは書かれた通りにコンパイルされません。代わりに、静的な読み取り専用バイト配列を試してください。new()演算子が必要なため、コンパイラはこれを抑制します。
Centijo 2014

92

証明書の検証を完全に無効にしたい場合は、次のようにServicePointManagerでServerCertificateValidationCallbackを変更できます。

ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

これにより、すべての証明書(無効な証明書、期限切れの証明書、または自己署名証明書を含む)が検証されます。


2
開発マシンに対するいくつかのクイックテストに最適です。ありがとう。
ネイト

2
これはどのスコープに影響しますか?appdomain内のすべてですか?アプリプールのすべて?マシン上のすべて?
codeulike 2011

29
ただし、注意してください。RLの経験から、この開発ハックがリリース製品に組み込まれることがよくあります。世界で最も危険なコード
Doomjunky 2012年

4
これは開発に役立つハックなので、#if DEBUG #endifステートメントをその周りに配置することは、これをより安全にし、これが本番環境で終了するのを防ぐために行うべき最小限のことです。
AndyD 2013

3
この男がこの回答を削除しない限り、間違った回答は正しい回答よりもはるかに多くの票を獲得するという面白い事実がわかります。
Lex Li

47

.NET 4.5では、HttpWebRequest自体ごとにSSL検証をオーバーライドできることに注意してください(すべての要求に影響するグローバルデリゲートを介してではありません)。

http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.servercertificatevalidationcallback.aspx

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

1
これに賛成してください。これは4.5にアップグレードする価値があります!
Lynn Crumbling 2015年

1
@FlorianWinterはい、ユーザーdevstuffのロジックを採用する必要があります
Summer-Time

43

自己署名証明書をローカルコンピューターの信頼されたルート証明機関に追加します

MMCを管理者として実行すると、証明書をインポートできます。

方法:MMCスナップインを使用して証明書を表示する


4
私見これが最も正しい方法です。人々は怠惰すぎるので、おそらくすべきではないことのために特別な例外をコーディングします。
BrainSlugs83 2011

4
その方法はWindowsMo​​bile 6.5で機能しますか?7はどうですか?私の場合、開発バージョンを実行する予定のすべてのモバイルデバイスにローカル証明書を追加する必要はありませんでした。この場合の良い例外は、展開を1トン簡単にします。怠惰または効率、あなたは私に言います。
Dominic Sc​​heirlinck 2011

3
@domsterエンドポイントを確認するためにSSL証明書を使用しています。特にそれを回避するコードを開発する場合、それを適切にテストしておらず、そのコードが実際の環境にリークするリスクがあります。クライアントに証明書をインストールするのが本当に大変な作業である場合は、すべてのデバイスによって信頼されている発行者からの証明書の料金を支払うだけではどうでしょうか。
基本的な

1
@Basicこの特定のケースを覚えているとしたら、いくつかのワイルドカード証明書が必要でした(接続先のTLDは6つあり、すべて私たちの管理下にありました)。これは、開発環境にとって正当化するのが難しいコストです。この場合、「回避」されてテストされていない唯一のコードは、例外がスローされないことです。この回避策を使用しているかどうかに関係なく、その特定の例外パスをテストする必要があります。そして最後に、開発コードを本番環境から除外できない場合は、SSL検証よりもはるかに大きな問題が発生します。
Dominic Sc​​heirlinck 2012

Webアプリの場合は、必ずアプリプールをリサイクルするか、Webサイトを再起動してください。個人的には、再コンパイルしたところ、うまくいきました。wsdlのものの場合、証明書の検証は初期化時に行われ、キャッシュされているように見えます。
sonjz 2015

34

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
これは、マルチスレッド環境での問題を実際には解決しません。
ハンス

1
maaan !!!、5歳の投稿が私の日を救ってくれました、無効な証明書でold-satellite-modem-applianceに接続するのに問題があります!! ありがとうございました!!
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;
    }

これは私たちのアプリケーションではうまく機能しているようで、ユーザーが「いいえ」を押すと、通信は機能しません。

更新:2015-12-11 -StoreName.RootをStoreName.Myに変更しました-Myは、Rootではなくローカルユーザーストアにインストールされます。「管理者として実行」しても、一部のシステムのルートは機能しません


これは、Compact FrameworkwinCEで機能する場合は素晴らしいでしょう。store.Add(..)は使用できません。
Dawit 2017

1

覚えておくべきことの1つは、ServicePointManager.ServerCertificateValidationCallbackがあるということは、CRLチェックとサーバー名の検証が行われていないことを意味するのではなく、結果をオーバーライドする手段を提供するだけであるということです。そのため、サービスがCRLを取得するまでにまだ時間がかかる場合があります。その後、いくつかのチェックに失敗したことがわかります。


1

私は、Webリクエストがその正確な例外をスローするOPと同じ問題に遭遇していました。すべてが正しくセットアップされ、証明書がインストールされ、マシンストアで問題なく見つけて、Webリクエストに添付でき、リクエストコンテキストでの証明書の検証を無効にしました。

私は自分のユーザーアカウントで実行していて、証明書がマシンストアにインストールされていることがわかりました。これにより、Webリクエストがこの例外をスローしました。この問題を解決するには、管理者として実行するか、証明書をユーザーストアにインストールして、そこから読み取る必要がありました。

C#は、Web要求で使用できない場合でも、マシンストアで証明書を見つけることができるように見えます。これにより、Web要求が発行されると、OPの例外がスローされます。


Windowsサービスの場合、サービスごとに個別の証明書構成をセットアップできます。デスクトップアプリではなくサービスを作成している場合は、CA証明書をサービスデーモン専用のMMCにインポートできます。ユーザーアカウントとマシンアカウントの違いは何ですか?マシンアカウントのすべてが自動的にユーザーに適用されると思いました。
ArticIceJuice 2017

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.