class Riak::Client::BeefcakeProtobuffsBackend::BeefcakeSocket::TlsInitiator

Wrap up the logic to turn a TCP socket into a TLS socket. Depends on Beefcake, which should be relatively safe.

Constants

BC

@private


@private




Public Class Methods

new(tcp_socket, host, authentication) click to toggle source

Create a TLS Initiator

@param tcp_socket [TCPSocket] the {TCPSocket} to start TLS on @param authentication [Hash] a hash of authentication details

# File lib/riak/client/beefcake/socket.rb, line 44
def initialize(tcp_socket, host, authentication)
  @sock = @tcp = tcp_socket
  @host = host
  @auth = authentication
end

Public Instance Methods

tls_socket() click to toggle source

Return the SSLSocket that has a TLS session running. (TLS is a better and safer SSL).

@return [OpenSSL::SSL::SSLSocket]

# File lib/riak/client/beefcake/socket.rb, line 54
def tls_socket
  configure_context
  start_tls
  validate_session
  send_authentication
  validate_connection
  return @tls
end

Private Instance Methods

ca_cert() click to toggle source
# File lib/riak/client/beefcake/socket.rb, line 68
def ca_cert
  @ca_cert ||= @tls.peer_cert_chain[1]
end
cert_ify() click to toggle source

Convert cert and client_ca fields to X509 Certs

# File lib/riak/client/beefcake/socket.rb, line 102
def cert_ify
  %w{ cert client_ca }.each do |k|
    candidate = @auth[k.to_sym]
    next if candidate.nil?
    next if candidate.is_a? OpenSSL::X509::Certificate

    @auth[k.to_sym] = OpenSSL::X509::Certificate.new try_load candidate
  end
end
configure_context() click to toggle source

Set up an SSL context with appropriate defaults for Riak TLS

# File lib/riak/client/beefcake/socket.rb, line 73
def configure_context
  @context = OpenSSL::SSL::SSLContext.new

  # Replace insecure defaults
  @context.ssl_version = (@auth[:ssl_version] || default_ssl_version).to_sym
  @context.verify_mode = (@auth[:verify_mode] || OpenSSL::SSL::VERIFY_PEER).to_i

  cert_ify
  key_ify

  # Defer to defaults
  %w{ cert key client_ca ca_file ca_path timeout }.each do |k|
    @context.send(:"#{k}=", @auth[k.to_sym]) if @auth[k.to_sym]
  end
end
default_ssl_version() click to toggle source

Choose the most secure SSL version available

# File lib/riak/client/beefcake/socket.rb, line 90
def default_ssl_version
  available = OpenSSL::SSL::SSLContext::METHODS
  selected = %w{TLSv1_2_client TLSv1_1_client TLSv1.1 TLSv1_client TLS}.detect do |v|
    available.include? v.to_sym
  end

  raise TlsError::SslVersionConfigurationError.new unless selected

  return selected
end
expect_message(expected_code) click to toggle source
# File lib/riak/client/beefcake/socket.rb, line 240
def expect_message(expected_code)
  if expected_code.is_a? Numeric
    expected_code = BeefcakeMessageCodes[code]
  end

  candidate_code, message = read_message
  return message if expected_code == candidate_code

  raise TlsError.new(t('ssl.unexpected_during_init',
                       expected: expected_code.inspect,
                       actual: candidate_code.inspect,
                       body: message.inspect
                       ))

end
key_ify() click to toggle source
# File lib/riak/client/beefcake/socket.rb, line 112
def key_ify
  candidate = @auth[:key]
  return if candidate.nil?
  return if candidate.is_a? OpenSSL::PKey::PKey

  candidate = try_load candidate

  pkey_class_names = OpenSSL::PKey.
    constants.
    reject{|s| s.to_s =~ /Error$/}

  pkey_classes = pkey_class_names.map{ |n| OpenSSL::PKey.const_get n }

  pkey_classes.each do |klass|
    begin
      successfully_initialized = klass.new candidate
      @auth[:key] = successfully_initialized
      return
    rescue
      next
    end
  end

  # Don't try and guess what the key is
  raise TlsError::UnknownKeyTypeError.new
