web-dev-qa-db-ja.com

C#/ .NET-アプリケーションでHTTPSの「カスタム」ルートCAを許可するにはどうすればよいですか(のみ)?

さて、これが私がする必要があることです:

C#(。NET Framework 4.5)で記述された私のアプリケーションは、HTTPS経由でサーバーと通信する必要があります。私たちのサーバーは、私たち自身のルートCAによって発行されたTLS/SSL証明書を使用しています。そのRoot-CAは、私のアプリケーションによって完全に信頼されていますが、システムの「信頼されたルート」証明書ストアにインストールされていますnot。したがって、サーバーの証明書を検証できないため、C#はそれ以上の作業を行わないと、サーバーへの接続を拒否します。注:システムにすでにインストールされているルートCAは使用できません。

アプリケーションが(安全に)サーバーに接続できるようにするにはどうすればよいですか? C#は、ルートCA証明書をシステムの証明書ストアに「信頼されたルート」としてインストールするためのクラスを提供していることを知っています。それはnot私たちがやりたいことです!これは、(a)ユーザーに警告(および技術的すぎる)警告を表示し、(b)他のアプリケーションにも影響を与えるためです。欲しいまたは必要。

したがって、必要なのは、システムの代わりに「カスタム」(つまりアプリケーション固有)の証明書セットを使用するようにC#/。NETに指示するものです。 -ワイド証明書ストア-サーバー証明書のチェーンを検証します。証明書チェーン全体を適切に検証する必要があります(失効リストを含む!)。 myアプリケーションの「信頼できる」ルートとして受け入れる必要があるのは、Root-CAだけです。

これを行うための最良の方法は何でしょうか?

どんな助けでも大歓迎です。前もって感謝します!


ところで、私はServicePointManager.ServerCertificateValidationCallbackを使用して独自の証明書検証機能をインストールできることを知りました。この動作します。しかし、その方法は良くありません。自分のコードで全体証明書の検証を手動で実行する必要があるからです。ただし、.NET Frameworkですでに実装(およびテスト)されている証明書検証プロセス全体(CRLのダウンロードやチェックなど)を再実装したいのですが、notです。これは、車輪の再発明のようなものであり、既存の.NET実装ほど徹底的にテストすることはできません。

15
dtr84

RemoteCertificateValidationCallbackデリゲートは、ソリューションへの正しい方法です。ただし、Olivierが提案したものとは異なる動作をデリゲートで使用します。そのため、関連性のないチェックが多すぎて実行されません。

したがって、問題を詳細に見てください。

最初に、サービスが商用CAから購入した正当な証明書を使用するシナリオを検討します(これは現在は当てはまらないかもしれませんが、将来的にはそうなる可能性があります)。これは、sslPolicyErrorsパラメータにNoneフラグが表示されている場合、すぐにTrueを返すことを意味します。証明書は有効であり、拒否する明確な理由はありません。この手順は、次のステートメントが厳密でない場合にのみ必要です。

私のアプリケーションの「信頼できる」ルートとして受け入れられる必要があるのは、Root-CAだけです。

それ以外の場合は、最初のステップを無視してください。

サービスがまだプライベートで信頼できないCAからの証明書を使用していると仮定しましょう。この場合、証明書チェーンに関連せず、SSLセッションのみに固有のエラーを処理する必要があります。したがって、RemoteCertificateValidationCallbackデリゲートが呼び出されると、RemoteCertificateNameMismatchおよびRemoteCertificateNotAvailableフラグがsslPolicyErrorsパラメーターに表示されないようにする必要があります。それらのいずれかが提示された場合、追加のチェックなしで接続を拒否します。

これらのフラグのいずれも提示されていないと仮定しましょう。この時点で、SSL固有のエラーを正しく処理し、証明書チェーンのみに問題がある可能性があります。

ここまで到達すると、sslPolicyErrorsパラメータにRemoteCertificateChainErrorsフラグが含まれていると主張できます。これはすべてを意味する可能性があり、追加のチェックを行う必要があります。ルートCA証明書は定数です。これは、chainパラメーターでルート証明書を調べて、それを定数(たとえば、ルートCA証明書の拇印)と比較できることを意味します。比較に失敗した場合、証明書はすぐに拒否されます。これは、証明書があなたのものではなく、不明なCAによって発行された証明書を信頼する明確な理由がなく、他のチェーンの問題がある可能性があるためです。

比較が成功した場合、慎重かつ適切に処理する必要があるというケースに到達しました。証明書チェーンエンジンの別のインスタンスを実行し、UntrustedRootエラーのみを除くチェーンの問題を収集するように指示する必要があります。これは、SSL証明書に他の問題(RevocationOffline、有効性、ポリシーエラーなど)がある場合、それを認識し、この証明書を拒否することを意味します。

