class TTTLS13::Client

rubocop: disable Metrics/ClassLength

Public Class Methods

new(socket, hostname, **settings) click to toggle source

@param socket [Socket] @param hostname [String] @param settings [Hash]

Calls superclass method
# File lib/tttls1.3/client.rb, line 74
def initialize(socket, hostname, **settings)
  super(socket)

  @endpoint = :client
  @hostname = hostname
  @settings = DEFAULT_CLIENT_SETTINGS.merge(settings)
  logger.level = @settings[:loglevel]

  @early_data = ''
  @succeed_early_data = false
  raise Error::ConfigError unless valid_settings?
end
softfail_check_certificate_status(res, cert, chain) click to toggle source

@param res [OpenSSL::OCSP::Response] @param cert [OpenSSL::X509::Certificate] @param chain [Array of OpenSSL::X509::Certificate, nil]

@return [Boolean]

@example

m = Client.method(:softfail_check_certificate_status)
Client.new(
  socket,
  hostname,
  check_certificate_status: true,
  process_certificate_status: m
)
# File lib/tttls1.3/client.rb, line 413
def self.softfail_check_certificate_status(res, cert, chain)
  ocsp_response = res
  cid = OpenSSL::OCSP::CertificateId.new(cert, chain.first)

  # When NOT received OCSPResponse in TLS handshake, this method will
  # send OCSPRequest. If ocsp_uri is NOT presented in Certificate, return
  # true. Also, if it sends OCSPRequest and does NOT receive a HTTPresponse
  # within 2 seconds, return true.
  if ocsp_response.nil?
    uri = cert.ocsp_uris&.find { |u| URI::DEFAULT_PARSER.make_regexp =~ u }
    return true if uri.nil?

    begin
      # send OCSP::Request
      ocsp_request = gen_ocsp_request(cid)
      Timeout.timeout(2) do
        ocsp_response = send_ocsp_request(ocsp_request, uri)
      end

      # check nonce of OCSP::Response
      check_nonce = ocsp_request.check_nonce(ocsp_response.basic)
      return true unless [-1, 1].include?(check_nonce)
    rescue StandardError
      return true
    end
  end
  return true \
    if ocsp_response.status != OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL

  status = ocsp_response.basic.status.find { |s| s.first.cmp(cid) }
  status[1] != OpenSSL::OCSP::V_CERTSTATUS_REVOKED
end

Public Instance Methods

connect() click to toggle source

NOTE:

                         START <----+
          Send ClientHello |        | Recv HelloRetryRequest
     [K_send = early data] |        |
                           v        |
      /                 WAIT_SH ----+
      |                    | Recv ServerHello
      |                    | K_recv = handshake
  Can |                    V
 send |                 WAIT_EE
early |                    | Recv EncryptedExtensions
 data |           +--------+--------+
      |     Using |                 | Using certificate
      |       PSK |                 v
      |           |            WAIT_CERT_CR
      |           |        Recv |       | Recv CertificateRequest
      |           | Certificate |       v
      |           |             |    WAIT_CERT
      |           |             |       | Recv Certificate
      |           |             v       v
      |           |              WAIT_CV
      |           |                 | Recv CertificateVerify
      |           +> WAIT_FINISHED <+
      |                  | Recv Finished
      \                  | [Send EndOfEarlyData]
                         | K_send = handshake
                         | [Send Certificate [+ CertificateVerify]]

Can send | Send Finished app data –> | K_send = K_recv = application after here v

CONNECTED

tools.ietf.org/html/rfc8446#appendix-A.1

rubocop: disable Metrics/AbcSize rubocop: disable Metrics/BlockLength rubocop: disable Metrics/CyclomaticComplexity rubocop: disable Metrics/MethodLength rubocop: disable Metrics/PerceivedComplexity

