WolfSSL도 별로야, 이제 어떻게 할까?

발행: (2026년 2월 13일 오후 07:18 GMT+9)
12 분 소요

Source: Hacker News

(번역할 텍스트를 제공해 주시면 한국어로 번역해 드리겠습니다.)

암호화 라이브러리 현황 – 간단한 생각

  • OpenSSL – 일반적으로 신뢰할 수 있지만, 일부 프로젝트에서는 무겁게 느껴질 수 있습니다.
  • BoringSSL (Google) – 구글 내부 요구에 맞게 맞춤화되어 있어, 외부 사용 사례에 항상 완벽하게 맞지는 않을 수 있습니다.
  • AWS‑LC (Amazon) – 아마존 서비스에 최적화되어 있으며, 보다 넓은 커뮤니티 중심이 부족할 수 있습니다.
  • GnuTLS – 역사적으로 견고하지만, 개인적인 경험은 엇갈렸습니다.
  • LibreSSL – 칭찬할 만한 시도이지만, 동료들에 비해 여전히 일부 기능이 부족합니다.

Note: 각 라이브러리는 고유한 설계 목표와 트레이드오프를 가지고 있습니다. 올바른 선택은 귀하의 구체적인 요구사항, 위협 모델 및 생태계 제약에 따라 달라집니다.

지금 무슨 일이 일어났나요?

지난 해 HAProxy의 기사에서 OpenSSL이 얼마나 끔찍하게 느려졌는지를 강조했습니다. 이 이야기는 여러 차례 퍼졌고, 저는 wolfSSL로 빌드된 HAProxy 변형을 FreeBSD에 패키징하도록 돕기로 했습니다.

대부분의 Linux 배포판이 이러한 빌드를 제공하지 않을 것이기 때문에, wolfSSL에 더 넓은 노출을 주는 쉬운 방법처럼 보였습니다. 실제로는 wolfSSL을 기반으로 한 HAProxy를 실행하는 사람들은 자신이 무엇을 하는지 알고 직접 컴파일하는 경우가 대부분입니다. Arch, Gentoo, Nix 등에서 비슷한 패키지가 있는지는 확인하지 않았지만, 이들 배포판이 haproxy‑wolfssl 패키지를 만들기에 가장 직관적인 후보가 될 것입니다.

버그

wolfSSL로 HAProxy를 빌드하고 몇몇 환경에서 실행했지만 버그를 발견했습니다. 문제를 보고하고는 잊어버렸지만, 같은 문제가 다시 나타났습니다. 해결 의지를 갖고 버그 보고서를 다시 열어 코드를 파헤쳐 근본 원인을 찾아냈습니다.

TLS 1.3 및 미들박스

TLS 1.3은 RFC 8446에서 정의됩니다. TLS 1.2와는 상당히 다르게 동작하며, 호환성 문제를 많이 야기했습니다. 사양에는 다음과 같이 적혀 있습니다:

“TLS 1.3의 설계는 널리 배포된 비준수 TLS 미들박스에 의해 제한되었습니다.”

아, 악명 높은 미들박스—트래픽을 조작할 수 있는 눈에 보이지 않는 네트워크 인프라입니다. 존재를 알지 못하는 경우가 많지만, 문제가 생기면 분명히 드러납니다.

미들박스 지옥

미들박스는 사실상 “지옥”에서 발명되었으며, 아무리 소원을 빌어도 사라지지 않습니다. (아마도 몇몇 Etsy 마녀가 행운을 빌려줄 수 있겠지만, 그 이야기는 또 다른 이야기…)

문제는 우리가 TLS 1.2가 제공하는 것보다 더 강력한 보안 보장을 원하지만, 많은 미들박스가 TLS 1.2만 이해한다는 점입니다. TLS 1.3이 이러한 장치들에 의해 깨질 수 있기 때문에, TLS 1.3은 TLS 1.2인 척 해야 합니다.

RFC‑정의 우회 방법

TLS 1.3 사양에는 미들박스 호환 모드가 포함되어 있습니다(see RFC 8446, Appendix D.4). 아이디어는 간단하지만 비용이 많이 듭니다:

  1. ClientHello – 클라이언트가 비어 있지 않은 Session ID를 포함합니다. 이는 TLS 1.2 핸드쉐이크를 기대하는 미들박스를 속입니다.
  2. 더미 ChangeCipherSpec 레코드 – 클라이언트와 서버가 ChangeCipherSpec 메시지를 자리 표시자로 교환합니다. TLS 1.3에서는 무시되지만 레거시 장치의 기대를 만족시킵니다.
  3. TLS 1.3 진행 – 더미 교환이 끝난 후 정상적인 TLS 1.3 핸드쉐이크가 계속됩니다.

이것이 의미하는 바

  • 호환성 – TLS 1.2만 이해하는 미들박스를 통과할 수 있습니다.
  • 지연 패널티 – 추가된 Session ID와 더미 ChangeCipherSpec 레코드가 핸드쉐이크에 왕복 시간을 더합니다.
  • 보안 – 미들박스를 통과한 뒤에는 세션이 TLS 1.3의 전체 보안 보장을 누립니다.

요컨대, 미들박스 호환 모드는 비효율적이지만 실용적인 다리 역할을 하여 현대 TLS 1.3 연결이 레거시 미들박스가 지배하는 환경에서도 살아남을 수 있게 합니다.

