네이티브 TLS

지원 대상

 

TsgcWebSocketClient

TsgcTCPClient

TsgcHTTP1Client

TsgcHTTP2Client

TsgcWebSocketServer

TsgcWebSocketHTTPServer

TsgcHTTPServer

TsgcWebSocketProxyServer

TsgcWebSocketLoadBalancerServer

 

*Standard 에디션부터 제공되는 sgcCrypto 팩이 필요합니다.

 

네이티브 TLS 엔진은 sgcCrypto 유닛 위에 전부 Object Pascal로 작성된 TLS 1.3 및 TLS 1.2 구현입니다. 바인딩이 아닙니다. 배포할 OpenSSL DLL도, SChannel 호출도, 플랫폼 TLS 라이브러리도 관여하지 않습니다. 같은 소스가 Delphi 7부터 RAD Studio 13까지 컴파일되며, 이 구성 요소들이 지원하는 모든 플랫폼에서 실행됩니다.

 

이 엔진을 선택할 이유는 키 교환입니다. 엔진은 RFC 10024의 하이브리드 포스트 양자 그룹을 제공하며, X25519MLKEM768이 가장 먼저 제안하는 그룹입니다. 따라서 이 엔진으로 바꾼 클라이언트는 애플리케이션을 달리 고치지 않아도 포스트 양자 키 교환을 지원하는 모든 서버와 이를 협상합니다. 이 하이브리드 그룹은 TLS 1.3에만 있으므로, TLS 1.2로 대체되는 연결은 고전적인 곡선을 사용합니다. 이 그룹들이 무엇이고 오늘 하이브리드가 왜 합리적인 이행 방법인지는 포스트 양자 암호화를 참조하십시오.

 

켜는 방법

 

IOHandler만 설정하면 되고 그 밖에는 달라지는 것이 없습니다. 이미 사용하던 인증서, 키, 루트 인증서, 검증, ALPN 속성이 그대로 동작하며, 엔진은 같은 속성을 읽습니다.

 


// client
oClient := TsgcWebSocketClient.Create(nil);
oClient.URL := 'wss://www.esegece.com:2053';
oClient.TLS := True;
oClient.TLSOptions.IOHandler := iohNativeTLS;
oClient.TLSOptions.VerifyCertificate := True;
oClient.TLSOptions.RootCertFile := 'cacert.pem';
oClient.Active := True;

// server
oServer := TsgcWebSocketServer.Create(nil);
oServer.SSL := True;
oServer.SSLOptions.IOHandler := iohNativeTLS;
oServer.SSLOptions.CertFile := 'certificate.pem';
oServer.SSLOptions.KeyFile := 'key.pem';
oServer.Active := True;

 

서버에서는 이벤트 구동 엔진에서도 동작합니다. IOHandlerOptions.IOHandlerType을 Windows에서는 iohIOCP, Linux에서는 iohEPOLL로 설정하면 핸들러가 그에 맞는 TLS 제공자 위에 만들어지므로, 같은 구성이 세 가지 엔진 모두에서 동작합니다.

 

sgcCrypto 팩이 없는 빌드에서 iohNativeTLS를 선택하면 조용히 다른 라이브러리로 되돌아가지 않고 그 사실을 알리는 예외가 발생합니다.

 

TLS 버전

 

이 엔진은 클라이언트와 서버 모두에서, Indy 클라이언트와 Indy 서버에서, 그리고 IOCP와 EPOLL 서버 엔진에서 TLS 1.3과 TLS 1.2를 구사합니다. 양쪽이 모두 지원하면 항상 TLS 1.3이 선택됩니다. 네이티브 엔진에서 클라이언트의 TLSOptions.Version과 서버의 SSLOptions.Version은 허용되는 가장 낮은 버전입니다.

 

버전 협상 결과
tlsUndefined(기본값) TLS 1.3 또는 TLS 1.2
tls1_2 TLS 1.3 또는 TLS 1.2
tls1_3 TLS 1.3 전용
tls1_0, tls1_1 아무것도 협상하지 않습니다. 이 엔진은 TLS 1.2와 TLS 1.3만 지원하므로, 클라이언트는 연결할 때, 서버는 시작할 때 구성 오류가 발생합니다.

 

OpenSSL 핸들러는 이 속성을 다르게 해석합니다. iohOpenSSL에서는 tls1_2가 정확히 TLS 1.2를 뜻하지만, iohNativeTLS에서는 TLS 1.2 이상을 뜻합니다. 나머지는 암호 제품군 목록이 결정합니다. TLS 1.2만 사용하려면 Version을 tls1_2로 두고 NativeTLS_Options.CipherSuites에 TLS 1.2 제품군만 지정하십시오. TLS 1.3 제품군만 지정한 목록은 TLS 1.3 전용이 되고, 두 버전의 제품군을 함께 지정한 목록은 둘 다 허용합니다. Version을 tls1_3으로 두고 TLS 1.2 제품군 목록을 지정하면 협상할 버전이 남지 않아 연결이 시작될 때 오류가 발생합니다.

 