# File lib/tttls1.3/client.rb, line 126
def connect
  transcript = Transcript.new
  key_schedule = nil # TTTLS13::KeySchedule
  psk = nil
  priv_keys = {} # Hash of NamedGroup => OpenSSL::PKey::$Object
  if use_psk?
    psk = gen_psk_from_nst(
      @settings[:resumption_master_secret],
      @settings[:ticket_nonce],
      CipherSuite.digest(@settings[:psk_cipher_suite])
    )
    key_schedule = KeySchedule.new(
      psk: psk,
      shared_secret: nil,
      cipher_suite: @settings[:psk_cipher_suite],
      transcript: transcript
    )
  end

  hs_wcipher = nil # TTTLS13::Cryptograph::$Object
  hs_rcipher = nil # TTTLS13::Cryptograph::$Object
  e_wcipher = nil # TTTLS13::Cryptograph::$Object

  @state = ClientState::START
  loop do
    case @state
    when ClientState::START
      logger.debug('ClientState::START')

      extensions, priv_keys = gen_ch_extensions
      binder_key = (use_psk? ? key_schedule.binder_key_res : nil)
      transcript[CH] = send_client_hello(extensions, binder_key)

      send_ccs if @settings[:compatibility_mode]
      if use_early_data?
        e_wcipher = gen_cipher(
          @settings[:psk_cipher_suite],
          key_schedule.early_data_write_key,
          key_schedule.early_data_write_iv
        )
        send_early_data(e_wcipher)
      end

      @state = ClientState::WAIT_SH
    when ClientState::WAIT_SH
      logger.debug('ClientState::WAIT_SH')

      sh = transcript[SH] = recv_server_hello

      # downgrade protection
      if !sh.negotiated_tls_1_3? && sh.downgraded?
        terminate(:illegal_parameter)
      # support only TLS 1.3
      elsif !sh.negotiated_tls_1_3?
        terminate(:protocol_version)
      end

      # validate parameters
      terminate(:illegal_parameter) \
        unless sh.appearable_extensions?
      terminate(:illegal_parameter) \
        unless sh.legacy_compression_method == "\x00"

      # validate sh using ch
      ch = transcript[CH]
      terminate(:illegal_parameter) \
        unless sh.legacy_version == ch.legacy_version
      terminate(:illegal_parameter) \
        unless sh.legacy_session_id_echo == ch.legacy_session_id
      terminate(:illegal_parameter) \
        unless ch.cipher_suites.include?(sh.cipher_suite)
      terminate(:unsupported_extension) \
        unless (sh.extensions.keys - ch.extensions.keys).empty?

      # validate sh using hrr
      if transcript.include?(HRR)
        hrr = transcript[HRR]
        terminate(:illegal_parameter) \
          unless sh.cipher_suite == hrr.cipher_suite

        sh_sv = sh.extensions[Message::ExtensionType::SUPPORTED_VERSIONS]
        hrr_sv = hrr.extensions[Message::ExtensionType::SUPPORTED_VERSIONS]
        terminate(:illegal_parameter) \
          unless sh_sv.versions == hrr_sv.versions
      end

      # handling HRR
      if sh.hrr?
        terminate(:unexpected_message) if transcript.include?(HRR)
        ch1 = transcript[CH1] = transcript.delete(CH)
        hrr = transcript[HRR] = transcript.delete(SH)

        # validate cookie
        diff_sets = sh.extensions.keys - ch1.extensions.keys
        terminate(:unsupported_extension) \
          unless (diff_sets - [Message::ExtensionType::COOKIE]).empty?

        # validate key_share
        # TODO: pre_shared_key
        ngl = ch1.extensions[Message::ExtensionType::SUPPORTED_GROUPS]
                 .named_group_list
        kse = ch1.extensions[Message::ExtensionType::KEY_SHARE]
                 .key_share_entry
        group = hrr.extensions[Message::ExtensionType::KEY_SHARE]
                   .key_share_entry.first.group
        terminate(:illegal_parameter) \
          unless ngl.include?(group) && !kse.map(&:group).include?(group)

        # send new client_hello
        extensions, pk = gen_newch_extensions(ch1, hrr)
        priv_keys = pk.merge(priv_keys)
        binder_key = (use_psk? ? key_schedule.binder_key_res : nil)
        transcript[CH] = send_new_client_hello(ch1, hrr, extensions,
                                               binder_key)
        @state = ClientState::WAIT_SH
        next
      end

      # generate shared secret
      psk = nil unless sh.extensions
                         .include?(Message::ExtensionType::PRE_SHARED_KEY)
      ch_ks = ch.extensions[Message::ExtensionType::KEY_SHARE]
                .key_share_entry.map(&:group)
      sh_ks = sh.extensions[Message::ExtensionType::KEY_SHARE]
                .key_share_entry.first.group
      terminate(:illegal_parameter) unless ch_ks.include?(sh_ks)

      kse = sh.extensions[Message::ExtensionType::KEY_SHARE]
              .key_share_entry.first
      ke = kse.key_exchange
      @named_group = kse.group
      priv_key = priv_keys[@named_group]
      shared_secret = gen_shared_secret(ke, priv_key, @named_group)
      @cipher_suite = sh.cipher_suite
      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.client_handshake_write_key,
        key_schedule.client_handshake_write_iv
      )
      hs_rcipher = gen_cipher(
        @cipher_suite,
        key_schedule.server_handshake_write_key,
        key_schedule.server_handshake_write_iv
      )
      @state = ClientState::WAIT_EE
    when ClientState::WAIT_EE
      logger.debug('ClientState::WAIT_EE')

      ee = transcript[EE] = recv_encrypted_extensions(hs_rcipher)
      terminate(:illegal_parameter) unless ee.appearable_extensions?

      ch = transcript[CH]
      terminate(:unsupported_extension) \
        unless (ee.extensions.keys - ch.extensions.keys).empty?

      rsl = ee.extensions[Message::ExtensionType::RECORD_SIZE_LIMIT]
      @recv_record_size = rsl.record_size_limit unless rsl.nil?

      @succeed_early_data = true \
        if ee.extensions.include?(Message::ExtensionType::EARLY_DATA)

      @alpn = ee.extensions[
        Message::ExtensionType::APPLICATION_LAYER_PROTOCOL_NEGOTIATION
      ]&.protocol_name_list&.first

      @state = ClientState::WAIT_CERT_CR
      @state = ClientState::WAIT_FINISHED unless psk.nil?
    when ClientState::WAIT_CERT_CR
      logger.debug('ClientState::WAIT_CERT_CR')

      message = recv_message(receivable_ccs: true, cipher: hs_rcipher)
      if message.msg_type == Message::HandshakeType::CERTIFICATE
        ct = transcript[CT] = message
        alert = check_invalid_certificate(ct, transcript[CH])
        terminate(alert) unless alert.nil?

        @state = ClientState::WAIT_CV
      elsif message.msg_type == Message::HandshakeType::CERTIFICATE_REQUEST
        transcript[CR] = message
        # TODO: client authentication
        @state = ClientState::WAIT_CERT
      else
        terminate(:unexpected_message)
      end
    when ClientState::WAIT_CERT
      logger.debug('ClientState::WAIT_CERT')

      ct = transcript[CT] = recv_certificate(hs_rcipher)
      alert = check_invalid_certificate(ct, transcript[CH])
      terminate(alert) unless alert.nil?

      @state = ClientState::WAIT_CV
    when ClientState::WAIT_CV
      logger.debug('ClientState::WAIT_CV')

      cv = transcript[CV] = recv_certificate_verify(hs_rcipher)
      digest = CipherSuite.digest(@cipher_suite)
      hash = transcript.hash(digest, CT)
      terminate(:decrypt_error) \
        unless verified_certificate_verify?(transcript[CT], cv, hash)

      @signature_scheme = cv.signature_scheme

      @state = ClientState::WAIT_FINISHED
    when ClientState::WAIT_FINISHED
      logger.debug('ClientState::WAIT_FINISHED')

      sf = transcript[SF] = recv_finished(hs_rcipher)
      digest = CipherSuite.digest(@cipher_suite)
      verified = verified_finished?(
        finished: sf,
        digest: digest,
        finished_key: key_schedule.server_finished_key,
        hash: transcript.hash(digest, CV)
      )
      terminate(:decrypt_error) unless verified

      transcript[EOED] = send_eoed(e_wcipher) \
        if use_early_data? && succeed_early_data?

      # TODO: Send Certificate [+ CertificateVerify]
      signature = sign_finished(
        digest: digest,
        finished_key: key_schedule.client_finished_key,
        hash: transcript.hash(digest, EOED)
      )
      transcript[CF] = send_finished(signature, hs_wcipher)
      @alert_wcipher = @ap_wcipher = gen_cipher(
        @cipher_suite,
        key_schedule.client_application_write_key,
        key_schedule.client_application_write_iv
      )
      @ap_rcipher = gen_cipher(
        @cipher_suite,
        key_schedule.server_application_write_key,
        key_schedule.server_application_write_iv
      )
      @exporter_master_secret = key_schedule.exporter_master_secret
      @resumption_master_secret = key_schedule.resumption_master_secret
      @state = ClientState::CONNECTED
    when ClientState::CONNECTED
      logger.debug('ClientState::CONNECTED')

      break
    end
  end
