class HrrRbSsh::Transport

Attributes

i_c[R]
i_s[R]
incoming_compression_algorithm[R]
incoming_encryption_algorithm[R]
incoming_mac_algorithm[R]
incoming_sequence_number[R]
io[R]
mode[R]
outgoing_compression_algorithm[R]
outgoing_encryption_algorithm[R]
outgoing_mac_algorithm[R]
outgoing_sequence_number[R]
preferred_compression_algorithms[R]
preferred_encryption_algorithms[R]
preferred_kex_algorithms[R]
preferred_mac_algorithms[R]
preferred_server_host_key_algorithms[R]
server_host_key_algorithm[R]
session_id[R]
supported_compression_algorithms[R]
supported_encryption_algorithms[R]
supported_kex_algorithms[R]
supported_mac_algorithms[R]
supported_server_host_key_algorithms[R]
v_c[R]
v_s[R]

Public Class Methods

new(io, mode, options={}) click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 54
def initialize io, mode, options={}, logger: nil
  self.logger = logger

  @io = io
  @mode = mode
  @options = options

  @closed = nil

  @in_kex = false

  @sender   = Sender.new logger: logger
  @receiver = Receiver.new logger: logger

  @sender_monitor   = Monitor.new
  @receiver_monitor = Monitor.new

  @local_version  = @options.delete('local_version') || "SSH-2.0-HrrRbSsh-#{VERSION}".force_encoding(Encoding::ASCII_8BIT)
  @remote_version = "".force_encoding(Encoding::ASCII_8BIT)

  @incoming_sequence_number = SequenceNumber.new
  @outgoing_sequence_number = SequenceNumber.new

  @acceptable_services = Array.new

  update_supported_algorithms
  update_preferred_algorithms
  initialize_local_algorithms
  initialize_algorithms
end

Public Instance Methods

check_if_preferred_algorithms_are_supported() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 266
def check_if_preferred_algorithms_are_supported
  [
    ['kex',             @preferred_kex_algorithms,             @supported_kex_algorithms            ],
    ['server host key', @preferred_server_host_key_algorithms, @supported_server_host_key_algorithms],
    ['encryption',      @preferred_encryption_algorithms,      @supported_encryption_algorithms     ],
    ['mac',             @preferred_mac_algorithms,             @supported_mac_algorithms            ],
    ['compression',     @preferred_compression_algorithms,     @supported_compression_algorithms    ],
  ].each{ |algorithm_name, list_preferred, list_supported|
    list_preferred.each{ |a|
      unless list_supported.include? a
        raise ArgumentError, "#{algorithm_name} algorithm #{a} is not supported"
      end
    }
  }
end
close() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 181
def close
  @sender_monitor.synchronize do
    return if @closed
    log_info { "close transport" }
    begin
      disconnect
      @incoming_compression_algorithm.close
      @outgoing_compression_algorithm.close
    rescue => e
      log_error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
    ensure
      @closed = true
      log_info { "transport closed" }
    end
  end
end
closed?() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 198
def closed?
  @closed
end
disconnect() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 202
def disconnect
  log_info { "disconnect transport" }
  send_disconnect
  log_info { "transport disconnected" }
end
exchange_key(payload=nil) click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 214
def exchange_key payload=nil
  @in_kex = true
  @sender_monitor.synchronize do
    @receiver_monitor.synchronize do
      send_kexinit
      if payload
        receive_kexinit payload
      else
        receive_kexinit receive
      end
      update_kex_and_server_host_key_algorithms
      start_kex_algorithm
      send_newkeys
      receive_newkeys receive
      update_encryption_mac_compression_algorithms
    end
  end
  @in_kex = false
end
exchange_version() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 208
def exchange_version
  send_version
  receive_version
  update_version_strings
end
initialize_algorithms() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 293
def initialize_algorithms
  @incoming_encryption_algorithm  = EncryptionAlgorithm['none'].new
  @incoming_mac_algorithm         = MacAlgorithm['none'].new
  @incoming_compression_algorithm = CompressionAlgorithm['none'].new

  @outgoing_encryption_algorithm  = EncryptionAlgorithm['none'].new
  @outgoing_mac_algorithm         = MacAlgorithm['none'].new
  @outgoing_compression_algorithm = CompressionAlgorithm['none'].new
