class TTTLS13::Server
rubocop: disable Metrics/ClassLength
Public Class Methods
@param socket [Socket] @param settings [Hash]
TTTLS13::Connection::new
# File lib/tttls1.3/server.rb, line 65 def initialize(socket, **settings) super(socket) @endpoint = :server @settings = DEFAULT_SERVER_SETTINGS.merge(settings) logger.level = @settings[:loglevel] raise Error::ConfigError unless valid_settings? return if @settings[:crt_file].nil? crt_str = File.read(@settings[:crt_file]) @crt = OpenSSL::X509::Certificate.new(crt_str) # TODO: spki rsassaPss klass = @crt.public_key.class @key = klass.new(File.read(@settings[:key_file])) raise Error::ConfigError unless @crt.check_private_key(@key) @chain = @settings[:chain_files]&.map do |f| OpenSSL::X509::Certificate.new(File.read(f)) end @chain ||= [] ([@crt] + @chain).each_cons(2) do |cert, sign| raise Error::ConfigError unless cert.verify(sign.public_key) end end
Public Instance Methods
NOTE:
START <-----+ Recv ClientHello | | Send HelloRetryRequest v | RECVD_CH ----+ | Select parameters v NEGOTIATED | Send ServerHello | K_send = handshake | Send EncryptedExtensions | [Send CertificateRequest]
Can send | [Send Certificate + CertificateVerify] app data | Send Finished after –> | K_send = application here --------
——–+
No 0-RTT | | 0-RTT | | K_recv = handshake | | K_recv = early data
- Skip decrypt errors
-
| +——> WAIT_EOED -+ | | Recv | | Recv EndOfEarlyData | | early data | | K_recv = handshake |
------------
| | | +> WAIT_FLIGHT2 <——–+|
--------
——–+
No auth | | Client auth | | | v | WAIT_CERT | Recv | | Recv Certificate | empty | v | Certificate | WAIT_CV | | | Recv | v | CertificateVerify +-> WAIT_FINISHED <---+ | Recv Finished | K_recv = application v CONNECTED
tools.ietf.org/html/rfc8446#appendix-A.2
rubocop: disable Metrics/AbcSize rubocop: disable Metrics/BlockLength rubocop: disable Metrics/CyclomaticComplexity rubocop: disable Metrics/MethodLength rubocop: disable Metrics/PerceivedComplexity
# File lib/tttls1.3/server.rb, line 139 def accept transcript = Transcript.new key_schedule = nil # TTTLS13::KeySchedule priv_key = nil # OpenSSL::PKey::$Object hs_wcipher = nil # TTTLS13::Cryptograph::$Object hs_rcipher = nil # TTTLS13::Cryptograph::$Object @state = ServerState::START loop do case @state when ServerState::START logger.debug('ServerState::START') receivable_ccs = transcript.include?(CH1) ch = transcript[CH] = recv_client_hello(receivable_ccs) # support only TLS 1.3 terminate(:protocol_version) unless ch.negotiated_tls_1_3? # validate parameters terminate(:illegal_parameter) unless ch.appearable_extensions? terminamte(:illegal_parameter) \ unless ch.legacy_compression_methods == ["\x00"] terminate(:illegal_parameter) unless ch.valid_key_share? terminate(:unrecognized_name) unless recognized_server_name?(ch, @crt) # alpn ch_alpn = ch.extensions[ Message::ExtensionType::APPLICATION_LAYER_PROTOCOL_NEGOTIATION ] if !@settings[:alpn].nil? && !@settings[:alpn].empty? && !ch_alpn.nil? @alpn = ch_alpn.protocol_name_list .find { |p| @settings[:alpn].include?(p) } terminate(:no_application_protocol) if @alpn.nil? end # record_size_limit ch_rsl = ch.extensions[Message::ExtensionType::RECORD_SIZE_LIMIT] @send_record_size = ch_rsl.record_size_limit unless ch_rsl.nil? @state = ServerState::RECVD_CH when ServerState::RECVD_CH logger.debug('ServerState::RECVD_CH') # select parameters ch = transcript[CH] @cipher_suite = select_cipher_suite(ch) @named_group = select_named_group(ch) @signature_scheme = select_signature_scheme(ch, @crt) terminate(:handshake_failure) \ if @cipher_suite.nil? || @signature_scheme.nil? # send HRR if @named_group.nil? ch1 = transcript[CH1] = transcript.delete(CH) transcript[HRR] = send_hello_retry_request(ch1, @cipher_suite) @state = ServerState::START next end @state = ServerState::NEGOTIATED when ServerState::NEGOTIATED logger.debug('ServerState::NEGOTIATED') ch = transcript[CH] extensions, priv_key = gen_sh_extensions(@named_group) transcript[SH] = send_server_hello(extensions, @cipher_suite, ch.legacy_session_id) send_ccs if @settings[:compatibility_mode] # generate shared secret ke = ch.extensions[Message::ExtensionType::KEY_SHARE] &.key_share_entry &.find { |e| e.group == @named_group } &.key_exchange shared_secret = gen_shared_secret(ke, priv_key, @named_group) key_schedule = KeySchedule.new( psk: @psk, shared_secret: shared_secret, cipher_suite: @cipher_suite, transcript: transcript ) @alert_wcipher = hs_wcipher = gen_cipher( @cipher_suite, key_schedule.server_handshake_write_key, key_schedule.server_handshake_write_iv ) hs_rcipher = gen_cipher( @cipher_suite, key_schedule.client_handshake_write_key, key_schedule.client_handshake_write_iv ) @state = ServerState::WAIT_FLIGHT2 when ServerState::WAIT_EOED logger.debug('ServerState::WAIT_EOED') when ServerState::WAIT_FLIGHT2 logger.debug('ServerState::WAIT_FLIGHT2') ch = transcript[CH] rsl = @send_record_size \ if ch.extensions.include?(Message::ExtensionType::RECORD_SIZE_LIMIT) ee = transcript[EE] = gen_encrypted_extensions(ch, @alpn, rsl) # TODO: [Send CertificateRequest] # status_request ocsp_response = fetch_ocsp_response \ if ch.extensions.include?(Message::ExtensionType::STATUS_REQUEST) ct = transcript[CT] = gen_certificate(@crt, @chain, ocsp_response) digest = CipherSuite.digest(@cipher_suite) cv = transcript[CV] = gen_certificate_verify( @key, @signature_scheme, transcript.hash(digest, CT) ) finished_key = key_schedule.server_finished_key signature = sign_finished( digest: digest, finished_key: finished_key, hash: transcript.hash(digest, CV) ) sf = transcript[SF] = Message::Finished.new(signature) send_server_parameters([ee, ct, cv, sf], hs_wcipher) @state = ServerState::WAIT_FINISHED when ServerState::WAIT_CERT logger.debug('ServerState::WAIT_CERT') when ServerState::WAIT_CV logger.debug('ServerState::WAIT_CV') when ServerState::WAIT_FINISHED logger.debug('ServerState::WAIT_FINISHED') cf = transcript[CF] = recv_finished(hs_rcipher) digest = CipherSuite.digest(@cipher_suite) verified = verified_finished?( finished: cf, digest: digest, finished_key: key_schedule.client_finished_key, hash: transcript.hash(digest, EOED) ) terminate(:decrypt_error) unless verified @alert_wcipher = @ap_wcipher = gen_cipher( @cipher_suite, key_schedule.server_application_write_key, key_schedule.server_application_write_iv ) @ap_rcipher = gen_cipher( @cipher_suite, key_schedule.client_application_write_key, key_schedule.client_application_write_iv ) @exporter_master_secret = key_schedule.exporter_master_secret @state = ServerState::CONNECTED when ServerState::CONNECTED logger.debug('ServerState::CONNECTED') break end end end
Private Instance Methods
@return [OpenSSL::OCSP::Response, nil]
# File lib/tttls1.3/server.rb, line 559 def fetch_ocsp_response @settings[:process_ocsp_response]&.call end
@param crt [OpenSSL::X509::Certificate] @param chain [Array of OpenSSL::X509::Certificate] @param ocsp_response [OpenSSL::OCSP::Response]
@return [TTTLS13::Message::Certificate, nil]
# File lib/tttls1.3/server.rb, line 415 def gen_certificate(crt, chain = [], ocsp_response = nil) exs = Message::Extensions.new # status_request exs << Message::Extension::OCSPResponse.new(ocsp_response) \ unless ocsp_response.nil? ces = [Message::CertificateEntry.new(crt, exs)] \ + (chain || []).map { |c| Message::CertificateEntry.new(c) } Message::Certificate.new(certificate_list: ces) end
@param key [OpenSSL::PKey::PKey] @param signature_scheme [TTTLS13::SignatureScheme] @param hash [String]
@return [TTTLS13::Message::CertificateVerify, nil]
# File lib/tttls1.3/server.rb, line 430 def gen_certificate_verify(key, signature_scheme, hash) signature = sign_certificate_verify( key: key, signature_scheme: signature_scheme, hash: hash ) Message::CertificateVerify.new(signature_scheme: signature_scheme, signature: signature) end
@param ch [TTTLS13::Message::ClientHello] @param alpn [String] @param record_size_limit [Integer, nil]
@return [TTTLS13::Message::Extensions]
# File lib/tttls1.3/server.rb, line 476 def gen_ee_extensions(ch, alpn, record_size_limit) exs = Message::Extensions.new # server_name exs << Message::Extension::ServerName.new('') \ if ch.extensions.include?(Message::ExtensionType::SERVER_NAME) # supported_groups exs << Message::Extension::SupportedGroups.new( @settings[:supported_groups] ) # alpn exs << Message::Extension::Alpn.new([alpn]) unless alpn.nil? # record_size_limit exs << Message::Extension::RecordSizeLimit.new(record_size_limit) \ unless record_size_limit.nil? exs end
@param ch [TTTLS13::Message::ClientHello] @param alpn [String, nil] @param record_size_limit [Integer, nil]
@return [TTTLS13::Message::EncryptedExtensions]
# File lib/tttls1.3/server.rb, line 404 def gen_encrypted_extensions(ch, alpn = nil, record_size_limit = nil) Message::EncryptedExtensions.new( gen_ee_extensions(ch, alpn, record_size_limit) ) end
@param named_group [TTTLS13::NamedGroup]
@return [TTTLS13::Message::Extensions] @return [OpenSSL::PKey::EC.$Object]
# File lib/tttls1.3/server.rb, line 456 def gen_sh_extensions(named_group) exs = Message::Extensions.new # supported_versions: only TLS 1.3 exs << Message::Extension::SupportedVersions.new( msg_type: Message::HandshakeType::SERVER_HELLO ) # key_share key_share, priv_key \ = Message::Extension::KeyShare.gen_sh_key_share(named_group) exs << key_share [exs, priv_key] end
@param ch [TTTLS13::Message::ClientHello] @param crt [OpenSSL::X509::Certificate]
@return [Boolean]
# File lib/tttls1.3/server.rb, line 550 def recognized_server_name?(ch, crt) server_name = ch.extensions[Message::ExtensionType::SERVER_NAME] &.server_name return true if server_name.nil? matching_san?(crt, server_name) end
@param receivable_ccs [Boolean]
@raise [TTTLS13::Error::ErrorAlerts]
@return [TTTLS13::Message::ClientHello]
# File lib/tttls1.3/server.rb, line 328 def recv_client_hello(receivable_ccs) ch = recv_message(receivable_ccs: receivable_ccs, cipher: Cryptograph::Passer.new) terminate(:unexpected_message) unless ch.is_a?(Message::ClientHello) ch end
@param cipher [TTTLS13::Cryptograph::Aead]
@raise [TTTLS13::Error::ErrorAlerts]
@return [TTTLS13::Message::Finished]
# File lib/tttls1.3/server.rb, line 445 def recv_finished(cipher) cf = recv_message(receivable_ccs: true, cipher: cipher) terminate(:unexpected_message) unless cf.is_a?(Message::Finished) cf end
@param ch [TTTLS13::Message::ClientHello]
@return [TTTLS13::CipherSuite, nil]
# File lib/tttls1.3/server.rb, line 515 def select_cipher_suite(ch) ch.cipher_suites.find do |cs| @settings[:cipher_suites].include?(cs) end end
@param ch [TTTLS13::Message::ClientHello]
@return [TTTLS13::NamedGroup, nil]
# File lib/tttls1.3/server.rb, line 524 def select_named_group(ch) ks_groups = ch.extensions[Message::ExtensionType::KEY_SHARE] &.key_share_entry&.map(&:group) || [] ks_groups.find do |g| @settings[:supported_groups].include?(g) end end
@param ch [TTTLS13::Message::ClientHello] @param crt [OpenSSL::X509::Certificate]
@return [TTTLS13::SignatureScheme, nil]
# File lib/tttls1.3/server.rb, line 537 def select_signature_scheme(ch, crt) algorithms = ch.extensions[Message::ExtensionType::SIGNATURE_ALGORITHMS] &.supported_signature_algorithms || [] do_select_signature_algorithms(algorithms, crt).find do |ss| @settings[:signature_algorithms].include?(ss) end end
@param ch1 [TTTLS13::Message::ClientHello] @param cipher_suite [TTTLS13::CipherSuite]
@return [TTTLS13::Message::ServerHello]
# File lib/tttls1.3/server.rb, line 357 def send_hello_retry_request(ch1, cipher_suite) exs = Message::Extensions.new # supported_versions exs << Message::Extension::SupportedVersions.new( msg_type: Message::HandshakeType::SERVER_HELLO ) # key_share sp_groups = ch1.extensions[Message::ExtensionType::SUPPORTED_GROUPS] &.named_group_list || [] ks_groups = ch1.extensions[Message::ExtensionType::KEY_SHARE] &.key_share_entry&.map(&:group) || [] ksg = sp_groups.find do |g| !ks_groups.include?(g) && @settings[:supported_groups].include?(g) end exs << Message::Extension::KeyShare.gen_hrr_key_share(ksg) # TODO: cookie sh = Message::ServerHello.new( random: Message::HRR_RANDOM, legacy_session_id_echo: ch1.legacy_session_id, cipher_suite: cipher_suite, extensions: exs ) send_handshakes(Message::ContentType::HANDSHAKE, [sh], Cryptograph::Passer.new) sh end
@param extensions [TTTLS13::Message::Extensions] @param cipher_suite [TTTLS13::CipherSuite] @param session_id [String]
@return [TTTLS13::Message::ServerHello]
# File lib/tttls1.3/server.rb, line 341 def send_server_hello(extensions, cipher_suite, session_id) sh = Message::ServerHello.new( legacy_session_id_echo: session_id, cipher_suite: cipher_suite, extensions: extensions ) send_handshakes(Message::ContentType::HANDSHAKE, [sh], Cryptograph::Passer.new) sh end
@param messages [Array of TTTLS13::Message::$Object] @param cipher [TTTLS13::Cryptograph::Aead]
@return [Array of TTTLS13::Message::$Object]
# File lib/tttls1.3/server.rb, line 392 def send_server_parameters(messages, cipher) send_handshakes(Message::ContentType::APPLICATION_DATA, messages.reject(&:nil?), cipher) messages end
@param key [OpenSSL::PKey::PKey] @param signature_scheme [TTTLS13::SignatureScheme] @param hash [String]
@return [String]
# File lib/tttls1.3/server.rb, line 503 def sign_certificate_verify(key:, signature_scheme:, hash:) do_sign_certificate_verify( key: key, signature_scheme: signature_scheme, context: 'TLS 1.3, server CertificateVerify', hash: hash ) end
@return [Boolean]
# File lib/tttls1.3/server.rb, line 307 def valid_settings? mod = CipherSuite defined = mod.constants.map { |c| mod.const_get(c) } return false unless (@settings[:cipher_suites] - defined).empty? mod = SignatureScheme defined = mod.constants.map { |c| mod.const_get(c) } return false unless (@settings[:signature_algorithms] - defined).empty? mod = NamedGroup defined = mod.constants.map { |c| mod.const_get(c) } return false unless (@settings[:supported_groups] - defined).empty? true end