モジュール java.base
パッケージ javax.net.ssl

クラスSSLEngine

java.lang.Object
javax.net.ssl.SSLEngine

public abstract class SSLEngine extends Object
Secure Sockets Layer (SSL)やIETF RFC 2246の「Transport Layer Security」(TLS)などのプロトコルを使用してセキュア通信を有効にするが、トランスポートに依存しないクラス。

セキュア通信モードでは、次のセキュリティ保護が実施されます。

  • 整合性の保護 SSL/TLS/DTLSは、アクティブなワイヤタッパによるメッセージの変更から保護します。
  • 認証 ほとんどのモードでは、SSL/TLS/DTLSはピア認証を提供します。 通常は、サーバー認証が行われる。サーバーからの要求に応じて、クライアント認証も行われる。
  • 機密性(プライバシの保護) ほとんどのモードでは、SSL/TLS/DTLSはクライアントとサーバーの間で送信されるデータを暗号化します。 この結果、受動的な盗聴によって、金融情報や個人情報などの機密性の高いデータが盗聴されることがない。
これらのセキュリティ保護は、「暗号化方式群」を使用して指定します。暗号化方式群は、指定されたSSL接続で使用される暗号化アルゴリズムの組合せです。 ネゴシエーション処理中は、2つの端点に、両方の環境で使用可能である暗号化方式群が存在する必要があります。 共通の暗号化方式群がない場合は、SSL接続を確立できず、データを交換できません。

使用される暗号化方式群は、「ハンドシェーク」と呼ばれるネゴシエーション・プロセスによって確立されます。 ハンドシェークでは、セッションの作成またはセッションへの参加が行われます。このセッションは、時間の経過とともにさまざまな接続を保護します。 ハンドシェークが完了すると、getSession()メソッドを使用してセッション属性にアクセスできます。

SSLSocketクラスは、ほぼ同じセキュリティ機能を提供しますが、すべてのインバウンドおよびアウトバウンド・データは、基礎となるSocketを使用して自動的に転送されます。このデータは、設計によってブロック・モデルを使用します。 この処理は多くのアプリケーションに対して適切ですが、このモデルは大規模サーバーに必要な拡張性をもたらしません。

SSLEngineの主な特長として、転送メカニズムに依存することなくインバウンド/アウトバウンドのバイト・ストリームに作用する点があげられます。 SSLEngineユーザーは、ピアに対する入出力転送の信頼性を確保する必要があります。 SSL/TLS/DTLS抽象化をI/Oトランスポート・メカニズムから分離することで、SSLEngineは、non-blocking I/O (polling)selectable non-blocking I/OSocket、従来のInput/OutputStreams、ローカルByteBuffersまたはバイト配列、「将来の非同期I/Oモデル」などの様々なI/Oタイプに使用できます。

高レベルでは、SSLEngineは次のように表されます。

                   app data

                |           ^
                |     |     |
                v     |     |
           +----+-----|-----+----+
           |          |          |
           |       SSL|Engine    |
   wrap()  |          |          |  unwrap()
           | OUTBOUND | INBOUND  |
           |          |          |
           +----+-----|-----+----+
                |     |     ^
                |     |     |
                v           |

                   net data
 
アプリケーション・データ(別名「プレーン・テキスト」または「クリアテキスト」)は、アプリケーションによって生成または使用されるデータです。 アプリケーション・データと対になるものとして、ネットワーク・データがあります。ネットワーク・データは、ハンドシェークまたは暗号文(暗号化された)、またはその両方のいずれかで構成され、入出力メカニズムを介して転送されるデータです。 インバウンド・データはピアから受信されるデータ、アウトバウンド・データはピアへ送信されるデータです。

SSLEngineのコンテキストでは、セキュア接続の確立および制御目的で交換されるデータを「ハンドシェーク・データ」と総称します。 ハンドシェイク・データには、SSL/TLS/DTLSメッセージ"alert"、"change_cipher_spec,"、および"handshake."が含まれます)