end
early_data(binary) click to toggle source

@param binary [String]

@raise [TTTLS13::Error::ConfigError]

# File lib/tttls1.3/client.rb, line 388
def early_data(binary)
  raise Error::ConfigError unless @state == INITIAL && use_psk?

  @early_data = binary
end
succeed_early_data?() click to toggle source

@return [Boolean]

# File lib/tttls1.3/client.rb, line 395
def succeed_early_data?
  @succeed_early_data
end

Private Instance Methods

calc_obfuscated_ticket_age() click to toggle source

@return [Integer]

# File lib/tttls1.3/client.rb, line 661
def calc_obfuscated_ticket_age
  # the "ticket_lifetime" field in the NewSessionTicket message is
  # in seconds but the "obfuscated_ticket_age" is in milliseconds.
  age = (Time.now.to_f * 1000).to_i - @settings[:ticket_timestamp] * 1000
  (age + Convert.bin2i(@settings[:ticket_age_add])) % (2**32)
end
check_invalid_certificate(ct, ch) click to toggle source

@param ct [TTTLS13::Message::Certificate] @param ch [TTTLS13::Message::ClientHello]

@return [Symbol, nil] return key of ALERT_DESCRIPTION, if invalid

# File lib/tttls1.3/client.rb, line 818
def check_invalid_certificate(ct, ch)
  return :illegal_parameter unless ct.appearable_extensions?

  return :unsupported_extension \
    unless ct.certificate_list.map(&:extensions)
             .all? { |e| (e.keys - ch.extensions.keys).empty? }

  return :certificate_unknown unless trusted_certificate?(
    ct.certificate_list,
    @settings[:ca_file],
    @hostname
  )

  if @settings[:check_certificate_status]
    ee = ct.certificate_list.first
    ocsp_response = ee.extensions[Message::ExtensionType::STATUS_REQUEST]
                     &.ocsp_response
    cert = ee.cert_data
    chain = ct.certificate_list[1..]&.map(&:cert_data)
    return :bad_certificate_status_response \
      unless satisfactory_certificate_status?(ocsp_response, cert, chain)
  end

  nil
