리눅스[데비안, WSL]에서 [apis.data.go.kr] 사이트 오류 문제

안녕하세요.

다름이 아니라 apis.data.go.kr 사이트는 공공데이터포털 사이트 인데 windows[11] 에서는 잘 동작하는데 리눅스[데비안12, WSL]에서 오류를 발생하네요.
대상 도메인을 https://example.com/ 으로 바꾸어 보면 오류가 없습니다.
여러가지 방법으로 시도해 보았는데 패배 했네요…

  • 오류코드
System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
 ---> System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception.
 ---> Interop+OpenSsl+SslException: SSL Handshake failed with OpenSSL error - SSL_ERROR_SSL.
 ---> Interop+Crypto+OpenSslCryptographicException: error:0A000417:SSL routines::sslv3 alert illegal parameter
   --- End of inner exception stack trace ---
  • 소스
using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        try
        {
            using HttpClient client = new HttpClient();
            var response = await client.GetAsync("https://apis.data.go.kr");
            Console.WriteLine(await response.Content.ReadAsStringAsync());
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }
    }
}
1개의 좋아요

apis.data.go.kr 쪽 서버가 TLS 설정이 너무 낮은 버전이라서 생기는 현상일 수 있을 것 같습니다.

윈도우에서는 잘 되는데 리눅스(특히 Debian 12나 WSL)에서는 SSL 오류가 나는 이유가, 리눅스 쪽이 좀 더 엄격하게 보안 정책을 적용하기 때문인듯 합니다.

좀 더 구체적으로 말하면, apis.data.go.kr 서버가 요즘 거의 안 쓰는 3DES나 SEED 같은 오래된 암호화 방식만 지원하고 있는데, 최신 리눅스에서는 이런 암호군을 기본적으로 막아버립니다.

반면, 윈도우는 아직 구식 암호화 방식도 허용하다 보니 아무 문제 없이 접속이 되겠고요.

그래서 제 생각에는 해결 방법은 크게 두 가지입니다.

가장 좋은 건 서버 쪽이 TLS 구성을 최신으로 바꿔서 접속할 수 있는 대체 경로를 제공하는지 문의해보시는 것일텐데 요즘 표준 암호군(ECDHE, AES-GCM 등)만 추가해도 리눅스에서도 문제없이 접속됩니다.

그런데 서버 쪽 대안 제공이 어렵다면, 임시로 리눅스에서 구식 암호군도 허용하도록 OpenSSL 설정을 낮출 수는 있습니다.

/etc/ssl/openssl.cnf 파일을 수정해서 레거시(legacy) 암호군을 활성화하고, SECLEVEL=1로 보안레벨을 한 단계 내리는 식인데, 이렇게 하면 일단 통신은 될것 같지만, 보안 수준을 낮추는것이니 근본적인 문제는 덮고 가는 것이라서 문제있어 보이네요.

1개의 좋아요

LLM에서 알려준 여러가지 방법도 해보고 저렇게 까지 해봤는데 않되더라구요

nano /etc/ssl/openssl.cnf

[system_default_sect]
MinProtocol = TLSv1.2
CipherString = DEFAULT@SECLEVEL=1

흥미롭네요. Python에서도 막상 특별한 설정 없이 가능하기에 찾아보니 .NET이 스스로 통신할 수 있는 보안 수준을 높게 잡도록 정책을 .NET 5 이후 변경했기 때문에 발생하는 차이인 것 같습니다.

OpenSSL config 파일 수정 없이 아래 코드만으로 통신이 성공했습니다.

using System;
using System.Net.Http;
using System.Net.Security;
using System.Security.Authentication;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        try
        {
            var handler = new SocketsHttpHandler
            {
                SslOptions =
                {
                    // ① TLS 1.2만 강제 – RSA-CBC는 1.3에서 사라집니다
                    EnabledSslProtocols = SslProtocols.Tls12,

                    // ② 서버가 수락하는 RSA-CBC 수트를 명시
                    CipherSuitesPolicy = new CipherSuitesPolicy(
                        new[]
                        {
                            TlsCipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,       // 0x2F
                            TlsCipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA        // 0x35
                        })
                }
            };

            using HttpClient client = new HttpClient(handler);
            var response = await client.GetAsync("https://apis.data.go.kr");
            Console.WriteLine(await response.Content.ReadAsStringAsync());
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }
    }
}

이렇게 실행했을 때 openssl 설정을 특별히 바꾸지 않고도 통신이 되는듯 합니다.

서버에서 실제로 사용하는 프로토콜이 무엇인지 알아내고, Cipher Suite 파라미터를 조정하는 절차를 거쳐서 디버깅을 했습니다.

openssl s_client -connect apis.data.go.kr:443 -tls1_2 -cipher 'ALL'

참고 자료: Breaking change: Default TLS cipher suites for .NET on Linux - .NET | Microsoft Learn

4개의 좋아요

추가: 특정 OS에서만 사용 가능하다는 CipherSuitesPolicy에 관한 플랫폼 빌드 경고/오류 해결은 RuntimeInformation.IsOSPlatform(OSPlatform.Linux) 코드를 사용하여 분기를 구분하면 컴파일러 레벨에서도 accept이 되므로 해결이 되실겁니다.

1개의 좋아요