Praktik terbaik TLS/SSL

TLS (Transport Layer Security) adalah protokol kriptografi yang dirancang untuk mengamankan komunikasi antara dua komputer melalui internet. Protokol TLS diekspos di .NET melalui SslStream kelas .

Artikel ini menyajikan praktik terbaik untuk menyiapkan komunikasi yang aman antara klien dan server dan mengasumsikan penggunaan .NET. Untuk praktik terbaik dengan .NET Framework, lihat Praktik terbaik Keamanan Lapisan Transportasi (TLS) dengan .NET Framework.

Pilih versi TLS

Meskipun dimungkinkan untuk menentukan versi protokol TLS yang akan digunakan melalui properti EnabledSslProtocols, disarankan untuk mengandalkan pengaturan sistem operasi dengan menggunakan nilai None (ini adalah bawaan).

Menuangkan keputusan ke OS secara otomatis menggunakan versi terbaru TLS yang tersedia dan memungkinkan aplikasi mengambil perubahan setelah peningkatan OS. Sistem operasi juga dapat mencegah penggunaan versi TLS yang tidak lagi dianggap aman.

Pilih cipher suite

SslStream memungkinkan pengguna untuk menentukan cipher suite mana yang dapat dinegosiasikan oleh jabat tangan TLS melalui CipherSuitesPolicy kelas . Seperti halnya versi TLS, disarankan untuk membiarkan OS memutuskan cipher suite mana yang terbaik untuk dinegosiasikan dengan, dan, oleh karena itu, disarankan untuk menghindari penggunaan CipherSuitesPolicy.

Nota

CipherSuitesPolicy tidak didukung pada Windows dan upaya untuk membuat instans itu akan menyebabkan NotSupportedException dilemparkan.

Tentukan sertifikat lokal

Saat mengautentikasi sebagai server, SslStream selalu memerlukan sertifikat. Saat mengautentikasi sebagai klien, Anda juga memberikan sertifikat jika server memintanya untuk TLS bersama (mTLS). Dalam kedua peran tersebut, sertifikat harus berupa instans X509Certificate2 yang berisi kunci privat.

Versi .NET terbaru menangani sisi server dan klien secara simetris, sehingga panduan berikut berlaku apakah aplikasi mengautentikasi sebagai server, klien, atau keduanya.

Anda dapat memberikan sertifikat kepada SslStream dengan beberapa cara.

Saat Anda mengautentikasi sebagai server:

Saat Anda mengautentikasi sebagai klien:

Nota

Properti ClientCertificateContext ini tersedia mulai dari .NET 8.

Untuk performa yang lebih baik, gunakan properti konteks sertifikat (ServerCertificateContext atau ClientCertificateContext). Saat Anda memberikan sertifikat dengan salah satu cara lainnya, SslStream membuat SslStreamCertificateContext secara internal. Pembuatan konteks menghasilkan X509Chain, yang merupakan operasi yang membutuhkan CPU secara intensif, sehingga lebih efisien untuk membuat konteks satu kali dan menggunakannya kembali di beberapa instans SslStream.

Menggunakan kembali instance SslStreamCertificateContext juga memungkinkan fitur tambahan seperti pelanjutan sesi TLS di server Linux.

Mengirim sertifikat perantara ke peer

Ketika otoritas sertifikat perantara mengeluarkan sertifikat lokal, rekan mungkin tidak dapat membangun rantai sertifikat penuh kecuali jabat tangan menyertakan sertifikat perantara. Untuk mengirim perantara ini, buat SslStreamCertificateContext dengan Create metode dan berikan sertifikat perantara dalam additionalCertificates parameter:

X509Certificate2 leafCertificate = GetLeafCertificate();
X509Certificate2Collection intermediates = GetIntermediateCertificates();

SslStreamCertificateContext certificateContext =
    SslStreamCertificateContext.Create(leafCertificate, intermediates);

// When you authenticate as a server.
serverOptions.ServerCertificateContext = certificateContext;

// When you authenticate as a client for mutual TLS.
clientOptions.ClientCertificateContext = certificateContext;