// TLS 1.3 and TLS 1.2, tls1_2 is the lowest version allowed
oClient.TLSOptions.IOHandler := iohNativeTLS;
oClient.TLSOptions.Version := tls1_2;

// TLS 1.3 only
oClient.TLSOptions.Version := tls1_3;

// TLS 1.2 only: name TLS 1.2 suites and no TLS 1.3 suite
oClient.TLSOptions.Version := tls1_2;
oClient.TLSOptions.NativeTLS_Options.CipherSuites :=
  'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256';

 

그룹과 암호 제품군

 

클라이언트의 TLSOptions.NativeTLS_Options와 서버의 SSLOptions.NativeTLS_Options가 두 가지 우선순위 목록을 담고 있습니다. 둘 다 OpenSSL 방식의 문자열로, 항목을 콜론으로 구분하며 가장 선호하는 것을 앞에 두고, 하나의 목록이 TLS 1.3과 TLS 1.2에 함께 쓰입니다. 한쪽을 비워 두면 엔진 기본값이 유지됩니다. 잘못된 목록은 연결이 시작될 때 오류를 발생시키므로 오타가 조용히 무시되지 않습니다.

 

속성 허용되는 이름 기본값
Groups X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024, X25519, secp256r1, secp384r1. 별칭 P-256, prime256v1, P-384도 받아들이며, 이름은 대소문자를 가리지 않고 비교합니다. 세 가지 하이브리드 그룹은 TLS 1.3 전용입니다. X25519MLKEM768:X25519:secp256r1:secp384r1
CipherSuites TLS 1.3: TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256. TLS 1.2: 아래 표의 제품군. IANA 이름과 ECDHE-RSA-AES128-GCM-SHA256과 같은 OpenSSL 방식 이름을 모두 받아들이며, 엔진은 IANA 이름으로 보고합니다. TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256, 그리고 아래의 TLS 1.2 기본값

 

목록의 첫 번째 그룹이 클라이언트가 키 셰어를 보내는 그룹이므로, X25519MLKEM768을 맨 앞에 두는 것이 첫 비행부터 포스트 양자가 되게 하는 방법입니다. 항목 앞에 물음표를 붙이면 이 빌드에 그 항목이 없을 때 건너뛰며, 그 밖에 알 수 없거나 사용할 수 없는 항목이 있으면 목록 전체가 실패합니다.

 


// only the hybrid post-quantum groups, and only AES-256-GCM
oClient.TLSOptions.NativeTLS_Options.Groups :=
  'X25519MLKEM768:SecP256r1MLKEM768:SecP384r1MLKEM1024';
oClient.TLSOptions.NativeTLS_Options.CipherSuites := 'TLS_AES_256_GCM_SHA384';

// tolerate a build where X25519 is not compiled in
oClient.TLSOptions.NativeTLS_Options.Groups := '?X25519MLKEM768:secp256r1';

 

TLS 1.2 암호 제품군

 

기본값으로는 순방향 비밀성을 갖춘 AEAD 제품군만 활성화되어 있습니다. AES-CBC 제품군과 RSA 키 교환은 더 나은 방식을 쓸 수 없는 피어를 위한 것이며, CipherSuites에 명시된 경우에만 제안되거나 허용됩니다.

 

제품군 활성화
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 기본값으로 이 순서대로 활성화됩니다.
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 CipherSuites에 명시된 경우에만 사용됩니다.
TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_256_CBC_SHA256 CipherSuites에 명시된 경우에만 사용됩니다. RSA 키 교환에는 순방향 비밀성이 없습니다.

 

TLS 1.2 연결의 ECDHE 제품군은 Groups 목록의 고전적인 그룹인 X25519, secp256r1, secp384r1 중 하나를 사용하므로, TLS 1.2 피어가 접속해야 한다면 목록에 이 중 하나는 남겨 두십시오. RSA 키 교환은 그룹을 전혀 사용하지 않습니다.

 


// TLS 1.3, and TLS 1.2 with AES-GCM plus AES-CBC for older clients
oServer.SSLOptions.IOHandler := iohNativeTLS;
oServer.SSLOptions.Version := tls1_2;
oServer.SSLOptions.NativeTLS_Options.CipherSuites :=
  'TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:' +
  'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:' +
  'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA';

 

