ネイティブ TLS

サポート対象

 

TsgcWebSocketClient

TsgcTCPClient

TsgcHTTP1Client

TsgcHTTP2Client

TsgcWebSocketServer

TsgcWebSocketHTTPServer

TsgcHTTPServer

TsgcWebSocketProxyServer

TsgcWebSocketLoadBalancerServer

 

*sgcCrypto パックが必要です。このパックは Standard エディション以上で提供されます。

 

ネイティブ 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 プロバイダー上に構築されるため、同じ構成が 3 つのエンジンすべてで機能します。

 

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

 

グループと暗号スイート

 

2 つの優先順位リストは、クライアントでは TLSOptions.NativeTLS_Options、サーバーでは SSLOptions.NativeTLS_Options が保持します。どちらも OpenSSL 形式の文字列で、項目をコロンで区切り、優先度の高いものから並べます。1 つのリストが TLS 1.3 と TLS 1.2 の両方をまとめて扱います。エンジンのデフォルトを使用するには空のままにしてください。不正なリストは接続の開始時にエラーになるため、入力ミスが黙って無視されることはありません。

 

プロパティ 受け付ける名前 デフォルト
Groups X25519MLKEM768、SecP256r1MLKEM768、SecP384r1MLKEM1024、X25519、secp256r1、secp384r1。別名の P-256、prime256v1、P-384 も受け付けられ、名前は大文字小文字を区別せずに比較されます。3 つのハイブリッドグループは 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 のピアを接続させる必要がある場合は、そのうち少なくとも 1 つをリストに残してください。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 を土台とする 2 つのグループ、ChaCha20-Poly1305 スイート、および RSA 鍵交換がコンパイル対象から除外され、デフォルトは SecP256r1MLKEM768:SecP384r1MLKEM1024:secp256r1:secp384r1 と TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256 に変わり、TLS 1.2 では 4 つの 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 秒ごとにローテーションし、直前の鍵はさらに 1 期間だけ有効です。

これらのオプションは、クライアントでは 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 で開かれます。これは sgcCrypto_PKCS12 の純粋な Pascal 製 PKCS#12 リーダーによって読み取られ、OpenSSL は使用しません。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 ではありませんが、これら 2 つの値を読み取るため、どの IOHandler を選択してもプロパティの意味は同じです。

 

サーバーでは、SSLOptions.VerifyCertificate がクライアントに証明書を要求し、上記で説明した信頼ルートに対して検証します。SSLOptions.VerifyCertificate_Options.FailIfNoCertificate は証明書を送らないクライアントを拒否します。VerifyClientOnce はここでは何もしません。TLS 1.3 には再ネゴシエーションがなく、エンジンは TLS 1.2 でもそれを拒否するため、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 なしで実行するを参照してください。