class SSLShake::TLS

Constants

ALERT_DESCRIPTIONS

tools.ietf.org/html/rfc5246#appendix-A.3 tools.ietf.org/html/rfc8446#appendix-B.2

ALERT_SEVERITY
CONTENT_TYPES
HANDSHAKE_TYPES
OPENSSL_1_0_2_TLS10_CIPHERS

Additional collection of ciphers used by different apps and versions

OPENSSL_1_0_2_TLS11_CIPHERS
OPENSSL_1_0_2_TLS12_CIPHERS
SSL3_CIPHERS

tools.ietf.org/html/rfc6101#appendix-A.6

TLS10_CIPHERS
TLS13_CIPHERS
TLS_CIPHERS
VERSIONS

Public Instance Methods

hello(version, cipher_search = nil, extensions = nil) click to toggle source
# File lib/sslshake/tls.rb, line 118
def hello(version, cipher_search = nil, extensions = nil)
  case version
  when 'ssl3'
    ciphers = cipher_string(SSL3_CIPHERS, cipher_search)
  when 'tls1.0', 'tls1.1'
    ciphers = cipher_string(TLS_CIPHERS, cipher_search)
    (extensions ||= '') << '000f000101' # add Heartbeat
    extensions << '000d00140012040308040401050308050501080606010201' # add signature_algorithms
    extensions << '000b00020100' # add ec_points_format
    extensions << '000a000a0008fafa001d00170018' # add elliptic_curve
  when 'tls1.2'
    ciphers = cipher_string(TLS_CIPHERS, cipher_search)
    (extensions ||= '') << '000f000101' # add Heartbeat
    extensions << '000d00140012040308040401050308050501080606010201' # add signature_algorithms
    extensions << '000b00020100' # add ec_points_format
    extensions << '000a000a0008fafa001d00170018' # add elliptic_curve
  when 'tls1.3'
    ciphers = cipher_string(TLS13_CIPHERS, cipher_search)
    (extensions ||= '') << '002b0003020304' # TLSv1.3 Supported Versions extension
    extensions << '000d00140012040308040401050308050501080606010201' # add signature_algorithms
    extensions << '000a00080006001d00170018' # Supported Groups extension
    # This is a pre-generated public/private key pair using the x25519 curve:
    # It was generated from the command line with:
    #
    # > openssl-1.1.1e/apps/openssl genpkey -algorithm x25519 > pkey
    # > openssl-1.1.1e/apps/openssl pkey -noout -text < pkey
    # priv:
    #     30:90:f3:89:f4:9e:52:59:3c:ba:e9:f4:78:84:a0:
    #     23:86:73:5e:f5:c9:46:6c:3a:c3:4e:ec:56:57:81:
    #     5d:62
    # pub:
    #     e7:08:71:36:d0:81:e0:16:19:3a:cb:67:ca:b8:28:
    #     d9:45:92:16:ff:36:63:0d:0d:5a:3d:9d:47:ce:3e:
    #     cd:7e
    public_key= 'e7087136d081e016193acb67cab828d9459216ff36630d0d5a3d9d47ce3ecd7e'
    extensions << '003300260024001d0020' + public_key
  else
    fail UserError, "This version is not supported: #{version.inspect}"
  end
  hello_tls(version, ciphers, extensions || '')
end
parse_hello(socket, opts) click to toggle source
# File lib/sslshake/tls.rb, line 160
def parse_hello(socket, opts) # rubocop:disable Meterics/AbcSize
  raw = socket_read(socket, 5, opts[:timeout], opts[:retries])
          .unpack('H*')[0].upcase.scan(/../)
  type = raw.shift
  if type == CONTENT_TYPES['Alert']
    raw.shift(2) # Shift off version
    len = raw.shift(2).join.to_i(16)
    raw_alert = socket_read(socket, len, opts[:timeout], opts[:retries])
                  .unpack('H*')[0].upcase.scan(/../)
    severity = ALERT_SEVERITY.key(raw_alert.shift)
    desc_raw = raw_alert.shift
    description = ALERT_DESCRIPTIONS.key(desc_raw)
    return { 'error' => "SSL Alert: #{severity} #{description || desc_raw}" }
  end
  unless type == CONTENT_TYPES['Handshake']
    return { 'error' => 'Failed to parse response. It is not an SSL handshake.' }
  end

  res = {}
  res['version'] = VERSIONS.key(raw.shift(2).join(''))
  len = raw.shift(2).join.to_i(16)

  res['raw'] = response = socket_read(socket, len, opts[:timeout], opts[:retries])
                          .unpack('H*')[0].upcase
  raw = response.scan(/../)

  res['handshake_type'] = HANDSHAKE_TYPES.key(raw.shift)
  _len = raw.shift(3)

  res['handshake_tls_version'] = VERSIONS.key(raw.shift(2).join(''))
  res['random'] = raw.shift(32).join('')

  len = raw.shift.to_i(16)
  res['session_id'] = raw.shift(len).join('')
  ciphers =
    case res['version']
    when 'ssl3'
      SSL3_CIPHERS
    else
      TLS_CIPHERS
    end
  res['cipher_suite'] = ciphers.key(raw.shift(2).join(''))
  res['compression_method'] = raw.shift

  #
  # TLS 1.3 pretends to be TLS 1.2 in the preceeding headers for
  # compatibility. To correctly identify it, we have to look at
  # any Supported Versions extensions that the server sent us.
  #
  all_ext_len = raw.shift(2).join.to_i(16)
  all_ext_data = raw.shift(all_ext_len)
  while all_ext_data.length > 0
    ext_type = all_ext_data.shift(2).join
    ext_len = all_ext_data.shift(2).join.to_i(16)
    ext_data = all_ext_data.shift(ext_len)
    if ext_type == '002B' && ext_data.join == '0304' # Supported Versions
      res['version'] = 'tls1.3'
    end
  end
  res['success'] = true
  res['success'] = (res['version'] == opts[:protocol]) unless opts[:protocol].nil?
  res
rescue SystemCallError, Alert => _
  return { 'error' => 'Failed to parse response. The connection was terminated.' }
end
ssl_hello(content, version) click to toggle source
# File lib/sslshake/tls.rb, line 110
def ssl_hello(content, version)
  ssl_record(
    CONTENT_TYPES['Handshake'],
    HANDSHAKE_TYPES['ClientHello'] + int_bytes(content.length / 2, 3) + content,
    version,
  )
end
ssl_record(content_type, content, version) click to toggle source
# File lib/sslshake/tls.rb, line 105
def ssl_record(content_type, content, version)
  res = content_type + version + int_bytes(content.length / 2, 2) + content
  [res].pack('H*')
end

Private Instance Methods

hello_tls(version, ciphers, extensions) click to toggle source
# File lib/sslshake/tls.rb, line 228
def hello_tls(version, ciphers, extensions)
  random = SecureRandom.hex(32)
  session_id = ''
  compressions = '00'
  c = VERSIONS[version] +
      random +
      int_bytes(session_id.length / 2, 1) +
      session_id +
      int_bytes(ciphers.length / 2, 2) +
      ciphers +
      int_bytes(compressions.length / 2, 1) +
      compressions +
      int_bytes(extensions.length / 2, 2) +
      extensions
  ssl_hello(c, VERSIONS[version])
end