Share via


TLS/SSL のベスト プラクティス

TLS (トランスポート層セキュリティ) は、インターネット経由で 2 台のコンピューター間の通信をセキュリティで保護するために設計された暗号化プロトコルです。 TLS プロトコルは、SslStream クラスを介して .NET で公開されます。

この記事では、クライアントとサーバー間のセキュリティで保護された通信を設定するためのベスト プラクティスについて説明し、.NET を使用することを前提としています。 .NET Framework のベスト プラクティスについては、「.NET Framework でのトランスポート層セキュリティ (TLS) のベスト プラクティス」を参照してください。

TLS バージョンを選択する

EnabledSslProtocols プロパティを介して使用する TLS プロトコルのバージョンを指定することはできますが、None 値 (これが既定値です) を使ってオペレーティング システムの設定を延期することをお勧めします。

OS の決定を延期すると、使用可能な最新バージョンの TLS が自動的に使用され、OS のアップグレード後にアプリケーションで変更内容を取得できるようになります。 オペレーティング システムにより、安全と見なされなくなった TLS バージョンの使用が妨げられる場合もあります。

暗号スイートを選択する

SslStream を使用すると、ユーザーは、CipherSuitesPolicy クラスを介して TLS ハンドシェイクによってネゴシエートできる暗号スイートを指定できます。 TLS バージョンと同様に、OS でネゴシエートに最適な暗号スイートを決定することをお勧めします。そのため、CipherSuitesPolicy を使用しないことをお勧めします。

Note

CipherSuitesPolicy は Windows ではサポートされておらず、インスタンス化を試みると、NotSupportedException がスローされます。

サーバー証明書を指定する

サーバーとして認証する場合、SslStream には X509Certificate2 インスタンスが必要です。 秘密キーも含まれる X509Certificate2 インスタンスを常に使用することをお勧めします。

サーバー証明書を SslStream に渡すことができる方法は複数あります。

SslServerAuthenticationOptions.ServerCertificateContext プロパティを使用することをお勧めします。 他の 2 つの方法のいずれかで証明書を取得すると、SslStream 実装によって内部的に SslStreamCertificateContext インスタンスが作成されます。 SslStreamCertificateContext を作成するには、CPU を集中的に使用する操作である X509Chain を構築する必要があります。 SslStreamCertificateContext を 1 回作成し、複数の SslStream インスタンスに再利用する方が効率的です。

SslStreamCertificateContext インスタンスを再利用すると、Linux サーバー上で TLS セッションの再開などの追加機能も有効になります。

カスタム X509Certificate 検証

既定の証明書検証手順が適切ではなく、いくつかのカスタム検証ロジックが必要な特定のシナリオがあります。 検証ロジックの一部は、SslClientAuthenticationOptions.CertificateChainPolicy または SslServerAuthenticationOptions.CertificateChainPolicy を指定してカスタマイズできます。 あるいは、<System.Net.Security.SslClientAuthenticationOptions.RemoteCertificateValidationCallback> プロパティを使用して、完全にカスタム ロジックを指定することもできます。 詳細については、「カスタム証明書の信頼」を参照してください。

カスタム証明書の信頼

コンピューターにより信頼されている証明機関によって発行されていない証明書 (自己署名証明書を含む) が検出されると、既定の証明書検証手順は失敗します。 これを解決する 1 つの方法として、必要な発行者証明書をコンピューターの信頼されたストアに追加することが考えられます。 しかし、これはシステム上の他のアプリケーションに影響を与える可能性があり、常に可能であるとは限りません。

別の解決策は、X509ChainPolicy を使用してカスタムの信頼されたルート証明書を指定することです。 検証時にシステム信頼リストの代わりに使用されるカスタム信頼リストを指定する場合は、次の例を考えてみてください。

SslClientAuthenticationOptions clientOptions = new();

clientOptions.CertificateChainPolicy = new X509ChainPolicy()
{
    TrustMode = X509ChainTrustMode.CustomRootTrust,
    CustomTrustStore =
    {
        customIssuerCert
    }
};

上記のポリシーで構成されたクライアントは、customIssuerCert によって信頼された証明書のみを受け入れます。

特定の検証エラーを無視する