end
initialize_local_algorithms() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 282
def initialize_local_algorithms
  @local_kex_algorithms                          = @preferred_kex_algorithms
  @local_server_host_key_algorithms              = @preferred_server_host_key_algorithms
  @local_encryption_algorithms_client_to_server  = @preferred_encryption_algorithms
  @local_encryption_algorithms_server_to_client  = @preferred_encryption_algorithms
  @local_mac_algorithms_client_to_server         = @preferred_mac_algorithms
  @local_mac_algorithms_server_to_client         = @preferred_mac_algorithms
  @local_compression_algorithms_client_to_server = @preferred_compression_algorithms
  @local_compression_algorithms_server_to_client = @preferred_compression_algorithms
end
receive() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 106
def receive
  raise Error::ClosedTransport if @closed
  @receiver_monitor.synchronize do
    begin
      payload = @receiver.receive self
      case payload[0,1].unpack("C")[0]
      when Message::SSH_MSG_DISCONNECT::VALUE
        log_info { "received disconnect message" }
        message = Message::SSH_MSG_DISCONNECT.new(logger: logger).decode payload
        close
        raise Error::ClosedTransport
      when Message::SSH_MSG_IGNORE::VALUE
        log_info { "received ignore message" }
        message = Message::SSH_MSG_IGNORE.new(logger: logger).decode payload
        receive
      when Message::SSH_MSG_UNIMPLEMENTED::VALUE
        log_info { "received unimplemented message" }
        message = Message::SSH_MSG_UNIMPLEMENTED.new(logger: logger).decode payload
        receive
      when Message::SSH_MSG_DEBUG::VALUE
        log_info { "received debug message" }
        message = Message::SSH_MSG_DEBUG.new(logger: logger).decode payload
        receive
      when Message::SSH_MSG_KEXINIT::VALUE
        log_info { "received kexinit message" }
        if @in_kex
          payload
        else
          exchange_key payload
          receive
        end
      else
        payload
      end
    rescue Error::ClosedTransport
      raise
    rescue EOFError, IOError, SystemCallError => e
      log_info { "#{e.message} (#{e.class})" }
      close
      raise Error::ClosedTransport
    rescue => e
      log_error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
      close
      raise Error::ClosedTransport
    end
  end
end
receive_kexinit(payload) click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 383
def receive_kexinit payload
  case @mode
  when Mode::SERVER
    @i_c = payload
  when Mode::CLIENT
    @i_s = payload
  end
  message = Message::SSH_MSG_KEXINIT.new(logger: logger).decode payload
  update_remote_algorithms message
end
receive_newkeys(payload) click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 402
def receive_newkeys payload
  message = Message::SSH_MSG_NEWKEYS.new(logger: logger).decode payload
end
receive_service_request() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 418
def receive_service_request
  payload = @receiver.receive self
  message = Message::SSH_MSG_SERVICE_REQUEST.new(logger: logger).decode payload
end
receive_version() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 307
def receive_version
  str_io = StringIO.new
  loop do
    str_io.write @io.read(1)
    if str_io.string[-2..-1] == "#{CR}#{LF}"
      if str_io.string[0..3] == "SSH-"
        @remote_version = str_io.string[0..-3]
        log_info { "received remote version string: #{@remote_version}" }
        break
      else
        log_info { "received message before remote version string: #{str_io.string}" }
        str_io.rewind
        str_io.truncate(0)
      end
    end
  end
end
register_acceptable_service(service_name) click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 85
def register_acceptable_service service_name
  @acceptable_services.push service_name
end
send(payload) click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 89
def send payload
  raise Error::ClosedTransport if @closed
  @sender_monitor.synchronize do
    begin
      @sender.send self, payload
    rescue IOError, SystemCallError => e
      log_info { "#{e.message} (#{e.class})" }
      close
      raise Error::ClosedTransport
    rescue => e
      log_error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
      close
      raise Error::ClosedTransport
    end
  end
end
send_disconnect() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 336
def send_disconnect
  message = {
    :'message number' => Message::SSH_MSG_DISCONNECT::VALUE,
    :'reason code'    => Message::SSH_MSG_DISCONNECT::ReasonCode::SSH_DISCONNECT_BY_APPLICATION,
    :'description'    => "disconnected by user",
    :'language tag'   => ""
  }
  payload = Message::SSH_MSG_DISCONNECT.new(logger: logger).encode message
  @sender_monitor.synchronize do
    begin
      @sender.send self, payload
    rescue IOError, SystemCallError => e
      log_info { "#{e.message} (#{e.class})" }
    rescue => e
      log_error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
    end
  end