SGC_CRYPTO_FIPS로 만든 빌드에서는 X25519 위에 만들어진 두 그룹, ChaCha20-Poly1305 제품군, RSA 키 교환이 컴파일에서 제외되며, 기본값이 SecP256r1MLKEM768:SecP384r1MLKEM1024:secp256r1:secp384r1과 TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256으로 바뀌고, TLS 1.2에는 ECDHE AES-GCM 제품군 네 가지가 남습니다. FIPS 모드를 참조하십시오.

 

TLS 1.2 동작

 

TLS 1.2는 최신 피어가 기대하는 보호 기능을 갖추고, 알려진 공격의 원인이 된 프로토콜 요소는 제외한 채 구현되어 있습니다.

 

 

세션 재개

 

엔진은 TLS 1.3(RFC 8446)과 TLS 1.2(RFC 5077)에서 상태 비저장 세션 티켓으로 세션을 재개합니다. 전체 핸드셰이크가 끝나면 서버는 오직 서버만 가지고 있는 키로 AES-256-GCM으로 봉인한 티켓을 보냅니다. 클라이언트는 이를 호스트, 포트, ALPN 목록을 키로 하는 세션 캐시에 보관하고, 다음 연결에서 이를 제시하며, 그러면 인증서 교환을 건너뜁니다. 서버가 열 수 없는 티켓이나 만료된 티켓은 새 티켓과 함께 전체 핸드셰이크로 이어집니다.

 

속성 기본값 의미
NativeTLS_Options.SessionTickets True 서버가 티켓을 발급하고 클라이언트가 이를 저장하고 제시합니다. False로 설정하면 재개가 꺼지며, 그러면 모든 핸드셰이크가 전체 핸드셰이크가 됩니다.
NativeTLS_Options.TicketLifetime 7200 서버 전용입니다. 티켓을 재개에 사용할 수 있는 시간(초)이며, 최대 604800입니다. 서버 티켓 키는 TicketLifetime초마다 회전하며, 이전 키는 한 주기 더 유효합니다.

 

이 옵션은 클라이언트에서는 TLSOptions.NativeTLS_Options이고 서버에서는 SSLOptions.NativeTLS_Options입니다. 세션 캐시는 각 클라이언트 구성 요소에 속하므로, 같은 TsgcWebSocketClient, TsgcHTTP1Client, TsgcTCPClient, TsgcLDAPClient의 재연결이 재개되며, TsgcWebSocketProxyServer의 업스트림 연결도 마찬가지입니다.

 

TLS 1.2 재개에는 RFC 7627의 확장 마스터 시크릿이 필요합니다. 이것이 없는 TLS 1.2 세션은 결코 재개되지 않고 티켓도 받지 않으며, 서버가 클라이언트 인증서를 요구할 때도 티켓이 발급되지 않습니다. TLS 1.3 티켓이 TLS 1.2 세션으로 열리는 일은 없으며, 그 반대도 없습니다.

 

연결이 재개되었는지는 아래에서 설명하는 핸들러인 TsgcIdSSLIOHandlerSocketNativeTLS의 Resumed 속성으로 보고됩니다.

 

협상 결과

 

핸드셰이크가 끝나면 sgcSSL_NativeTLS_Indy 유닛의 클라이언트 핸들러 TsgcIdSSLIOHandlerSocketNativeTLS가 그 결과를 보고합니다. VersionName은 TLSv1.2 또는 TLSv1.3이고, CipherSuiteName은 제품군의 IANA 이름이며, GroupName은 키 교환 그룹으로 RSA 키 교환에서는 비어 있고, PeerSignatureSchemeName은 서버가 사용한 서명 스킴입니다. 이 핸들러는 클라이언트가 OnSSLAfterCreateHandler에 전달하는 바로 그 핸들러입니다.

 


procedure TForm1.oClientSSLAfterCreateHandler(Sender: TObject;
  aType: TwsSSLHandler; aSSLHandler: TIdSSLIOHandlerSocketBase);
begin
  FHandler := aSSLHandler;
end;

procedure TForm1.oClientConnect(Connection: TsgcWSConnection);
var
  oHandler: TsgcIdSSLIOHandlerSocketNativeTLS;
begin
  if FHandler is TsgcIdSSLIOHandlerSocketNativeTLS then
  begin
    oHandler := TsgcIdSSLIOHandlerSocketNativeTLS(FHandler);
    Memo1.Lines.Add(oHandler.VersionName + ' ' + oHandler.CipherSuiteName +
      ' ' + oHandler.GroupName + ' ' + oHandler.PeerSignatureSchemeName);
  end;
end;

 

인증서와 검증

 