Konteks bekerja dengan cara yang sama untuk kedua peran. Di sisi klien, konteks sertifikat adalah cara yang disarankan untuk mengirim perantara, karena alternatifnya—menambahkan perantara ke komputer atau penyimpanan sertifikat pengguna—memengaruhi setiap aplikasi pada sistem.

Validasi kustom X509Certificate

Ada skenario tertentu di mana prosedur validasi sertifikat default tidak memadai dan beberapa logika validasi kustom diperlukan. Bagian dari logika validasi dapat disesuaikan dengan menentukan SslClientAuthenticationOptions.CertificateChainPolicy atau SslServerAuthenticationOptions.CertificateChainPolicy. Atau, logika kustom sepenuhnya dapat disediakan melalui <properti System.Net.Security.SslClientAuthenticationOptions.RemoteCertificateValidationCallback> . Untuk informasi selengkapnya, lihat Kepercayaan sertifikat kustom.

Kepercayaan sertifikat kustom

Saat menemukan sertifikat yang tidak dikeluarkan oleh salah satu otoritas sertifikat yang dipercaya oleh mesin (termasuk sertifikat yang ditandatangani sendiri), prosedur validasi sertifikat default akan gagal. Salah satu cara yang mungkin untuk mengatasinya adalah dengan menambahkan sertifikat penerbit yang diperlukan ke penyimpanan tepercaya komputer. Namun, itu dapat memengaruhi aplikasi lain pada sistem dan tidak selalu memungkinkan.

Solusi alternatifnya adalah menentukan sertifikat akar tepercaya kustom melalui X509ChainPolicy. Untuk menentukan daftar kepercayaan kustom yang akan digunakan alih-alih daftar kepercayaan sistem selama validasi, pertimbangkan contoh berikut:

SslClientAuthenticationOptions clientOptions = new();

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

Klien yang dikonfigurasi dengan kebijakan sebelumnya hanya akan menerima sertifikat yang dipercaya oleh customIssuerCert.

Abaikan kesalahan validasi tertentu

Pertimbangkan perangkat IoT tanpa jam persisten. Setelah dinyalakan, jam perangkat akan mulai menunjukkan waktu bertahun-tahun yang lalu, dan oleh karena itu, semua sertifikat akan dianggap "belum valid". Pertimbangkan kode berikut yang menunjukkan implementasi panggilan balik validasi yang mengabaikan pelanggaran periode validitas.

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;
}

Penyematan sertifikat

Situasi lain di mana validasi sertifikat kustom diperlukan adalah ketika klien mengharapkan server untuk menggunakan sertifikat tertentu, atau sertifikat dari sekumpulan kecil sertifikat yang diketahui. Praktik ini dikenal sebagai penyematan sertifikat. Cuplikan kode berikut menunjukkan panggilan balik validasi yang memeriksa apakah server menyajikan sertifikat dengan kunci publik tertentu yang diketahui.

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);
}

Pertimbangan untuk validasi sertifikat klien

Aplikasi server harus berhati-hati saat memerlukan dan memvalidasi sertifikat klien. Sertifikat mungkin berisi ekstensi AIA (Akses Informasi Otoritas) yang menentukan di mana sertifikat penerbit dapat diunduh. Oleh karena itu, server dapat mencoba mengunduh sertifikat penerbit dari server eksternal saat membangun X509Chain untuk sertifikat klien. Demikian pula, server mungkin perlu menghubungi server eksternal untuk memastikan bahwa sertifikat klien belum dicabut.

Kebutuhan untuk menghubungi server eksternal saat membangun dan memvalidasi X509Chain mungkin mengekspos aplikasi terhadap penolakan serangan layanan jika server eksternal lambat merespons. Oleh karena itu, aplikasi server harus mengonfigurasi perilaku pembangunan X509Chain menggunakan CertificateChainPolicy.

Nota

Mulai .NET 11, SslStream menonaktifkan unduhan sertifikat AIA secara default saat memvalidasi sertifikat klien sebagai server. Jika tidak ada kustom CertificateChainPolicy yang disediakan, server tidak akan mencoba mengambil sertifikat perantara yang hilang melalui AIA. Untuk informasi selengkapnya, lihat Unduhan sertifikat AIA pada sisi server SslStream yang secara default dinonaktifkan.