end
send_kexinit() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 355
def send_kexinit
  message = {
    :'message number'                          => Message::SSH_MSG_KEXINIT::VALUE,
    :'cookie (random byte)'                    => lambda { rand(0x01_00) },
    :'kex_algorithms'                          => @local_kex_algorithms,
    :'server_host_key_algorithms'              => @local_server_host_key_algorithms,
    :'encryption_algorithms_client_to_server'  => @local_encryption_algorithms_client_to_server,
    :'encryption_algorithms_server_to_client'  => @local_encryption_algorithms_server_to_client,
    :'mac_algorithms_client_to_server'         => @local_mac_algorithms_client_to_server,
    :'mac_algorithms_server_to_client'         => @local_mac_algorithms_server_to_client,
    :'compression_algorithms_client_to_server' => @local_compression_algorithms_client_to_server,
    :'compression_algorithms_server_to_client' => @local_compression_algorithms_server_to_client,
    :'languages_client_to_server'              => [],
    :'languages_server_to_client'              => [],
    :'first_kex_packet_follows'                => false,
    :'0 (reserved for future extension)'       => 0,
  }
  payload = Message::SSH_MSG_KEXINIT.new(logger: logger).encode message
  send payload

  case @mode
  when Mode::SERVER
    @i_s = payload
  when Mode::CLIENT
    @i_c = payload
  end
end
send_newkeys() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 394
def send_newkeys
    message = {
      :'message number' => Message::SSH_MSG_NEWKEYS::VALUE,
    }
    payload = Message::SSH_MSG_NEWKEYS.new(logger: logger).encode message
    send payload
end
send_service_accept(service_name) click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 423
def send_service_accept service_name
  message = {
    :'message number' => Message::SSH_MSG_SERVICE_ACCEPT::VALUE,
    :'service name'   => service_name,
  }
  payload = Message::SSH_MSG_SERVICE_ACCEPT.new(logger: logger).encode message
  send payload
end
send_service_request() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 406
def send_service_request
  message = {
    :'message number' => Message::SSH_MSG_SERVICE_REQUEST::VALUE,
    :'service name' => 'ssh-userauth',
  }
  payload = Message::SSH_MSG_SERVICE_REQUEST.new(logger: logger).encode message
  send payload

  payload = @receiver.receive self
  message = Message::SSH_MSG_SERVICE_ACCEPT.new(logger: logger).decode payload
end
send_version() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 303
def send_version
  @io.write (@local_version + CR + LF)
end
start() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 154
def start
  log_info { "start transport" }
  begin
    exchange_version
    exchange_key
    case @mode
    when Mode::SERVER
      verify_service_request
    when Mode::CLIENT
      send_service_request
    end
    @closed = false
  rescue Error::ClosedTransport
    raise
  rescue EOFError, IOError, SystemCallError => e
    log_info { "#{e.message} (#{e.class})" }
    close
    raise Error::ClosedTransport
  rescue => e
    log_error { [e.backtrace[0], ": ", e.message, " (", e.class.to_s, ")\n\t", e.backtrace[1..-1].join("\n\t")].join }
    close
    raise Error::ClosedTransport
  else
    log_info { "transport started" }
  end
end
start_kex_algorithm() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 234
def start_kex_algorithm
  @kex_algorithm.start self
end
update_compression_algorithm() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 511
def update_compression_algorithm
  case @mode
  when Mode::SERVER
    compression_algorithm_c_to_s_name = @remote_compression_algorithms_client_to_server.find{ |a| @local_compression_algorithms_client_to_server.include? a } or raise
    compression_algorithm_s_to_c_name = @remote_compression_algorithms_server_to_client.find{ |a| @local_compression_algorithms_server_to_client.include? a } or raise
    incoming_compression_algorithm_name = compression_algorithm_c_to_s_name
    outgoing_compression_algorithm_name = compression_algorithm_s_to_c_name
  when Mode::CLIENT
    compression_algorithm_s_to_c_name = @local_compression_algorithms_server_to_client.find{ |a| @remote_compression_algorithms_server_to_client.include? a } or raise
    compression_algorithm_c_to_s_name = @local_compression_algorithms_client_to_server.find{ |a| @remote_compression_algorithms_client_to_server.include? a } or raise
    incoming_compression_algorithm_name = compression_algorithm_s_to_c_name
    outgoing_compression_algorithm_name = compression_algorithm_c_to_s_name
  end
  @incoming_compression_algorithm.close
  @outgoing_compression_algorithm.close
  @incoming_compression_algorithm = CompressionAlgorithm[incoming_compression_algorithm_name].new Direction::INCOMING
  @outgoing_compression_algorithm = CompressionAlgorithm[outgoing_compression_algorithm_name].new Direction::OUTGOING