end
gen_ch_extensions() click to toggle source

@return [TTTLS13::Message::Extensions] @return [Hash of NamedGroup => OpenSSL::PKey::EC.$Object] rubocop: disable Metrics/AbcSize rubocop: disable Metrics/CyclomaticComplexity rubocop: disable Metrics/MethodLength rubocop: disable Metrics/PerceivedComplexity

# File lib/tttls1.3/client.rb, line 530
def gen_ch_extensions
  exs = Message::Extensions.new
  # server_name
  exs << Message::Extension::ServerName.new(@hostname)

  # record_size_limit
  unless @settings[:record_size_limit].nil?
    exs << Message::Extension::RecordSizeLimit.new(
      @settings[:record_size_limit]
    )
  end

  # supported_versions: only TLS 1.3
  exs << Message::Extension::SupportedVersions.new(
    msg_type: Message::HandshakeType::CLIENT_HELLO
  )

  # signature_algorithms
  exs << Message::Extension::SignatureAlgorithms.new(
    @settings[:signature_algorithms]
  )

  # signature_algorithms_cert
  if !@settings[:signature_algorithms_cert].nil? &&
     !@settings[:signature_algorithms_cert].empty?
    exs << Message::Extension::SignatureAlgorithmsCert.new(
      @settings[:signature_algorithms_cert]
    )
  end

  # supported_groups
  groups = @settings[:supported_groups]
  exs << Message::Extension::SupportedGroups.new(groups)

  # key_share
  ksg = @settings[:key_share_groups] || groups
  key_share, priv_keys \
             = Message::Extension::KeyShare.gen_ch_key_share(ksg)
  exs << key_share

  # early_data
  exs << Message::Extension::EarlyDataIndication.new if use_early_data?

  # alpn
  exs << Message::Extension::Alpn.new(@settings[:alpn].reject(&:empty?)) \
    if !@settings[:alpn].nil? && !@settings[:alpn].empty?

  # status_request
  exs << Message::Extension::OCSPStatusRequest.new \
    if @settings[:check_certificate_status]

  [exs, priv_keys]
end
gen_newch_extensions(ch1, hrr) click to toggle source

@param ch1 [TTTLS13::Message::ClientHello] @param hrr [TTTLS13::Message::ServerHello]

@return [TTTLS13::Message::Extensions] @return [Hash of NamedGroup => OpenSSL::PKey::EC.$Object]