SSLEngineは、次の5つの段階をたどります。

  1. 作成 - SSLEngineの作成と初期化は完了しましたが、まだ使用されてはいません。 この段階では、アプリケーションにより、SSLEngine固有のあらゆる設定(暗号化方式群の有効化、SSLEngineがクライアント・モードとサーバー・モードのどちらでハンドシェークを行うかなど)を行うことができます。 ハンドシェークが始まると、次のハンドシェークからクライアント/サーバー・モードの設定を除く(下記参照)新しい設定が使用されます。
  2. 初期ハンドシェーク - SSLSessionが確立されるまでの間、2つのピアが通信パラメータを交換する手続きです。 この段階では、アプリケーション・データは送信できません。
  3. アプリケーション・データ - 通信パラメータが確立され、ハンドシェークが完了すると、SSLEngineからアプリケーション・データが送信されます。 アウトバウンド・アプリケーション・メッセージは暗号化され、データの整合性が確保されます。インバウンド・メッセージでは、この逆の手続きが行われます。
  4. 再ハンドシェーク -「アプリケーション・データ」段階の間はいつでも、どちら側のピアからでも、必要に応じてセッションの再ネゴシエーションを要求できます。 アプリケーション・データに新しいハンドシェーク・データを混合できます。 再ハンドシェーク・フェーズを開始する前に、アプリケーションは、有効な暗号スイートのリストやクライアント認証を使用するかどうかなどのSSL/TLS/DTLS通信パラメータをリセットすることができますが、クライアント/サーバー・モード間で変更することはできません。 前回と同様に、ハンドシェークが始まってから次のハンドシェークまで、新しいSSLEngine構成設定は使用されません。
  5. クローズ - 接続が不要になった場合、クライアントとサーバー・アプリケーションは、それぞれの接続の両側を閉じる必要があります。 SSLEngineオブジェクトの場合、アプリケーションはcloseOutbound()を呼び出し、残りのメッセージをピアに送信する必要があります。 同様に、アプリケーションはcloseInbound()を呼び出す前にピアから残りのメッセージを受信する必要があります。 その後、SSLEngineの両側が閉じられた後に、配下の転送メカニズムを閉じることができます。 適切な方法で(たとえば、ピアの書込み閉包通知を受信する前にcloseInbound()がコールされます。)に接続がクローズされない場合、エラーが発生したことを示す例外が発生します。 いったん閉じられたエンジンを再利用することはできません。新しいSSLEngineを作成する必要があります。
SSLEngineを作成するには、初期化されたSSLContextからSSLContext.createSSLEngine()を呼び出します。 wrap()unwrap()、またはbeginHandshake()を最初に呼び出す前に、任意の構成パラメータを構成してください。 これらのメソッドはすべて、初期ハンドシェークをトリガーします。

エンジン内でデータを移動するには、アウトバウンド・データの場合はwrap()、インバウンド・データの場合はunwrap()をそれぞれ呼び出します。 SSLEngineの状態によっては、wrap()呼出しによってソース・バッファからアプリケーション・データが消費され、宛先バッファにネットワーク・データが生成される場合もあります。 アウトバウンド・データには、アプリケーション・データまたはハンドシェーク・データ、あるいはその両方が含まれます。 unwrap()を呼び出すと、ソース・バッファがチェックされ、データがハンドシェーク情報であればハンドシェークが実施されます。アプリケーションであれば、アプリケーション・データが宛先バッファに格納されます。 基礎となるSSL/TLS/DTLSアルゴリズムの状態によって、データがいつ消費および生成されるかが決まります。

wrap()unwrap()を呼び出すと、オペレーションの状態と、処理を続行する場合のエンジンとのやりとりの内容(オプション)を示すSSLEngineResultが返されます。

SSLEngineは、完全なSSL/TLS/DTLSパケットのみを生成/消費し、wrap()/unwrap()へのコール間でアプリケーション・データを内部的に格納しません。 したがって、生成されるレコードのうち最大のものを格納できるように、入出力ByteBufferのサイズを決定する必要があります。 適切なバッファ・サイズを決定するには、SSLSession.getPacketBufferSize()SSLSession.getApplicationBufferSize()の呼び出しを使用する必要があります。 アウトバウンド・アプリケーション・データのバッファ・サイズは、通常、考慮する必要はありません。 データの使用および生成に適したバッファ条件でない場合、アプリケーションはSSLEngineResultによって問題を特定し、修正したあと、再度呼出しを試行しなければいけません。