end
update_encryption_algorithm() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 465
def update_encryption_algorithm
  case @mode
  when Mode::SERVER
    encryption_algorithm_c_to_s_name = @remote_encryption_algorithms_client_to_server.find{ |a| @local_encryption_algorithms_client_to_server.include? a } or raise
    encryption_algorithm_s_to_c_name = @remote_encryption_algorithms_server_to_client.find{ |a| @local_encryption_algorithms_server_to_client.include? a } or raise
    incoming_encryption_algorithm_name = encryption_algorithm_c_to_s_name
    outgoing_encryption_algorithm_name = encryption_algorithm_s_to_c_name
    incoming_crpt_iv = @kex_algorithm.iv_c_to_s self, incoming_encryption_algorithm_name
    outgoing_crpt_iv = @kex_algorithm.iv_s_to_c self, outgoing_encryption_algorithm_name
    incoming_crpt_key = @kex_algorithm.key_c_to_s self, incoming_encryption_algorithm_name
    outgoing_crpt_key = @kex_algorithm.key_s_to_c self, outgoing_encryption_algorithm_name
  when Mode::CLIENT
    encryption_algorithm_s_to_c_name = @local_encryption_algorithms_server_to_client.find{ |a| @remote_encryption_algorithms_server_to_client.include? a } or raise
    encryption_algorithm_c_to_s_name = @local_encryption_algorithms_client_to_server.find{ |a| @remote_encryption_algorithms_client_to_server.include? a } or raise
    incoming_encryption_algorithm_name = encryption_algorithm_s_to_c_name
    outgoing_encryption_algorithm_name = encryption_algorithm_c_to_s_name
    incoming_crpt_iv = @kex_algorithm.iv_s_to_c self, incoming_encryption_algorithm_name
    outgoing_crpt_iv = @kex_algorithm.iv_c_to_s self, outgoing_encryption_algorithm_name
    incoming_crpt_key = @kex_algorithm.key_s_to_c self, incoming_encryption_algorithm_name
    outgoing_crpt_key = @kex_algorithm.key_c_to_s self, outgoing_encryption_algorithm_name
  end
  @incoming_encryption_algorithm = EncryptionAlgorithm[incoming_encryption_algorithm_name].new Direction::INCOMING, incoming_crpt_iv, incoming_crpt_key
  @outgoing_encryption_algorithm = EncryptionAlgorithm[outgoing_encryption_algorithm_name].new Direction::OUTGOING, outgoing_crpt_iv, outgoing_crpt_key
end
update_encryption_mac_compression_algorithms() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 458
def update_encryption_mac_compression_algorithms
  @session_id ||= @kex_algorithm.hash(self)
  update_encryption_algorithm
  update_mac_algorithm
  update_compression_algorithm
end
update_kex_and_server_host_key_algorithms() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 443
def update_kex_and_server_host_key_algorithms
  case @mode
  when Mode::SERVER
    kex_algorithm_name             = @remote_kex_algorithms.find{ |a| @local_kex_algorithms.include? a } or raise
    server_host_key_algorithm_name = @remote_server_host_key_algorithms.find{ |a| @local_server_host_key_algorithms.include? a } or raise
    server_secret_host_key         = @options.fetch('transport_server_secret_host_keys', {}).fetch(server_host_key_algorithm_name, nil)
  when Mode::CLIENT
    kex_algorithm_name             = @local_kex_algorithms.find{ |a| @remote_kex_algorithms.include? a } or raise
    server_host_key_algorithm_name = @local_server_host_key_algorithms.find{ |a| @remote_server_host_key_algorithms.include? a } or raise
    server_secret_host_key         = nil
  end
  @server_host_key_algorithm = ServerHostKeyAlgorithm[server_host_key_algorithm_name].new server_secret_host_key
  @kex_algorithm             = KexAlgorithm[kex_algorithm_name].new