거꾸로

RFC는 이 모든 것이 어떻게 진행되어야 하는지에 대해 꽤 명확합니다.

호환 모드

  • 이 모드는 부분적으로 협상됩니다:

    • 클라이언트는 세션 ID를 제공할 있습니다(또는 생략할 수 있음).
    • 서버는 세션 ID가 존재하면 반드시 에코해야 합니다.
  • 클라이언트가 비어 있지 않은 세션 ID를 전송하면, 서버는 부록에 설명된 대로 ChangeCipherSpec반드시 전송해야 합니다.


WolfSSL의 입장

WolfSSL은 라이브러리를 다음과 같이 컴파일하도록 요구합니다

-DWOLFSSL_TLS13_MIDDLEBOX_COMPAT

middlebox‑호환성 기능을 활성화합니다. 이 플래그는 라이브러리를 항상 이 모드로 하거나 전혀 사용하지 않도록 강제합니다—중간 옵션은 없습니다.

결과

  1. TLS 1.3 클라이언트는 WolfSSL과 신뢰성 있게 상호 운용될 수 없다고 간주됩니다.
  2. 호환성은 이제 클라이언트 구현이 얼마나 관대하게 동작하느냐에 전적으로 달려 있으며, 이는 신뢰를 주지 못합니다.
  3. 원문 끝에 링크된 GitHub 이슈 댓글에 따르면 WolfSSL 팀은 이 기능에 대해 RFC 준수를 우선순위에 두고 있지 않음을 시사합니다.

WolfSSL에는 middlebox 호환성을 위한 “절충점”이나 대체 구현이 없습니다: RFC‑준수 하거나 그렇지 않거나이며, 현재 구현은 후자에 해당합니다.

원고

현재 이 결정으로 인해 피해자를 한 명만 확인했지만, 더 많을 가능성이 있습니다.

Erlang/OTP는 자체 SSL 라이브러리 구현을 제공하며, 팀이 TLS 1.3 지원을 추가할 때 Joe Armstrong의 조언을 가슴에 새겼다고 합리적으로 추정할 수 있습니다:

“Make it work, then make it beautiful, then if you really, really have to, make it fast.” – Joe Armstrong

자신들을 보호하기 위해 Erlang/OTP 개발자들은 middlebox_comp_mode를 기본값으로 활성화했습니다(소스 라인 여기 참고).

  • 최대 속도를 원하고 안전하다고 확신한다면, 이 옵션을 끌 수 있습니다.

불행히도, 기본 설정으로 인해 이제 모든 Elixir/Erlang(및 관련) HTTP 클라이언트는 TLS 1.3이 사용 가능한 경우 WolfSSL HTTPS 서버에 연결하지 못합니다.

여기서 우리는 어디로 가야 할까요?

OpenBSD가 아마도 옳았습니다: LibreSSL에 집중하고 다른 TLS 라이브러리를 뒤섞어 보려는 시도를 멈춰야 합니다. HAProxy가 언급했듯이, OpenSSL 3.0 “실수”의 피해자가 아닌 이유는 더 일찍 포크했기 때문이지만, 몇몇 최적화는 놓치고 있습니다. 이는 타당한 트레이드‑오프이며, 그 격차는 시간이 지나면서 메워질 것입니다.

그러니 저처럼 되지 마세요. 제 자만심 때문에 사이트에 더 빠른 TLS 종료를 얻을 수 있다고 생각했지만, 필요 없는 것을 배우느라 많은 시간을 낭비하고 이 블로그 글을 쓰게 되었습니다. 경고했습니다.

Elixir PoC

Elixir 1.17.3(Erlang/OTP 26으로 컴파일)용 개념 증명은 아래 스크립트만큼 간단합니다.

#!/usr/bin/env elixir

url = "https://some-wolfssl-endpoint"
url = String.to_charlist(url)

{:ok, _} = Application.ensure_all_started(:inets)
{:ok, _} = Application.ensure_all_started(:ssl)

:logger.set_application_level(:ssl, :debug)

http_options = [
  ssl: [
    verify: :verify_peer,
    cacerts: :public_key.cacerts_get(),
    depth: 2,
    customize_hostname_check: [
      match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
    ],
    versions: [:"tlsv1.2", :"tlsv1.3"],
    middlebox_comp_mode: true   # ← toggle this
  ]
]

options = [body_format: :binary]

:httpc.request(:get, {url, []}, http_options, options)

middlebox_comp_mode: true일 때 예상되는 오류

11:00:44.996 [warning] Description: ~c"Failed to assert middlebox server message"
     Reason: [missing: {:change_cipher_spec, 1}]

11:00:45.014 [notice] TLS :client: In state :hello_middlebox_assert at ssl_gen_statem.erl:821 generated CLIENT ALERT: Fatal - Unexpected Message
 - {:unexpected_msg,
    {:internal,
      {:encrypted_extensions,
        %{
          elliptic_curves: {:supported_groups,
            [:secp521r1, :secp384r1, :secp256r1, :x25519, :ffdhe2048]}
        }}}}

이 출력이 보이면 “늑대에게 던져졌음”을 의미합니다.

해결 방법: http_options에서 middlebox_comp_modefalse로 설정하면 요청이 예상대로 성공합니다.

0 조회
Back to Blog

관련 글

더 보기 »