たとえば、有効な宛先バッファの容量が不十分であるとエンジンが判定した場合、unwrap()SSLEngineResult.Status.BUFFER_OVERFLOWの結果を返します。 必要に応じて、アプリケーションでSSLSession.getApplicationBufferSize()を呼び出し、その値と宛先バッファ内の有効な容量を比較して、バッファを大きくするようにしてください。 同様に、unwrap()SSLEngineResult.Status.BUFFER_UNDERFLOWを返そうとした場合は、アプリケーションでSSLSession.getPacketBufferSize()を呼び出して、レコードを保持するのに十分な容量をソース・バッファに確保し(また、必要に応じて拡張し)、より多くの着信データを取得するようにしてください。


   SSLEngineResult r = engine.unwrap(src, dst);
   switch (r.getStatus()) {
   case BUFFER_OVERFLOW:
       // Could attempt to drain the dst buffer of any already obtained
       // data, but we'll just increase it to the size needed.
       int appSize = engine.getSession().getApplicationBufferSize();
       ByteBuffer b = ByteBuffer.allocate(appSize + dst.position());
       dst.flip();
       b.put(dst);
       dst = b;
       // retry the operation.
       break;
   case BUFFER_UNDERFLOW:
       int netSize = engine.getSession().getPacketBufferSize();
       // Resize buffer if needed.
       if (netSize > src.capacity()) {
           ByteBuffer b = ByteBuffer.allocate(netSize);
           src.flip();
           b.put(src);
           src = b;
       }
       // Obtain more inbound network data for src,
       // then retry the operation.
       break;
   // other cases: CLOSED, OK.
   }
 

SSLSocketとは異なり、SSLEngineのメソッドはすべて非ブロックになります。 SSLEngine実装は、完了までに長い時間がかかったり、ブロックしてしまう可能性のあるタスクの結果を必要とする可能性があります。 たとえば、TrustManagerでは、リモート証明書検証サービスへの接続が必要になる場合があります。また、KeyManagerは、クライアント認証の一環として使用する証明書を決定するようにユーザーに要求する必要がある場合があります。 さらに、暗号化署名を作成し、これらを検証する場合、処理時間がかなり長くなり、処理がブロックされたように見えることがあります。

SSLEngineは、ブロックされる可能性があるあらゆるオペレーションに対して、Runnable委譲タスクを生成します。 SSLEngineResultにより、委譲タスクの結果の必要性が示された場合、アプリケーションはgetDelegatedTask()を呼び出して未処理の委譲タスクを取得し、そのrun()メソッドを呼び出す必要があります。呼出しに使用されるスレッドは、計算方法によって異なります。 アプリケーションは、すべての委譲タスクを取得すると、元の操作を再試行します。

通信セッションの最後に、アプリケーションはSSL/TLS/DTLSリンクを適切にクローズする必要があります。 SSL/TLS/DTLSプロトコルにはクローズ・ハンドシェイク・メッセージがあり、これらのメッセージは、SSLEngineを解放して基礎となるトランスポート・メカニズムを閉じる前にピアに通信する必要があります。 通信セッションの終了は、SSLException、ハンドシェーク終了メッセージの着信、または任意の終了メソッドによって開始されます。 どの場合でも、エンジンからハンドシェーク終了メッセージが生成され、SSLEngineResultの状態がCLOSEDになるか、isOutboundDone()の戻り値がtrueになるまで、wrap()が繰返し呼び出されます。 wrap()メソッドによって取得されたデータはすべてピアに送信されます。

アプリケーションから送信されるデータがもうないことをエンジンに通知するには、closeOutbound()を使用します。

ピアは、固有のハンドシェーク終了メッセージを送信することで、終了の意図を通知します。 このメッセージがローカルのSSLEngineunwrap()呼出しによって受信および処理されると、アプリケーションは、unwrap()を呼び出し、ステータスがCLOSEDのSSLEngineResultを検索します。条件に合うものが見つかるか、isInboundDone()の戻り値がtrueであれば、終了が確認されます。 なんらかの理由でピアが適切なSSL/TLS/DTLSクローズ・メッセージを送信せずに通信リンクを閉じる場合、アプリケーションはストリームの終わりを検出し、処理するインバウンド・メッセージはこれ以上ないことをcloseInbound()経由でエンジンに通知できます。 アプリケーションによっては、ピアからのシャットダウン・メッセージを順序どおりに受け取るように要求する設定になっている場合があります。このような場合、アプリケーションはストリームの終了位置の条件ではなく、ハンドシェーク・メッセージによって終了をチェックできます。