end
update_mac_algorithm() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 490
def update_mac_algorithm
  case @mode
  when Mode::SERVER
    mac_algorithm_c_to_s_name = @remote_mac_algorithms_client_to_server.find{ |a| @local_mac_algorithms_client_to_server.include? a } or raise
    mac_algorithm_s_to_c_name = @remote_mac_algorithms_server_to_client.find{ |a| @local_mac_algorithms_server_to_client.include? a } or raise
    incoming_mac_algorithm_name = mac_algorithm_c_to_s_name
    outgoing_mac_algorithm_name = mac_algorithm_s_to_c_name
    incoming_mac_key = @kex_algorithm.mac_c_to_s self, incoming_mac_algorithm_name
    outgoing_mac_key = @kex_algorithm.mac_s_to_c self, outgoing_mac_algorithm_name
  when Mode::CLIENT
    mac_algorithm_s_to_c_name = @local_mac_algorithms_server_to_client.find{ |a| @remote_mac_algorithms_server_to_client.include? a } or raise
    mac_algorithm_c_to_s_name = @local_mac_algorithms_client_to_server.find{ |a| @remote_mac_algorithms_client_to_server.include? a } or raise
    incoming_mac_algorithm_name = mac_algorithm_s_to_c_name
    outgoing_mac_algorithm_name = mac_algorithm_c_to_s_name
    incoming_mac_key = @kex_algorithm.mac_s_to_c self, incoming_mac_algorithm_name
    outgoing_mac_key = @kex_algorithm.mac_c_to_s self, outgoing_mac_algorithm_name
  end
  @incoming_mac_algorithm = MacAlgorithm[incoming_mac_algorithm_name].new incoming_mac_key
  @outgoing_mac_algorithm = MacAlgorithm[outgoing_mac_algorithm_name].new outgoing_mac_key
end
update_preferred_algorithms() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 256
def update_preferred_algorithms
  @preferred_kex_algorithms             = @options['transport_preferred_kex_algorithms']             || KexAlgorithm.list_preferred
  @preferred_server_host_key_algorithms = @options['transport_preferred_server_host_key_algorithms'] || ServerHostKeyAlgorithm.list_preferred
  @preferred_encryption_algorithms      = @options['transport_preferred_encryption_algorithms']      || EncryptionAlgorithm.list_preferred
  @preferred_mac_algorithms             = @options['transport_preferred_mac_algorithms']             || MacAlgorithm.list_preferred
  @preferred_compression_algorithms     = @options['transport_preferred_compression_algorithms']     || CompressionAlgorithm.list_preferred

  check_if_preferred_algorithms_are_supported
end
update_remote_algorithms(message) click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 432
def update_remote_algorithms message
  @remote_kex_algorithms                          = message[:'kex_algorithms']
  @remote_server_host_key_algorithms              = message[:'server_host_key_algorithms']
  @remote_encryption_algorithms_client_to_server  = message[:'encryption_algorithms_client_to_server']
  @remote_encryption_algorithms_server_to_client  = message[:'encryption_algorithms_server_to_client']
  @remote_mac_algorithms_client_to_server         = message[:'mac_algorithms_client_to_server']
  @remote_mac_algorithms_server_to_client         = message[:'mac_algorithms_server_to_client']
  @remote_compression_algorithms_client_to_server = message[:'compression_algorithms_client_to_server']
  @remote_compression_algorithms_server_to_client = message[:'compression_algorithms_server_to_client']
end
update_supported_algorithms() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 248
def update_supported_algorithms
  @supported_kex_algorithms             = KexAlgorithm.list_supported
  @supported_server_host_key_algorithms = ServerHostKeyAlgorithm.list_supported
  @supported_encryption_algorithms      = EncryptionAlgorithm.list_supported
  @supported_mac_algorithms             = MacAlgorithm.list_supported
  @supported_compression_algorithms     = CompressionAlgorithm.list_supported
end
update_version_strings() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 325
def update_version_strings
  case @mode
  when Mode::SERVER
    @v_c = @remote_version
    @v_s = @local_version
  when Mode::CLIENT
    @v_c = @local_version
    @v_s = @remote_version
  end
end
verify_service_request() click to toggle source
# File lib/hrr_rb_ssh/transport.rb, line 238
def verify_service_request
  service_request_message = receive_service_request
  service_name = service_request_message[:'service name']
  if @acceptable_services.include? service_name
    send_service_accept service_name
  else
    close
  end
end