# File lib/tttls1.3/client.rb, line 673
def gen_newch_extensions(ch1, hrr)
  exs = Message::Extensions.new
  # key_share
  if hrr.extensions.include?(Message::ExtensionType::KEY_SHARE)
    group = hrr.extensions[Message::ExtensionType::KEY_SHARE]
               .key_share_entry.first.group
    key_share, priv_keys \
               = Message::Extension::KeyShare.gen_ch_key_share([group])
    exs << key_share
  end

  # cookie
  #
  # When sending a HelloRetryRequest, the server MAY provide a "cookie"
  # extension to the client. When sending the new ClientHello, the client
  # MUST copy the contents of the extension received in the
  # HelloRetryRequest into a "cookie" extension in the new ClientHello.
  #
  # https://tools.ietf.org/html/rfc8446#section-4.2.2
  exs << hrr.extensions[Message::ExtensionType::COOKIE] \
    if hrr.extensions.include?(Message::ExtensionType::COOKIE)

  # early_data
  new_exs = ch1.extensions.merge(exs)
  new_exs.delete(Message::ExtensionType::EARLY_DATA)

  [new_exs, priv_keys]
end
gen_psk_from_nst(resumption_master_secret, ticket_nonce, digest) click to toggle source

@param resumption_master_secret [String] @param ticket_nonce [String] @param digest [String] name of digest algorithm

@return [String]

# File lib/tttls1.3/client.rb, line 518
def gen_psk_from_nst(resumption_master_secret, ticket_nonce, digest)
  hash_len = OpenSSL::Digest.new(digest).digest_length
  KeySchedule.hkdf_expand_label(resumption_master_secret, 'resumption',
                                ticket_nonce, hash_len, digest)
end
process_new_session_ticket(nst) click to toggle source

@param nst [TTTLS13::Message::NewSessionTicket]

@raise [TTTLS13::Error::ErrorAlerts]

Calls superclass method
# File lib/tttls1.3/client.rb, line 875
def process_new_session_ticket(nst)
  super(nst)

  rms = @resumption_master_secret
  cs = @cipher_suite
  @settings[:process_new_session_ticket]&.call(nst, rms, cs)
end
recv_certificate(cipher) click to toggle source

@param cipher [TTTLS13::Cryptograph::Aead]

@raise [TTTLS13::Error::ErrorAlerts]

@return [TTTLS13::Message::Certificate]

# File lib/tttls1.3/client.rb, line 763
def recv_certificate(cipher)
  ct = recv_message(receivable_ccs: true, cipher: cipher)
  terminate(:unexpected_message) unless ct.is_a?(Message::Certificate)

  ct
end
recv_certificate_verify(cipher) click to toggle source

@param cipher [TTTLS13::Cryptograph::Aead]

@raise [TTTLS13::Error::ErrorAlerts]

@return [TTTLS13::Message::CertificateVerify]

# File lib/tttls1.3/client.rb, line 775
def recv_certificate_verify(cipher)
  cv = recv_message(receivable_ccs: true, cipher: cipher)
  terminate(:unexpected_message) unless cv.is_a?(Message::CertificateVerify)

  cv
end
recv_encrypted_extensions(cipher) click to toggle source

@param cipher [TTTLS13::Cryptograph::Aead]

@raise [TTTLS13::Error::ErrorAlerts]

@return [TTTLS13::Message::EncryptedExtensions]

# File lib/tttls1.3/client.rb, line 750
def recv_encrypted_extensions(cipher)
  ee = recv_message(receivable_ccs: true, cipher: cipher)
  terminate(:unexpected_message) \
    unless ee.is_a?(Message::EncryptedExtensions)

  ee
end
recv_finished(cipher) click to toggle source

@param cipher [TTTLS13::Cryptograph::Aead]

@raise [TTTLS13::Error::ErrorAlerts]

@return [TTTLS13::Message::Finished]

# File lib/tttls1.3/client.rb, line 787
def recv_finished(cipher)
  sf = recv_message(receivable_ccs: true, cipher: cipher)
  terminate(:unexpected_message) unless sf.is_a?(Message::Finished)

  sf
end
recv_server_hello() click to toggle source

@raise [TTTLS13::Error::ErrorAlerts]

@return [TTTLS13::Message::ServerHello]

