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の使用は避けてください。
注
CipherSuitesPolicy は Windows ではサポートされていないため、インスタンス化を試みると NotSupportedException がスローされます。
サーバー証明書を指定する
サーバーとして認証する場合、 SslStream には X509Certificate2 インスタンスが必要です。 秘密キーも含む X509Certificate2 インスタンスを常に使用することをお勧めします。
サーバー証明書を SslStream に渡すには、複数の方法があります。
- SslStream.AuthenticateAsServerAsyncまたはSslServerAuthenticationOptions.ServerCertificateプロパティ経由で、直接パラメーターとして使用する
- SslServerAuthenticationOptions.ServerCertificateSelectionCallback プロパティの選択コールバックから
- SslStreamCertificateContext プロパティにSslServerAuthenticationOptions.ServerCertificateContextを渡す
SslServerAuthenticationOptions.ServerCertificateContext プロパティを使用することをお勧めします。 他の 2 つの方法のいずれかで証明書を取得すると、SslStreamCertificateContext実装によって内部的にSslStream インスタンスが作成されます。 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を構築して検証するときに外部サーバーに連絡する必要があるため、外部サーバーの応答が遅い場合、アプリケーションがサービス拒否攻撃にさらされる可能性があります。 そのため、サーバー アプリケーションは、X509Chainを使用してCertificateChainPolicyビルド動作を構成する必要があります。
.NET