永続クロックのない IoT デバイスを考えてみましょう。 電源を入れた後、デバイスのクロックは何年も前に開始されることになるため、すべての証明書は "まだ有効ではない" と見なされます。 有効期間違反を無視する検証コールバックの実装を示す次のコードについて考えてみましょう。

static bool CustomCertificateValidationCallback(
    object sender,
    X509Certificate? certificate,
    X509Chain? chain,
    SslPolicyErrors sslPolicyErrors)
{
    // Anything that would have been accepted by default is OK
    if (sslPolicyErrors == SslPolicyErrors.None)
    {
        return true;
    }
    
    // If there is something wrong other than a chain processing error, don't trust it.
    if (sslPolicyErrors != SslPolicyErrors.RemoteCertificateChainErrors)
    {
        return false;
    }
    
    Debug.Assert(chain is not null);

    // If the reason for RemoteCertificateChainError is that the chain built empty, don't trust it.
    if (chain.ChainStatus.Length == 0)
    {
        return false;
    }

    foreach (X509ChainStatus status in chain.ChainStatus)
    {
        // If an error other than `NotTimeValid` (or `NoError`) is present, don't trust it.
        if ((status.Status & ~X509ChainStatusFlags.NotTimeValid) != X509ChainStatusFlags.NoError)
        {
            return false;
        }
    }

    return true;
}

証明書のピン留め

カスタム証明書の検証が必要なもう 1 つの状況は、クライアントがサーバーで特定の証明書、または既知の証明書の小さなセットの証明書を使用することを想定する場合です。 この方法は証明書のピン留めと呼ばれます。 次のコード スニペットは、サーバーで特定の既知の公開キーを持つ証明書が提示されることを確認する検証コールバックを示しています。

static bool CustomCertificateValidationCallback(
    object sender,
    X509Certificate? certificate,
    X509Chain? chain,
    SslPolicyErrors sslPolicyErrors)
{
    // If there is something wrong other than a chain processing error, don't trust it.
    if ((sslPolicyErrors & ~SslPolicyErrors.RemoteCertificateChainErrors) != 0)
    {
        return false;
    }
    
    Debug.Assert(certificate is not null);

    const string ExpectedPublicKey =
        "3082010A0282010100C204ECF88CEE04C2B3D850D57058CC9318EB5C" +
        "A86849B022B5F9959EB12B2C763E6CC04B604C4CEAB2B4C00F80B6B0" +
        "F972C98602F95C415D132B7F71C44BBCE9942E5037A6671C618CF641" +
        "42C546D31687279F74EB0A9D11522621736C844C7955E4D16BE8063D" +
        "481552ADB328DBAAFF6EFF60954A776B39F124D131B6DD4DC0C4FC53" +
        "B96D42ADB57CFEAEF515D23348E72271C7C2147A6C28EA374ADFEA6C" +
        "B572B47E5AA216DC69B15744DB0A12ABDEC30F47745C4122E19AF91B" +
        "93E6AD2206292EB1BA491C0C279EA3FB8BF7407200AC9208D98C5784" +
        "538105CBE6FE6B5498402785C710BB7370EF6918410745557CF9643F" +
        "3D2CC3A97CEB931A4C86D1CA850203010001";

    return certificate.GetPublicKeyString().Equals(ExpectedPublicKey);
}

クライアント証明書の検証に関する考慮事項

サーバー アプリケーションでクライアント証明書を要求して検証するときには注意する必要があります。 証明書には、発行者証明書をダウンロードできる場所を指定する AIA (Authority Information Access) 拡張機能が含まれている場合があります。 そのため、クライアント証明書の X509Chain を構築するときに、サーバーで外部サーバーからの発行者証明書のダウンロードが試行される場合があります。 同様に、クライアント証明書が取り消されていないことを確かめるために、サーバーを外部サーバーに接続する必要がある場合があります。

X509Chain を構築して検証するときに外部サーバーに接続する必要があると、外部サーバーの応答が遅い場合にアプリケーションがサービス拒否攻撃にさらされる可能性があります。 そのため、サーバー アプリケーションでは、CertificateChainPolicy を使用して X509Chain 構築動作を構成する必要があります。