# File lib/tttls1.3/client.rb, line 738
def recv_server_hello
  sh = recv_message(receivable_ccs: true, cipher: Cryptograph::Passer.new)
  terminate(:unexpected_message) unless sh.is_a?(Message::ServerHello)

  sh
end
satisfactory_certificate_status?(ocsp_response, cert, chain) click to toggle source

@param ocsp_response [OpenSSL::OCSP::Response] @param cert [OpenSSL::X509::Certificate] @param chain [Array of OpenSSL::X509::Certificate, nil]

@return [Boolean]

# File lib/tttls1.3/client.rb, line 868
def satisfactory_certificate_status?(ocsp_response, cert, chain)
  @settings[:process_certificate_status]&.call(ocsp_response, cert, chain)
end
send_client_hello(extensions, binder_key = nil) click to toggle source

@param extensions [TTTLS13::Message::Extensions] @param binder_key [String, nil]

@return [TTTLS13::Message::ClientHello]

# File lib/tttls1.3/client.rb, line 592
def send_client_hello(extensions, binder_key = nil)
  ch = Message::ClientHello.new(
    cipher_suites: CipherSuites.new(@settings[:cipher_suites]),
    extensions: extensions
  )

  if use_psk?
    # pre_shared_key && psk_key_exchange_modes
    #
    # In order to use PSKs, clients MUST also send a
    # "psk_key_exchange_modes" extension.
    #
    # https://tools.ietf.org/html/rfc8446#section-4.2.9
    pkem = Message::Extension::PskKeyExchangeModes.new(
      [Message::Extension::PskKeyExchangeMode::PSK_DHE_KE]
    )
    ch.extensions[Message::ExtensionType::PSK_KEY_EXCHANGE_MODES] = pkem
    # at the end, sign PSK binder
    sign_psk_binder(
      ch: ch,
      binder_key: binder_key
    )
  end

  send_handshakes(Message::ContentType::HANDSHAKE, [ch],
                  Cryptograph::Passer.new)

  ch
end
send_early_data(cipher) click to toggle source

@param cipher [TTTLS13::Cryptograph::Aead]

# File lib/tttls1.3/client.rb, line 502
def send_early_data(cipher)
  ap = Message::ApplicationData.new(@early_data)
  ap_record = Message::Record.new(
    type: Message::ContentType::APPLICATION_DATA,
    legacy_record_version: Message::ProtocolVersion::TLS_1_2,
    messages: [ap],
    cipher: cipher
  )
  send_record(ap_record)
end
send_eoed(cipher) click to toggle source

@param cipher [TTTLS13::Cryptograph::Aead]

@return [TTTLS13::Message::EndOfEarlyData]

# File lib/tttls1.3/client.rb, line 807
def send_eoed(cipher)
  eoed = Message::EndOfEarlyData.new
  send_handshakes(Message::ContentType::APPLICATION_DATA, [eoed], cipher)

  eoed
end
send_finished(signature, cipher) click to toggle source

@param cipher [TTTLS13::Cryptograph::Aead]

@return [TTTLS13::Message::Finished]

# File lib/tttls1.3/client.rb, line 797
def send_finished(signature, cipher)
  cf = Message::Finished.new(signature)
  send_handshakes(Message::ContentType::APPLICATION_DATA, [cf], cipher)

  cf
end
send_new_client_hello(ch1, hrr, extensions, binder_key = nil) click to toggle source

NOTE: tools.ietf.org/html/rfc8446#section-4.1.2

@param ch1 [TTTLS13::Message::ClientHello] @param hrr [TTTLS13::Message::ServerHello] @param extensions [TTTLS13::Message::Extensions] @param binder_key [String, nil]

@return [TTTLS13::Message::ClientHello]

# File lib/tttls1.3/client.rb, line 711
def send_new_client_hello(ch1, hrr, extensions, binder_key = nil)
  ch = Message::ClientHello.new(
    legacy_version: ch1.legacy_version,
    random: ch1.random,
    legacy_session_id: ch1.legacy_session_id,
    cipher_suites: ch1.cipher_suites,
    legacy_compression_methods: ch1.legacy_compression_methods,
    extensions: extensions
  )

  # pre_shared_key
  #
  # Updating the "pre_shared_key" extension if present by recomputing
  # the "obfuscated_ticket_age" and binder values.
  if ch1.extensions.include?(Message::ExtensionType::PRE_SHARED_KEY)
    sign_psk_binder(ch1: ch1, hrr: hrr, ch: ch, binder_key: binder_key)
  end

  send_handshakes(Message::ContentType::HANDSHAKE, [ch],
                  Cryptograph::Passer.new)

  ch
