前回からの続き。
read_state_machine/write_state_machineを見ます。
read_state_machine
ネゴシエーションパケットの読み込み側の処理です。
この処理は以下のフローから成ります。
- ヘッダの読み込み
- transition
- ボディの読み込み
- process_message
- post_process_message
post_process_message後に書き込みフェーズに移行する場合はSUB_STATE_FINISHEDを返却します。
transition/process_message/post_process_messageの処理の実体は、クライアント側であればssl/statem/statem_clnt.cに、サーバ側であればssl/statem/statem_srvr.cにあります。
ヘッダの読み込み
まだレコードヘッダを読み込んでいない場合、read_stateはREAD_STATE_HEADERであり、tls_get_message_header関数でレコードヘッダとハンドシェイクタイプ、長さ、バージョンまでのデータをソケットから読み出します。
transition
現在の状態と、読み込んだレコードのタイプから、SSL/TLSネゴシエーション状態の遷移を行います。
ネゴシエーション状態は、「ClientHello読み込み」や「ServerHello送信」のことであり、include/openssl/ssl.hのOSSL_HANDSHAKE_STATEのenum型です。
サーバ側のServerHelloDone送信状態(TLS_ST_SW_SRVR_DONE)で、read側のtransitionを行う場合、読み込んだパケットのタイプがClientKeyExchangeであれば、状態をサーバ側でKeyExchangeを受信した状態(TLS_ST_SR_KEY_EXCH)に遷移することになります。
ボディの読み込み
ヘッダの読み込みが完了した場合、取得したデータの長さ分をtls_get_message_body関数で読み込みます。
ソケットがブロッキングで動作している場合、レコードの読み込みが完了するまで、ヘッダの読み込み処理やボディの読み込み処理から戻りません。
process_message
ネゴシエーションデータの読み込みを行います。
post_process_message
ネゴシエーションデータの読み込み後の処理を行います。この処理は不要な場合は行いません。
OpenSSL-1.1.0hのサーバ側では、ClientHello受信時と、ClientKeyExchange受信時しか処理を行いません。
write_state_machine
ネゴシエーションデータの書き込み(送信)側の処理です。
この処理は以下のフローから成ります。
- transition
- pre_work
- 送信処理
- post_work
transition
現在の状態から、SSL/TLSネゴシエーション状態の遷移を行います。
ネゴシエーション状態は、読み込み側と共通のOSSL_HANDSHAKE_STATEのenum型です。
サーバ側のServerHello送信状態(TLS_ST_SW_SRVR_HELLO)の場合、ネゴシエーションの状況によって、ChangeCipherSpecを呼び出すか、サーバ証明書の送信に入るかなど、分岐します。
pre_work
送信前処理です。pre_workに登録した関数自体では、やることはあまりありません。
pre_work後にconstruct_messageで取得できる関数を利用して、ネゴシエーションパケットを構築します。
送信処理
statem_do_write関数により、レコードレイヤを構築して、ソケットに送信依頼を行います。
post_work
送信後処理です。
連続するネゴシエーションパケットがない場合は、ソケットをflushしてデータを送り出します。
state_machine後の動作
ブロッキングソケットの場合、ネゴシエーションが完了するまでループから抜けません。そのため、SSL_accept関数から戻った場合、ネゴシエーションが完了したか、失敗したかのいずれかになります。
ノンブロッキングソケットで、ソケットの読み込みデータがまだない場合、tls_get_message_header関数やtls_get_message_body関数が0を返却します。その場合はread_state_machine関数でSUB_STATE_ERRORを返却し、state_machine関数のwhileループから抜け、SSL_accept関数から戻ります。
その場合、SSL_get_error関数でSSL_ERROR_WANT_READ/SSL_ERROR_WANT_WRITEを取得できますので、再度SSL_accept関数やSSL_do_handshake関数を呼び出して、ネゴシエーションを再開させます。
次回
SSL_read関数を見ていきます。