서버는 CertFile, KeyFile, Password를 읽습니다. CertFile과 KeyFile은 PEM일 수 있고, CertFile은 확장자로 인식되는 .pfx나 .p12 파일일 수도 있습니다. PKCS#12 파일은 인증서, 체인, 개인 키를 모두 담고 있으므로 KeyFile은 필요하지 않으며, Password로 엽니다. 이는 OpenSSL 없이 sgcCrypto_PKCS12의 순수 Pascal PKCS#12 리더가 읽습니다. VerifyCertificate를 True로 설정한 클라이언트는 아래에서 설명하는 신뢰할 수 있는 루트를 기준으로 서버 체인을 검사하고, 이름이 접속한 호스트와 일치하는지도 확인합니다. VerifyDepth는 그대로 전달되며, 16을 넘는 값은 16으로 제한됩니다.

 

신뢰할 수 있는 루트는 RootCertFile에 있는 인증서입니다. NativeTLS_Options.UseSystemRoots를 True로 설정하면 운영체제가 신뢰하는 인증 기관이 추가되며, 기본값은 False입니다. 두 가지는 함께 작동합니다. RootCertFile의 루트가 먼저 로드되고 시스템 루트가 같은 저장소에 추가되며, 체인이 이 중 어느 하나로 이어지면 수락됩니다. 시스템 루트를 찾지 못하면 아무것도 추가하지 않고 오류도 발생시키지 않으므로, 이 경우 검증은 RootCertFile에만 의존합니다. 이 속성은 클라이언트에서는 TLSOptions.NativeTLS_Options.UseSystemRoots이고 서버에서는 SSLOptions.NativeTLS_Options.UseSystemRoots이며, 서버에서는 이 루트가 클라이언트 인증서를 검증하는 데만 쓰입니다. 시스템 루트를 어디서 가져오는지는 플랫폼에 따라 다릅니다:

 

 

직접 인증서 파일 폴더를 로드하려면, sgcSSL_NativeTLS_Certificates 유닛의 TsgcTLS13TrustStore.AddDirectory(aDirectory, aRemovedDirectory)가 aDirectory의 모든 PEM 또는 DER 파일을 읽고, aRemovedDirectory에도 이름이 있는 파일은 건너뛰며, 추가한 인증서 개수를 반환합니다. 모든 플랫폼에서 동작합니다.

 

연결 호스트 대신 인증서가 담고 있어야 할 이름을 고정하려면 TLSOptions.OpenSSL_Options.X509Checks.HostName이나 IPAddress를 설정하고, X509Checks.Mode에 그에 맞는 플래그를 지정하십시오. 이 엔진은 OpenSSL이 아니지만 이 두 값을 읽으므로, 어떤 IOHandler를 선택하든 이 속성의 의미는 같습니다.

 

서버에서 SSLOptions.VerifyCertificate는 클라이언트에 인증서를 요청하고 이를 위에서 설명한 신뢰할 수 있는 루트를 기준으로 검증하며, SSLOptions.VerifyCertificate_Options.FailIfNoCertificate는 인증서를 보내지 않는 클라이언트를 거부합니다. VerifyClientOnce는 여기서 할 일이 없습니다. TLS 1.3에는 재협상이 없고 엔진은 TLS 1.2에서도 이를 거부하므로, 두 번 요청하는 일이 없습니다.

 

ALPN은 클라이언트의 ALPNProtocols와 서버의 ALPN 선택을 통해 평소대로 동작하므로, 네이티브 엔진 위의 HTTP/2도 OpenSSL에서와 똑같이 h2를 협상합니다. ALPN을 참조하십시오.

 

QUIC 모드

 

이 엔진은 QUIC와 HTTP/3(RFC 9001)의 TLS 1.3 핸드셰이크도 실행합니다. TsgcQUICClient에서는 SSLQUICOptions.IOHandler를, TsgcHTTP3Client에서는 TLSOptions.IOHandler를, TsgcQUICServer와 TsgcHTTP3Server에서는 QUICOptions.SSLOptions.IOHandler를, 또는 HTTP3Options.Enabled가 설정된 서버에서는 SSLOptions.IOHandler를 iohNativeTLS로 설정하십시오.

 

 

엔진 비교는 QUIC를 참조하십시오.

 

지원하지 않는 것

 

이 엔진은 의도적으로 범위가 좁습니다. 아래는 모두 실제 제약이며, 찾아야 할 설정이 아닙니다.

 

 

피어가 보내는 어떤 것도 예외를 발생시키지 않습니다. 프로토콜 오류는 알맞은 TLS 경고와 함께 연결을 종료하며, 그 이유는 평소대로 구성 요소의 오류 이벤트를 통해 보고됩니다.

 

AES-GCM과 AES-CBC 레코드 보호는 Delphi로 빌드한 Windows 빌드에서 CPU가 지원하면 AES-NI와 PCLMULQDQ 명령어를 사용합니다. 대칭 암호화의 sgcAES_HardwareAvailable을 참조하십시오. 라이브러리의 어떤 부분도 OpenSSL을 로드하지 않는 빌드는 OpenSSL 없이 실행하기를 참조하십시오.