Project

General

Profile

Feature #12558

Updated by rhenium (Kazuki Yamaguchi) almost 4 years ago

In server keystore file, it specifies Owner like Owner: CN=default.server. When used SSL connection to connect to the server, it reported a error #<OpenSSL::SSL::SSLError: hostname "xxx.xx.xxx.xxx" does not match the server certificate>. I checked the ruby code and found that it seemed Ruby did not support this scenario when an Owner is specified in keystore file and its value is not the server real hostname or IP address.  

 First, lets have a look at the error message and call stacks 

 ~~~ 
 (Time: 2016-07-06 09:33:51 +0800)(PID: 30558)(LVL: ERROR) -: Failed to POST data due to error: #<OpenSSL::SSL::SSLError: hostname "xxx.xx.xxx.xxx" does not match the server certificate> 
 (Time: 2016-07-06 09:33:51 +0800)(PID: 30558)(LVL: ERROR) -: Trace: ["/usr/local/ruby/lib/ruby/site_ruby/2.1.0/openssl/ssl.rb:139:in `post_connection_check'", "/usr/local/ruby/lib/ruby/2.1.0/net/http.rb:922:in `connect'", "/usr/local/ruby/lib/ruby/2.1.0/net/http.rb:863  
 ~~~ 

  

 From code /usr/local/ruby/lib/ruby/2.1.0/net/http.rb:922 
 it calls s.post_connection_check(@address) function which raise the error I mentioned. This function takes @address as the parameters which is passed by initialize function as below. From the description of the initialize function, the address should be a DNS hostname or IP address. 

     # Creates a new Net::HTTP object for the specified server address, 
     # without opening the TCP connection or initializing the HTTP session. 
     # The +address+ should be a DNS hostname or IP address. 
     def initialize(address, port = nil) 
       @address = address 


 /usr/local/ruby/lib/ruby/2.1.0/net/http.rb:922 

       
       if use_ssl? 
         begin 
           if proxy? 
             buf = "CONNECT #{@address}:#{@port} HTTP/#{HTTPVersion}\r\n" 
             buf << "Host: #{@address}:#{@port}\r\n" 
             if proxy_user 
               credential = ["#{proxy_user}:#{proxy_pass}"].pack('m') 
               credential.delete!("\r\n") 
               buf << "Proxy-Authorization: Basic #{credential}\r\n" 
             end 
             buf << "\r\n" 
             @socket.write(buf) 
             HTTPResponse.read_new(@socket).value 
           end 
           s.session = @ssl_session if @ssl_session 
           # Server Name Indication (SNI) RFC 3546 
           s.hostname = @address if s.respond_to? :hostname= 
           Timeout.timeout(@open_timeout, Net::OpenTimeout) { s.connect } 
           if @ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE 
             s.post_connection_check(@address) 
           end 
           @ssl_session = s.session 
         rescue => exception 
           D "Conn close because of connect error #{exception}" 
           @socket.close if @socket and not @socket.closed? 
           raise exception 
         end 
       end 
       on_connect 
     end 

 Then take a look at s.post_connection_check(@address) function in /usr/local/ruby/lib/ruby/site_ruby/2.1.0/openssl/ssl.rb, 

       def post_connection_check(hostname) 
         unless OpenSSL::SSL.verify_certificate_identity(peer_cert, hostname) 
           raise SSLError, "hostname \"#{hostname}\" does not match the server certificate" 
         end 
         return true 
       end 


 it calls verify_certificate_identity(peer_cert, hostname) as below: 

     
     def verify_certificate_identity(cert, hostname) 
       should_verify_common_name = true 
       cert.extensions.each{|ext| 
         next if ext.oid != "subjectAltName" 
         ostr = OpenSSL::ASN1.decode(ext.to_der).value.last 
         sequence = OpenSSL::ASN1.decode(ostr.value) 
         sequence.value.each{|san| 
           case san.tag 
           when 2 # dNSName in GeneralName (RFC5280) 
             should_verify_common_name = false 
             reg = Regexp.escape(san.value).gsub(/\\\*/, "[^.]+") 
             return true if /\A#{reg}\z/i =~ hostname 
           when 7 # iPAddress in GeneralName (RFC5280) 
             should_verify_common_name = false 
             # follows GENERAL_NAME_print() in x509v3/v3_alt.c 
             if san.value.size == 4 
               return true if san.value.unpack('C*').join('.') == hostname 
             elsif san.value.size == 16 
               return true if san.value.unpack('n*').map { |e| sprintf("%X", e) }.join(':') == hostname 
             end 
           end 
         } 
       } 
       if should_verify_common_name 
         cert.subject.to_a.each{|oid, value| 
           if oid == "CN" 
             reg = Regexp.escape(value).gsub(/\\\*/, "[^.]+") 
             return true if /\A#{reg}\z/i =~ hostname 
           end 
         } 
       end 
       return false 
     end 

 As you can see from above code, there is options that can set a alternative server hostname so that SSL verification can pass.

Back