end
read_message() click to toggle source
# File lib/riak/client/beefcake/socket.rb, line 229
def read_message
  header = @sock.read 5
  raise TlsError.new(t('ssl.eof_during_init')) if header.nil?
  len, code = header.unpack 'NC'
  decode = BeefcakeMessageCodes[code]
  return decode, '' if len == 1

  message = @sock.read(len - 1)
  return decode, message
end
riak_cert() click to toggle source
# File lib/riak/client/beefcake/socket.rb, line 64
def riak_cert
  @riak_cert ||= @tls.peer_cert
end
send_authentication() click to toggle source

Send an AuthReq with the authentication data. Rely on beefcake discarding message parts it doesn’t understand.

# File lib/riak/client/beefcake/socket.rb, line 206
def send_authentication
  req = BC::RpbAuthReq.new @auth
  write_message :AuthReq, req.encode
  expect_message :AuthResp
end
start_tls() click to toggle source

Attempt to exchange the TCP socket for a TLS socket.

# File lib/riak/client/beefcake/socket.rb, line 157
def start_tls
  write_message :StartTls
  expect_message :StartTls
  # Swap the tls socket in for the tcp socket, so write_message and
  # read_message continue working
  @sock = @tls = OpenSSL::SSL::SSLSocket.new @tcp, @context
  @tls.connect
end
try_load(data_or_path) click to toggle source

Figure out if the given string is the data itself or a path to the data

# File lib/riak/client/beefcake/socket.rb, line 140
def try_load(data_or_path)
  begin
    data_or_path = File.read data_or_path
  rescue Errno::ENOENT
    # couldn't read the file, it might be a string containing
    # a key
  rescue Errno::ENAMETOOLONG
    # the filename is too long, it's almost certainly a string
    # containing a key
  rescue => e
    raise TlsError::ReadDataError.new e, data_or_path
  end

  return data_or_path
end
validate_connection() click to toggle source

Ping the Riak node and make sure it actually works.

# File lib/riak/client/beefcake/socket.rb, line 213
def validate_connection
  write_message :PingReq
  expect_message :PingResp
end
validate_session() click to toggle source

Validate the TLS session

# File lib/riak/client/beefcake/socket.rb, line 167
def validate_session
  if @auth[:verify_hostname] &&
      !OpenSSL::SSL::verify_certificate_identity(riak_cert, @host)
    raise TlsError::CertHostMismatchError.new
  end

  unless (riak_cert.not_before..riak_cert.not_after).cover? Time.now
    raise TlsError::CertNotValidError.new
  end

  validator = CertValidator.new riak_cert, ca_cert

  validator.crl = try_load @auth[:crl_file] if @auth[:crl_file]

  if @auth[:crl]
    raise TlsError::CertRevokedError.new unless validator.crl_valid?
  end

  if @auth[:ocsp]
    raise TlsError::CertRevokedError.new unless validator.ocsp_valid?
  end
end
validator_options() click to toggle source
# File lib/riak/client/beefcake/socket.rb, line 190
def validator_options
  o = {
    ocsp: !!@auth[:ocsp],
    crl: !!@auth[:crl]
  }

  if @auth[:crl_file]
    o[:crl_file] = @auth[:crl_file]
    o[:crl] = true
  end

  return o
end
write_message(code, message = '') click to toggle source

Write a protocol buffers message to whatever the current socket is.

# File lib/riak/client/beefcake/socket.rb, line 220
def write_message(code, message = '')
  if code.is_a? Symbol
    code = BeefcakeMessageCodes.index code
  end

  header = [message.length+1, code].pack 'NC'
  @sock.write header + message
end