TLS/SSL 모범 사례

TLS(전송 계층 보안)는 인터넷을 통해 두 컴퓨터 간의 통신을 보호하도록 설계된 암호화 프로토콜입니다. .NET에서 SslStream 클래스를 통해 TLS 프로토콜이 노출됩니다.

이 문서에서는 클라이언트와 서버 간의 보안 통신을 설정하는 모범 사례를 제시하고 .NET을 사용하는 것으로 가정합니다. .NET Framework의 모범 사례는 .NET Framework를 사용한 TLS(전송 계층 보안) 모범 사례를 참조하세요.

TLS 버전 선택

속성을 통해 EnabledSslProtocols 사용 가능한 TLS 프로토콜의 버전을 지정할 수 있지만, None 값을 사용하여 운영 체제 설정에 따르는 것이 좋습니다(이것이 기본값임).

결정을 OS로 연기하면 사용 가능한 최신 버전의 TLS가 자동으로 사용되며 OS 업그레이드 후 애플리케이션이 변경 내용을 선택할 수 있습니다. 운영 체제는 더 이상 안전한 것으로 간주되지 않는 TLS 버전을 사용하지 못할 수도 있습니다.

암호 그룹 선택

SslStream 에서는 사용자가 클래스를 통해 CipherSuitesPolicy TLS 핸드셰이크로 협상할 수 있는 암호 그룹을 지정할 수 있습니다. TLS 버전과 마찬가지로 OS에서 협상하기에 가장 적합한 암호화 제품군을 결정할 수 있도록 하는 것이 좋습니다. 따라서 사용하지 CipherSuitesPolicy않는 것이 좋습니다.

비고

CipherSuitesPolicy 는 Windows에서 지원되지 않으며 인스턴스화를 시도하면 NotSupportedException 예외가 발생합니다.

로컬 인증서 지정

서버로 인증할 때는 SslStream 항상 인증서가 필요합니다. 클라이언트로 인증할 때 서버가 mTLS(상호 TLS)에 대해 인증서를 요청하는 경우에도 인증서를 제공합니다. 두 역할 모두에서 인증서는 프라이빗 키가 포함된 인스턴스여야 X509Certificate2 합니다.

최근 .NET 버전은 서버와 클라이언트 쪽을 대칭적으로 처리하므로 다음 지침은 애플리케이션이 서버, 클라이언트 또는 둘 다로 인증하는지에 따라 적용됩니다.

인증서 SslStream 를 여러 가지 방법으로 제공할 수 있습니다.

서버로 인증하는 경우:

클라이언트로 인증하는 경우:

비고

이 속성은 ClientCertificateContext .NET 8부터 사용할 수 있습니다.

성능을 향상시키려면 인증서 컨텍스트 속성(ServerCertificateContext 또는 ClientCertificateContext)을 사용합니다. 다른 방법 중 하나로 인증서를 제공하면 SslStream가 내부적으로 SslStreamCertificateContext를 생성합니다. 컨텍스트를 만들면 CPU 집약적 작업인 컨텍스트가 빌드 X509Chain되므로 컨텍스트를 한 번 만들고 여러 SslStream 인스턴스에서 다시 사용하는 것이 더 효율적입니다.

인스턴스를 SslStreamCertificateContext 다시 사용하면 Linux 서버에서 TLS 세션 다시 시작 과 같은 추가 기능도 사용할 수 있습니다.

피어에 중간 인증서 보내기

중간 인증 기관에서 로컬 인증서를 발급하는 경우 핸드셰이크에 중간 인증서가 포함되어 있지 않으면 피어가 전체 인증서 체인을 빌드하지 못할 수 있습니다. 이러한 중간자를 보내려면 메서드를 사용하여 SslStreamCertificateContextCreate 만들고 매개 변수에 중간 인증서를 additionalCertificates 전달합니다.

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;

컨텍스트는 두 역할 모두에 대해 동일한 방식으로 작동합니다. 클라이언트 측에서는 중간 인증서를 전송하는 데 인증서 컨텍스트를 사용하는 것이 권장됩니다. 그 대안인 중간 인증서를 컴퓨터 또는 사용자 인증서 저장소에 추가하는 방법은 시스템의 모든 애플리케이션에 영향을 미치기 때문입니다.

사용자 지정 X509Certificate 유효성 검사

기본 인증서 유효성 검사 절차가 적절하지 않고 일부 사용자 지정 유효성 검사 논리가 필요한 특정 시나리오가 있습니다. 유효성 검사 논리의 일부를 지정 SslClientAuthenticationOptions.CertificateChainPolicy 하거나 SslServerAuthenticationOptions.CertificateChainPolicy지정하여 사용자 지정할 수 있습니다. 또는 System.Net.Security.SslClientAuthenticationOptions.RemoteCertificateValidationCallback< 속성을 통해 >완전히 사용자 지정 논리를 제공할 수 있습니다. 자세한 내용은 사용자 지정 인증서 트러스트를 참조하세요.

사용자 지정 인증서 신뢰

컴퓨터에서 신뢰하는 인증 기관에서 발급하지 않은 인증서(자체 서명된 인증서 포함)가 발견되면 기본 인증서 유효성 검사 절차가 실패합니다. 이 문제를 해결할 수 있는 한 가지 방법은 컴퓨터의 신뢰할 수 있는 저장소에 필요한 발급자 인증서를 추가하는 것입니다. 그러나 이는 시스템의 다른 애플리케이션에 영향을 줄 수 있으며 항상 가능한 것은 아닙니다.

대체 솔루션은 .를 통해 신뢰할 수 있는 사용자 지정 루트 인증서를 지정하는 것입니다 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;
}

인증서 고정

사용자 지정 인증서 유효성 검사가 필요한 또 다른 상황은 클라이언트가 서버에서 특정 인증서 또는 알려진 인증서의 작은 집합의 인증서를 사용해야 하는 경우입니다. 이 방법을 인증서 고정이라고 합니다. 다음 코드 조각은 서버가 알려진 특정 공개 키가 있는 인증서를 제공하는지 확인하는 유효성 검사 콜백을 보여 줍니다.

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(기관 정보 액세스) 확장이 포함될 수 있습니다. 따라서 서버는 클라이언트 인증서를 빌드할 때 외부 서버에서 발급자 인증서를 X509Chain 다운로드하려고 시도할 수 있습니다. 마찬가지로 서버는 외부 서버에 연결하여 클라이언트 인증서가 해지되지 않았는지 확인해야 할 수 있습니다.

외부 서버를 빌드하고 유효성을 X509Chain 검사할 때 외부 서버에 연결해야 하는 경우 외부 서버가 응답 속도가 느린 경우 애플리케이션이 서비스 거부 공격에 노출될 수 있습니다. 따라서 서버 애플리케이션은 X509Chain의 빌드 동작을 CertificateChainPolicy을 사용하여 구성해야 합니다.

비고

.NET 11부터 SslStream 클라이언트 인증서를 서버로 유효성을 검사할 때 기본적으로 AIA 인증서 다운로드를 사용하지 않도록 설정합니다. 사용자 지정 CertificateChainPolicy 이 제공되지 않으면 서버는 AIA를 통해 누락된 중간 인증서를 가져오려고 시도하지 않습니다. 자세한 내용은 기본적으로 사용하지 않도록 설정된 SslStream 서버 쪽 AIA 인증서 다운로드를 참조하세요.