From 75c94a194fb83d6109627e96535d5407f3ee96ec Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sun, 29 Mar 2015 17:28:44 -0700 Subject: [PATCH] Stricter hostname verification following RFC 6125 --- lib/openssl/ssl.rb | 43 +++++++++++++++++++++++++++++++++++++++---- test/test_ssl.rb | 16 ++++++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/lib/openssl/ssl.rb b/lib/openssl/ssl.rb index ff1d4efb3..bbf406746 100644 --- a/lib/openssl/ssl.rb +++ b/lib/openssl/ssl.rb @@ -143,8 +143,7 @@ def verify_certificate_identity(cert, hostname) 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 + return true if verify_hostname(hostname, san.value) when 7 # iPAddress in GeneralName (RFC5280) should_verify_common_name = false # follows GENERAL_NAME_print() in x509v3/v3_alt.c @@ -159,8 +158,7 @@ def verify_certificate_identity(cert, hostname) 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 + return true if verify_hostname(hostname, value) end } end @@ -168,6 +166,43 @@ def verify_certificate_identity(cert, hostname) end module_function :verify_certificate_identity + def verify_hostname(hostname, san) # :nodoc: + san_parts = san.split(".") + + # TODO: this behavior should probably be more strict + return san == hostname if san_parts.size < 2 + + host_parts = hostname.split(".") + return false unless san_parts.size == host_parts.size + + # RFC 6125, section 6.4.3, subitem 1. + # The client SHOULD NOT attempt to match a presented identifier in + # which the wildcard character comprises a label other than the + # left-most label (e.g., do not match bar.*.example.net). + return false unless verify_wildcard(host_parts.shift, san_parts.shift) + + san_parts.join(".") == host_parts.join(".") + end + module_function :verify_hostname + + def verify_wildcard(domain_component, san_component) # :nodoc: + parts = san_component.split("*", -1) + + return false if parts.size > 2 + return san_component == domain_component if parts.size == 1 + + # RFC 6125, section 6.4.3, subitem 3. + # The client SHOULD NOT attempt to match a presented identifier + # where the wildcard character is embedded within an A-label or + # U-label of an internationalized domain name. + return false if domain_component.start_with?("xn--") && (!parts[0].empty? || !parts[1].empty?) + + parts[0].length + parts[1].length < domain_component.length && + domain_component.start_with?(parts[0]) && + domain_component.end_with?(parts[1]) + end + module_function :verify_wildcard + class SSLSocket include Buffering include SocketForwarder diff --git a/test/test_ssl.rb b/test/test_ssl.rb index b65399f97..0b49ab561 100644 --- a/test/test_ssl.rb +++ b/test/test_ssl.rb @@ -426,6 +426,22 @@ def test_verify_certificate_identity end end + def test_verify_hostname + assert_equal(true, OpenSSL::SSL.verify_hostname("www.example.com", "*.example.com")) + assert_equal(false, OpenSSL::SSL.verify_hostname("www.subdomain.example.com", "*.example.com")) + end + + def test_verify_wildcard + assert_equal(false, OpenSSL::SSL.verify_wildcard("foo", "x*")) + assert_equal(true, OpenSSL::SSL.verify_wildcard("foo", "foo")) + assert_equal(true, OpenSSL::SSL.verify_wildcard("foo", "f*")) + assert_equal(true, OpenSSL::SSL.verify_wildcard("foo", "*")) + assert_equal(false, OpenSSL::SSL.verify_wildcard("abc*bcd", "abcd")) + assert_equal(false, OpenSSL::SSL.verify_wildcard("xn--qdk4b9b", "x*")) + assert_equal(false, OpenSSL::SSL.verify_wildcard("xn--qdk4b9b", "*--qdk4b9b") + assert_equal(true, OpenSSL::SSL.verify_wildcard("xn--qdk4b9b", "xn--qdk4b9b")) + end + # Create NULL byte SAN certificate def create_null_byte_SAN_certificate(critical = false) ef = OpenSSL::X509::ExtensionFactory.new