暗号化方式群を使用するときは、2つのグループについて理解する必要があります。

  • サポートされる暗号化方式群: SSL実装でサポートされるすべての暗号化方式群。 このリストは、getSupportedCipherSuites()を使用して報告される。
  • 有効になっている暗号化方式群。サポートされる暗号化方式群の完全なセットより少ないことがある。 このグループは、setEnabledCipherSuites(String[])メソッドを使用して設定し、getEnabledCipherSuites()メソッドを使用して照会する。 新しいエンジンでは、最小限の推奨構成を表すデフォルトの暗号化方式群が使用可能になっている。
デフォルトの実装で使用可能にする暗号化方式群では、サーバーを認証し、機密性が保証されなければいけません。 サーバー認証が行われず機密性が保証されない暗号化方式群を選択する場合は、サーバー認証が行われず非公開性が保証されない(暗号化されない)通信が使用されることに2つの終端が明示的に同意する必要があります。

各SSL/TLS/DTLS接続には1つのクライアントと1つのサーバーが必要であるため、各エンドポイントはどのロールを引き受けるかを決定する必要があります。 この選択内容によって、どちら側からハンドシェーク処理を開始するか、また、お互いにどのタイプのメッセージを送信するかが決まります。 setUseClientMode(boolean)メソッドはモードを構成します。 新しいSSLEngineのデフォルト・モードはプロバイダ固有であることに注意してください。 アプリケーションでは、SSLEngineの他のメソッドを呼び出す前に、モードを明示的に設定する必要があります。 いったん初期ハンドシェークが開始されてからは、再ネゴシエーションを実行する場合でも、SSLEngineのモードをクライアントからサーバー、サーバーからクライアントに切り替えることはできません。

このクラスのメソッドによって返されるApplicationProtocol String値は、ピアによって送信されるネットワーク・バイト表現内にあります。 バイトは、比較のために直接比較するか、UnicodeのString形式に変換できます。

     String networkString = sslEngine.getHandshakeApplicationProtocol();
     byte[] bytes = networkString.getBytes(StandardCharsets.ISO_8859_1);

     //
     // Match using bytes:
     //
     //   "http/1.1"                       (7-bit ASCII values same in UTF-8)
     //   MEETEI MAYEK LETTERS "HUK UN I"  (Unicode 0xabcd->0xabcf)
     //
     String HTTP1_1 = "http/1.1";
     byte[] HTTP1_1_BYTES = HTTP1_1.getBytes(StandardCharsets.UTF_8);

     byte[] HUK_UN_I_BYTES = new byte[] {
         (byte) 0xab, (byte) 0xcd,
         (byte) 0xab, (byte) 0xce,
         (byte) 0xab, (byte) 0xcf};

     if ((Arrays.compare(bytes, HTTP1_1_BYTES) == 0 )
             || Arrays.compare(bytes, HUK_UN_I_BYTES) == 0) {
        ...
     }

     //
     // Alternatively match using string.equals() if we know the ALPN value
     // was encoded from a String using a certain character set,
     // for example UTF-8.  The ALPN value must first be properly
     // decoded to a Unicode String before use.
     //
     String unicodeString = new String(bytes, StandardCharsets.UTF_8);
     if (unicodeString.equals(HTTP1_1)
             || unicodeString.equals("\uabcd\uabce\uabcf")) {
         ...
     }
 

アプリケーションにより、委譲タスクを別スレッドで処理するように選択できます。 SSLEngineが作成されると、現在のAccessControlContextが保存されます。 その後、すべての委譲タスクはこのコンテキストで処理されます。つまり、アクセス制御の意思決定はすべて、エンジンの作成時のに前方参照されたコンテキストを使用してなされます。


並行処理のノート: 次の2点に注意してください。
  1. wrap()メソッドとunwrap()メソッドは、並行実行が可能です。
  2. SSL/TLS/DTLSプロトコルは、順序付けされたパケットを使用します。 アプリケーションは、生成されたパケットが正しい順番で配信する必要があります。 パケットの到着順序が正しくないと、予期しない結果または致命的な結果を招くことがあります。

    たとえば、

                  synchronized (outboundLock) {
                      sslEngine.wrap(src, dst);
                      outboundQueue.put(dst);
                  }
          
    最終的なパケットの順序を保証することができないので、結果的に、2つのスレッドが同じメソッド(wrap()またはunwrap())を並行して呼び出すことはできません。

導入されたバージョン:
1.5
関連項目: