Class: HTTPI::Adapter::OpensslGost

Inherits:
Base
  • Object
show all
Defined in:
lib/httpi/adapter/openssl_gost.rb,
lib/httpi/adapter/openssl_gost/version.rb

Constant Summary collapse

VERSION =
'0.0.1'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(request) ⇒ OpensslGost

Returns a new instance of OpensslGost.



11
12
13
14
15
# File 'lib/httpi/adapter/openssl_gost.rb', line 11

def initialize(request)
  @request = request
  @pubkey_path  = request.auth.ssl.cert_file
  @privkey_path = request.auth.ssl.cert_key_file
end

Instance Attribute Details

#clientObject (readonly)

Returns the value of attribute client.



17
18
19
# File 'lib/httpi/adapter/openssl_gost.rb', line 17

def client
  @client
end

#privkey_pathObject

Returns the value of attribute privkey_path.



19
20
21
# File 'lib/httpi/adapter/openssl_gost.rb', line 19

def privkey_path
  @privkey_path
end

#pubkey_pathObject

Returns the value of attribute pubkey_path.



18
19
20
# File 'lib/httpi/adapter/openssl_gost.rb', line 18

def pubkey_path
  @pubkey_path
end

Instance Method Details

#request(method) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/httpi/adapter/openssl_gost.rb', line 21

def request(method)
  uri = @request.url
  cmd = "openssl s_client -engine gost -connect '#{uri.host}:#{uri.port}' -quiet"
  cmd += " -cert '#{pubkey_path}'"  if pubkey_path
  cmd += " -key '#{privkey_path}'"  if privkey_path

  # Prepare request
  req = "#{method.upcase} #{uri.request_uri} HTTP/1.1\r\n"
  headers = @request.headers.map{|k,v| "#{k}: #{v}\r\n" }.join
  # Set up Content-Length header if body present (HTTPI doesn't it for us)
  if @request.body and !@request.headers['Content-Length']
    headers += "Content-Length: #{@request.body.bytesize}\r\n"
  end
  # Add hostname header and explicitly close connection (we need command to exit immediately)
  headers += "Host: #{uri.host}\r\nConnection: close\r\n\r\n"
  req += headers
  req += "#{@request.body}\r\n\r\n"  if @request.body

  # Send request, get answer
  HTTPI.logger.debug "Connecting to server with command: #{cmd}"
  HTTPI.logger.debug "Sending request:\r\n#{req}"
  retries = 0
  begin
    raw_response, openssl_stderr, status = Open3.capture3(cmd, stdin_data: req, binmode: true)
  rescue Errno::EPIPE # Sometimes fails with no reason
    retry if retries+=1 < 3
  end

  # Check whether command finished correctly and prepare response
  if status.success?
    HTTPI.logger.debug "Received response:\r\n#{raw_response}"
    status_string, headers_and_body = raw_response.split("\r\n", 2)
    response_headers, response_body = headers_and_body.split("\r\n\r\n", 2)
    response_code = status_string.scan(/\d{3}/).first
    response_headers = Hash[response_headers.split("\r\n").map{|h| h.split(':', 2).map(&:strip) }]
    HTTPI::Response.new(response_code, response_headers, response_body)
  else
    HTTPI.logger.fatal "While connecting to server #{uri.host} with command: #{cmd}"
    HTTPI.logger.fatal "Command returned:\r\n#{status.inspect}"
    HTTPI.logger.fatal "STDERR is:\n#{openssl_stderr}"
    # OpenSSL's s_client always return 1 on fail, try to catch most common errors
    case openssl_stderr
      when /connect:errno=60/ then raise HTTPI::TimeoutError
      when /connect:errno=61/ then raise (HTTPI::Error.new).extend(HTTPI::ConnectionError) # Connection refused
      when /connect:errno=2/  then raise (HTTPI::Error.new).extend(HTTPI::ConnectionError) # No DNS name found
      when /ssl handshake failure/          then raise HTTPI::SSLError, 'Seems like you trying to connect to HTTP, not HTTPS'
      when /missing dsa signing cert/       then raise HTTPI::SSLError, 'Probably your OpenSSL lacks GOST configuration'
      when /unable to load certificate/     then raise HTTPI::SSLError, 'Can not load client certificate, check file path and access rights'
      when /unable to load .*? private key/ then raise HTTPI::SSLError, 'Can not load client certificate private key, check file path and access rights'
      else raise HTTPI::Error
    end
  end

end