以下のコードは、上記の多くの単語をプログラムで実装したものです。

using System;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

namespace MyNamespace {
    class MyClass {
        Boolean ServerCertificateValidationCallback(Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) {
            String rootCAThumbprint = ""; // write your code to get your CA's thumbprint

            // remove this line if commercial CAs are not allowed to issue certificate for your service.
            if ((sslPolicyErrors & (SslPolicyErrors.None)) > 0) { return true; }

            if (
                (sslPolicyErrors & (SslPolicyErrors.RemoteCertificateNameMismatch)) > 0 ||
                (sslPolicyErrors & (SslPolicyErrors.RemoteCertificateNotAvailable)) > 0
            ) { return false; }
            // get last chain element that should contain root CA certificate
            // but this may not be the case in partial chains
            X509Certificate2 projectedRootCert = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;
            if (projectedRootCert.Thumbprint != rootCAThumbprint) {
                return false;
            }
            // execute certificate chaining engine and ignore only "UntrustedRoot" error
            X509Chain customChain = new X509Chain {
                ChainPolicy = {
                    VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority
                }
            };
            Boolean retValue = customChain.Build(chain.ChainElements[0].Certificate);
            // RELEASE unmanaged resources behind X509Chain class.
            customChain.Reset();
            return retValue;
        }
    }
}

このメソッド(デリゲートという名前)は、ServicePointManager.ServerCertificateValidationCallbackにアタッチできます。コードが圧縮される可能性があります(たとえば、1つのIFステートメントに複数のIFを組み合わせる)。テキストロジックを反映するために詳細バージョンを使用しました。

14
Crypt32

そのコールバックを使用でき、車輪の再発明をする必要はありません。

よく見ると、 コールバックには複数のパラメータがあります

public delegate bool RemoteCertificateValidationCallback(
    object sender,
    X509Certificate certificate,
    X509Chain chain,
    SslPolicyErrors sslPolicyErrors
)

パラメータは、.Net実装によるチェックの結果です。あなたは例えばすることができます次のコードを使用して、唯一の問題がルートCAの欠落であるかどうかを確認します。

// Check if the only error of the chain is the missing root CA,
// otherwise reject the given certificate.
if (chain.ChainStatus.Any(statusFlags => statusFlags.Status != X509ChainStatusFlags.UntrustedRoot))
    return false;

チェーン全体を反復処理し、すべての状態をチェックします。信頼できないルート(列挙型にはフラグ属性があることに注意してください)以外のものがある場合は失敗します。しかし、唯一の悪いことが信頼されていないルートである場合、あなたはそれを取ることができます。

しかし、今あなたが直面しているもう1つの問題は、この証明書に信頼できないルートがあることはわかっているが、それが本当に自分の証明書(またはその他)であるかどうかわからないことです。

これを確実にするには、アプリケーションとともに保存したサーバー証明書の公開部分を読み取り、それを特定のチェーンと比較する必要があります。

// Read CA certificate from file.
var now = DateTime.UtcNow;
var certificateAuthority = new X509Certificate(_ServerCertificateLocation);
var caEffectiveDate = DateTime.Parse(certificateAuthority.GetEffectiveDateString());
var caExpirationDate = DateTime.Parse(certificateAuthority.GetExpirationDateString());

// Check if CA certificate is valid.
if (now <= caEffectiveDate
    || now > caExpirationDate)
    return false;

// Check if CA certificate is available in the chain.
return chain.ChainElements.Cast<X509ChainElement>()
                          .Select(element => element.Certificate)
                          .Where(chainCertificate => chainCertificate.Subject == certificateAuthority.Subject)
                          .Where(chainCertificate => chainCertificate.GetRawCertData().SequenceEqual(certificateAuthority.GetRawCertData()))
                          .Any();

サーバーがインストールされたルートCAによって署名された証明書を配信する場合は、関数の先頭に高速出口を追加するのが賢明かもしれません(これはおそらくあなたのものではありません!):

if (sslPolicyErrors == SslPolicyErrors.None
    && chain.ChainStatus.All(statusFlags => statusFlags.Status == X509ChainStatusFlags.NoError))
    return true;

また、必要に応じて(ニーズに応じて)、サーバー名と証明書名が一致しない証明書の使用を許可または禁止できます。これが発生します。証明書がlocalhost用に作成されていて、別のマシンからサーバーにアクセスする場合。または、イントラネット内では、http://myserverではなくhttp://myserver.domain.comのみを使用しますが、証明書は完全修飾名で作成されています(またはその逆)。

if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateNameMismatch)
    return false;

以上です。あなたはまだデフォルトの実装に依存していて、あなたのパーツを追加でチェックするだけです。

2
Oliver