Skip to content

Commit 4d78c3c

Browse files
karesclaude
andcommitted
[fix] normalize IP address in SAN verify to match CRuby
Use IPAddr for comparison instead of plain string equality, so that equivalent IPv6 representations (e.g. ::1 vs 0:0:0:0:0:0:0:1) match correctly. Ported IPv4/IPv6 tests from CRuby's test_ssl.rb. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5898ec1 commit 4d78c3c

2 files changed

Lines changed: 44 additions & 9 deletions

File tree

lib/openssl/ssl.rb

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
require "openssl/buffering"
1414
require "io/nonblock"
15+
require "ipaddr"
1516
require "socket"
1617

1718
module OpenSSL
@@ -319,15 +320,10 @@ def verify_certificate_identity(cert, hostname)
319320
#when 7 # iPAddress in GeneralName (RFC5280)
320321
elsif /\AIP(?: Address)?:(.*)/ =~ general_name
321322
should_verify_common_name = false
322-
return true if $1 == hostname
323-
# NOTE: bellow logic makes little sense JRuby reads exts differently
324-
# follows GENERAL_NAME_print() in x509v3/v3_alt.c
325-
# if san.value.size == 4 || san.value.size == 16
326-
# begin
327-
# return true if $1 == IPAddr.new(hostname).to_s
328-
# rescue IPAddr::InvalidAddressError
329-
# end
330-
# end
323+
begin
324+
return true if IPAddr.new($1) == IPAddr.new(hostname)
325+
rescue IPAddr::InvalidAddressError
326+
end
331327
end
332328
}
333329
}

src/test/ruby/ssl/test_ssl.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,45 @@ def test_post_connection_check
8080
end
8181
end
8282

83+
# ported from CRuby's test/openssl/test_ssl.rb
84+
def test_verify_certificate_identity_ipv4
85+
cert = create_cert_with_san("IP:192.168.7.1")
86+
assert_equal true, OpenSSL::SSL.verify_certificate_identity(cert, "192.168.7.1")
87+
assert_equal false, OpenSSL::SSL.verify_certificate_identity(cert, "192.168.7.255")
88+
assert_equal false, OpenSSL::SSL.verify_certificate_identity(cert, "192.168.7.2")
89+
end
90+
91+
# ported from CRuby's test/openssl/test_ssl.rb
92+
def test_verify_certificate_identity_ipv6
93+
cert = create_cert_with_san("IP:13::17")
94+
assert_equal true, OpenSSL::SSL.verify_certificate_identity(cert, "13::17")
95+
assert_equal false, OpenSSL::SSL.verify_certificate_identity(cert, "13::18")
96+
# expanded form
97+
assert_equal true, OpenSSL::SSL.verify_certificate_identity(cert, "13:0:0:0:0:0:0:17")
98+
assert_equal false, OpenSSL::SSL.verify_certificate_identity(cert, "44:0:0:0:0:0:0:17")
99+
# fully expanded with leading zeros
100+
assert_equal true, OpenSSL::SSL.verify_certificate_identity(cert, "0013:0000:0000:0000:0000:0000:0000:0017")
101+
assert_equal false, OpenSSL::SSL.verify_certificate_identity(cert, "1313:0000:0000:0000:0000:0000:0000:0017")
102+
end
103+
104+
def test_verify_certificate_identity_dns_no_ip_match
105+
cert = create_cert_with_san("DNS:example.com")
106+
assert_equal true, OpenSSL::SSL.verify_certificate_identity(cert, "example.com")
107+
assert_equal false, OpenSSL::SSL.verify_certificate_identity(cert, "192.168.7.1")
108+
end
109+
110+
private
111+
112+
def create_cert_with_san(san)
113+
ef = OpenSSL::X509::ExtensionFactory.new
114+
cert = OpenSSL::X509::Certificate.new
115+
cert.subject = OpenSSL::X509::Name.parse("/DC=some/DC=site/CN=Some Site")
116+
cert.add_extension ef.create_ext("subjectAltName", san)
117+
cert
118+
end
119+
120+
public
121+
83122
def test_post_connect_check_with_anon_ciphers
84123
unless OpenSSL::ExtConfig::TLS_DH_anon_WITH_AES_256_GCM_SHA384
85124
return skip('OpenSSL::ExtConfig::TLS_DH_anon_WITH_AES_256_GCM_SHA384 not enabled')

0 commit comments

Comments
 (0)