サポート対象
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 は、最新のピアが期待する保護機能を備えて実装されており、既知の攻撃の原因となったプロトコルの部分は含まれていません。
拡張マスターシークレット。 RFC 7627 はクライアントが提示し、サーバーが受け入れます。対応していないピアもそのまま受け入れられます。
再ネゴシエーションはありません。 再ネゴシエーションの要求は no_renegotiation アラートで拒否されます。セッション再開はセッションチケットを使用し、拡張マスターシークレットを必要とします。詳しくは後述の「セッション再開」を参照してください。
SHA-1 署名はありません。 RFC 9155 に従い、SHA-1 署名は送信も受け入れもされません。signature_algorithms 拡張を含まない TLS 1.2 の ClientHello は、SHA-1 のみを意味することになるため拒否されます。
ダウングレード保護。 RFC 8446 セクション 4.1.3 のダウングレード保護は両側で機能します。TLS 1.3 も許可しているサーバーは、すべての TLS 1.2 応答に印を付け、TLS 1.3 を提示したクライアントは、その印が付いた TLS 1.2 応答を拒否します。
定数時間の CBC。 CBC レコードは定数時間で復号されるため Lucky Thirteen を防ぎ、RSA 鍵交換には RFC 5246 セクション 7.4.7.1 の対策が適用されます。
証明書の鍵用途。 RSA 鍵交換にはサーバー証明書に keyEncipherment の鍵用途が必要です。ECDHE スイートと TLS 1.3 には digitalSignature が必要です。
TLS 1.3 専用の機能。 KeyUpdate と鍵材料のエクスポートは TLS 1.3 セッションにのみ存在します。
セッション再開
このエンジンは、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 で、サーバー側ではルートはクライアント証明書の検証にのみ使用されます。システムルートの取得元はプラットフォームによって異なります。
Windows。 ROOT システムストア(信頼されたルート証明機関)で、crypt32.dll を通じて読み取られます。
macOS。 Security.framework のアンカー証明書。フォールバックとして /etc/ssl/cert.pem が使われます。
iOS。 システムルートは一覧化できません。ルートが読み込まれていない場合、エンジンがルートを信頼できないという理由だけで拒否するチェーンは、SSL ポリシーとホスト名を使って Security.framework によって評価されます。エンジンの鍵用途チェックとホスト名チェックはその後も実行されます。
Android。 /apex/com.android.conscrypt/cacerts(Android 14 以降)または /system/etc/security/cacerts の証明書ファイル(PEM または DER)です。ユーザーが無効化したルート(/data/misc/user/0/cacerts-removed に列挙)はスキップされ、アプリケーションがそれらのフォルダーを読み取れる場合は /data/misc/user/0/cacerts-added のユーザールートが追加されます。
Linux およびその他のプラットフォーム。 存在し、証明書を 1 つ以上含む最初のファイルです。/etc/ssl/certs/ca-certificates.crt、/etc/pki/tls/certs/ca-bundle.crt、/etc/ssl/ca-bundle.pem、/etc/pki/tls/cacert.pem、/etc/ssl/cert.pem、/etc/ssl/certs/ca-bundle.crt です。
証明書ファイルが入ったフォルダーを自分で読み込むには、ユニット 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 に設定します。
sgc QUIC エンジンは、OpenSSL が何をエクスポートしていても常に使用されます。サーバーでは QUICMode が qmCustom を報告します。
ハンドシェイクは QUIC トランスポートパラメーターを運び、ALPN を必要とし、TLS アラートを QUIC エラーコードとして報告します。サーバー証明書は PEM または PFX から読み取られ、ピアはホスト名を含めて RootCertFile またはシステムルートに対して検証されます。
セッションチケット、再開、0-RTT は OpenSSL の場合と同様に機能します。サーバーはハンドシェイク後にチケットを送信し、クライアントはそれを ALPN とサーバーのトランスポートパラメーターとともに保存し、0-RTT はチケットごとに 1 回だけ受け入れられます。QUIC のチケットが TCP 上の TLS に流用されることはありません。
パケット保護もネイティブであるため、QUIC と HTTP/3 は OpenSSL を一切読み込みません。定義は不要です。
エンジンの比較については QUIC を参照してください。
できないこと
このエンジンは意図的に機能を絞っています。以下はいずれも実際の制限であり、探すべき設定ではありません。
TLS 1.3 と TLS 1.2 のみ。 SSL、TLS 1.0、TLS 1.1 はありません。Version が tls1_0 または tls1_1 の場合、クライアントは接続時に、サーバーは起動時にエラーになります。TLS 1.3 と TLS 1.2 のどちらも扱えないピアは接続できません。
チケットのない PSK はありません。 再開はエンジン自身が発行するセッションチケットでのみ機能します。前述の「セッション再開」を参照してください。外部の事前共有鍵(PSK)はサポートされません。
TCP 上では 0-RTT はありません。 TCP 上の TLS 接続では、Early data の送信も受け入れも行われません。ハンドシェイク後のクライアント認証も実装されていません。
QUIC は専用モードで。 QUIC と HTTP/3 は、前述の QUIC モードを使用し、それぞれのコンポーネントで設定します。
既定ではシステムルートを使用しません。 NativeTLS_Options.UseSystemRoots が True でない限り、信頼ルートは RootCertFile だけから取得されるため、証明書を検証するクライアントはルートファイルを同梱するか、その場所を指定する必要があります。これは、動作している OpenSSL や SChannel の構成から移行したときに最も気付きやすい、唯一の動作上の違いです。
OnSSLVerifyPeer は発生しません。 このイベントは OpenSSL ハンドラーに属します。ネイティブエンジンには独自の検証フックがあり、深さごとに 1 証明書ずつではなく、ピアのチェーン全体とエンジンの判定を一度に受け取ります。そのため、信頼の判断を上書きするアプリケーションは、コンポーネントのイベントではなくそれらを使用する必要があります。
ピアが送信する内容によって例外が発生することはありません。プロトコルの失敗は適切な TLS アラートで接続を終了し、その理由は通常どおりコンポーネントのエラーイベントを通じて報告されます。
AES-GCM と AES-CBC のレコード保護は、Delphi で作成した Windows ビルドにおいて CPU が対応している場合、AES-NI と PCLMULQDQ 命令を使用します。sgcAES_HardwareAvailable については対称暗号を参照してください。ライブラリのどの部分も OpenSSL を読み込まないビルドについては、OpenSSL なしで実行するを参照してください。