end
sign_psk_binder(ch1: nil, hrr: nil, ch:, binder_key:) click to toggle source

@param ch1 [TTTLS13::Message::ClientHello] @param hrr [TTTLS13::Message::ServerHello] @param ch [TTTLS13::Message::ClientHello] @param binder_key [String]

@return [String]

# File lib/tttls1.3/client.rb, line 628
def sign_psk_binder(ch1: nil, hrr: nil, ch:, binder_key:)
  # pre_shared_key
  #
  # binder is computed as an HMAC over a transcript hash containing a
  # partial ClientHello up to and including the
  # PreSharedKeyExtension.identities field.
  #
  # https://tools.ietf.org/html/rfc8446#section-4.2.11.2
  digest = CipherSuite.digest(@settings[:psk_cipher_suite])
  hash_len = OpenSSL::Digest.new(digest).digest_length
  dummy_binders = ["\x00" * hash_len]
  psk = Message::Extension::PreSharedKey.new(
    msg_type: Message::HandshakeType::CLIENT_HELLO,
    offered_psks: Message::Extension::OfferedPsks.new(
      identities: [Message::Extension::PskIdentity.new(
        identity: @settings[:ticket],
        obfuscated_ticket_age: calc_obfuscated_ticket_age
      )],
      binders: dummy_binders
    )
  )
  ch.extensions[Message::ExtensionType::PRE_SHARED_KEY] = psk

  psk.offered_psks.binders[0] = do_sign_psk_binder(
    ch1: ch1,
    hrr: hrr,
    ch: ch,
    binder_key: binder_key,
    digest: digest
  )
end
use_early_data?() click to toggle source

@return [Boolean]

# File lib/tttls1.3/client.rb, line 497
def use_early_data?
  !(@early_data.nil? || @early_data.empty?)
end
use_psk?() click to toggle source

@return [Boolean]

# File lib/tttls1.3/client.rb, line 487
def use_psk?
  !@settings[:ticket].nil? &&
    !@settings[:resumption_master_secret].nil? &&
    !@settings[:psk_cipher_suite].nil? &&
    !@settings[:ticket_nonce].nil? &&
    !@settings[:ticket_age_add].nil? &&
    !@settings[:ticket_timestamp].nil?
end
valid_settings?() click to toggle source

@return [Boolean] rubocop: disable Metrics/AbcSize rubocop: disable Metrics/CyclomaticComplexity rubocop: disable Metrics/PerceivedComplexity

# File lib/tttls1.3/client.rb, line 452
def valid_settings?
  mod = CipherSuite
  defined_cipher_suites = mod.constants.map { |c| mod.const_get(c) }
  return false \
    unless (@settings[:cipher_suites] - defined_cipher_suites).empty?

  sa = @settings[:signature_algorithms]
  mod = SignatureScheme
  defined_signature_schemes = mod.constants.map { |c| mod.const_get(c) }
  return false unless (sa - defined_signature_schemes).empty?

  sac = @settings[:signature_algorithms_cert] || []
  return false unless (sac - defined_signature_schemes).empty?

  sg = @settings[:supported_groups]
  return false unless (sac - defined_signature_schemes).empty?

  ksg = @settings[:key_share_groups]
  return false \
    unless ksg.nil? ||
           ((ksg - sg).empty? && sg.select { |g| ksg.include?(g) } == ksg)

  rsl = @settings[:record_size_limit]
  return false if !rsl.nil? && (rsl < 64 || rsl > 2**14 + 1)

  return false if @settings[:check_certificate_status] &&
                  @settings[:process_certificate_status].nil?

  true
end
verified_certificate_verify?(ct, cv, hash) click to toggle source

@param ct [TTTLS13::Message::Certificate] @param cv [TTTLS13::Message::CertificateVerify] @param hash [String]

@return [Boolean]

# File lib/tttls1.3/client.rb, line 849
def verified_certificate_verify?(ct, cv, hash)
  public_key = ct.certificate_list.first.cert_data.public_key
  signature_scheme = cv.signature_scheme
  signature = cv.signature

  do_verified_certificate_verify?(
    public_key: public_key,
    signature_scheme: signature_scheme,
    signature: signature,
    context: 'TLS 1.3, server CertificateVerify',
    hash: hash
  )
end