Skip to content

Commit e976331

Browse files
committed
OpenSSL::SSL .rb part from MRI ruby_2_2 - with tunings due ASN.1 decoding
1 parent f18c794 commit e976331

1 file changed

Lines changed: 79 additions & 48 deletions

File tree

lib/jopenssl21/openssl/ssl.rb

Lines changed: 79 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -21,78 +21,106 @@ module OpenSSL
2121
module SSL
2222

2323
# FIXME: Using the old non-ASN1 logic here because our ASN1 appears to
24-
# return the wrong types for some decoded objects. See #1102
24+
# return the wrong types for some decoded objects.
25+
# @see https://github.com/jruby/jruby/issues/1102
26+
# @private
2527
def verify_certificate_identity(cert, hostname)
2628
should_verify_common_name = true
27-
cert.extensions.each{|ext|
29+
cert.extensions.each { |ext|
2830
next if ext.oid != "subjectAltName"
29-
ext.value.split(/,\s+/).each{|general_name|
31+
ext.value.split(/,\s+/).each { |general_name|
32+
# MRI 1.9.3 (since we parse ASN.1 differently)
33+
# when 2 # dNSName in GeneralName (RFC5280)
3034
if /\ADNS:(.*)/ =~ general_name
3135
should_verify_common_name = false
32-
reg = Regexp.escape($1).gsub(/\\\*/, "[^.]+")
33-
return true if /\A#{reg}\z/i =~ hostname
34-
# NOTE: somehow we need the IP: canonical form
35-
# seems there were failures elsewhere when not
36-
# not sure how that's possible possible to-do!
36+
return true if verify_hostname(hostname, $1)
37+
# MRI 1.9.3 (since we parse ASN.1 differently)
38+
# when 7 # iPAddress in GeneralName (RFC5280)
3739
elsif /\AIP(?: Address)?:(.*)/ =~ general_name
38-
#elsif /\AIP Address:(.*)/ =~ general_name
3940
should_verify_common_name = false
4041
return true if $1 == hostname
42+
# NOTE: bellow logic makes little sense as we read exts differently
43+
#value = $1 # follows GENERAL_NAME_print() in x509v3/v3_alt.c
44+
#if value.size == 4
45+
# return true if value.unpack('C*').join('.') == hostname
46+
#elsif value.size == 16
47+
# return true if value.unpack('n*').map { |e| sprintf("%X", e) }.join(':') == hostname
48+
#end
4149
end
4250
}
4351
}
4452
if should_verify_common_name
45-
cert.subject.to_a.each{|oid, value|
53+
cert.subject.to_a.each { |oid, value|
4654
if oid == "CN"
47-
reg = Regexp.escape(value).gsub(/\\\*/, "[^.]+")
48-
return true if /\A#{reg}\z/i =~ hostname
55+
return true if verify_hostname(hostname, value)
4956
end
5057
}
5158
end
5259
return false
5360
end
54-
=begin
55-
def verify_certificate_identity(cert, hostname)
56-
should_verify_common_name = true
57-
cert.extensions.each{|ext|
58-
next if ext.oid != "subjectAltName"
59-
ostr = OpenSSL::ASN1.decode(ext.to_der).value.last
60-
sequence = OpenSSL::ASN1.decode(ostr.value)
61-
sequence.value.each{|san|
62-
case san.tag
63-
when 2 # dNSName in GeneralName (RFC5280)
64-
should_verify_common_name = false
65-
reg = Regexp.escape(san.value).gsub(/\\\*/, "[^.]+")
66-
return true if /\A#{reg}\z/i =~ hostname
67-
when 7 # iPAddress in GeneralName (RFC5280)
68-
should_verify_common_name = false
69-
# follows GENERAL_NAME_print() in x509v3/v3_alt.c
70-
if san.value.size == 4
71-
return true if san.value.unpack('C*').join('.') == hostname
72-
elsif san.value.size == 16
73-
return true if san.value.unpack('n*').map { |e| sprintf("%X", e) }.join(':') == hostname
74-
end
75-
end
76-
}
77-
}
78-
if should_verify_common_name
79-
cert.subject.to_a.each{|oid, value|
80-
if oid == "CN"
81-
reg = Regexp.escape(value).gsub(/\\\*/, "[^.]+")
82-
return true if /\A#{reg}\z/i =~ hostname
83-
end
84-
}
85-
end
86-
return false
87-
end
88-
=end
8961
module_function :verify_certificate_identity
9062

63+
def verify_hostname(hostname, san) # :nodoc:
64+
# RFC 5280, IA5String is limited to the set of ASCII characters
65+
return false unless san.ascii_only?
66+
return false unless hostname.ascii_only?
67+
68+
# See RFC 6125, section 6.4.1
69+
# Matching is case-insensitive.
70+
san_parts = san.downcase.split(".")
71+
72+
# TODO: this behavior should probably be more strict
73+
return san == hostname if san_parts.size < 2
74+
75+
# Matching is case-insensitive.
76+
host_parts = hostname.downcase.split(".")
77+
78+
# RFC 6125, section 6.4.3, subitem 2.
79+
# If the wildcard character is the only character of the left-most
80+
# label in the presented identifier, the client SHOULD NOT compare
81+
# against anything but the left-most label of the reference
82+
# identifier (e.g., *.example.com would match foo.example.com but
83+
# not bar.foo.example.com or example.com).
84+
return false unless san_parts.size == host_parts.size
85+
86+
# RFC 6125, section 6.4.3, subitem 1.
87+
# The client SHOULD NOT attempt to match a presented identifier in
88+
# which the wildcard character comprises a label other than the
89+
# left-most label (e.g., do not match bar.*.example.net).
90+
return false unless verify_wildcard(host_parts.shift, san_parts.shift)
91+
92+
san_parts.join(".") == host_parts.join(".")
93+
end
94+
module_function :verify_hostname
95+
96+
def verify_wildcard(domain_component, san_component) # :nodoc:
97+
parts = san_component.split("*", -1)
98+
99+
return false if parts.size > 2
100+
return san_component == domain_component if parts.size == 1
101+
102+
# RFC 6125, section 6.4.3, subitem 3.
103+
# The client SHOULD NOT attempt to match a presented identifier
104+
# where the wildcard character is embedded within an A-label or
105+
# U-label of an internationalized domain name.
106+
return false if domain_component.start_with?("xn--") && san_component != "*"
107+
108+
parts[0].length + parts[1].length < domain_component.length &&
109+
domain_component.start_with?(parts[0]) &&
110+
domain_component.end_with?(parts[1])
111+
end
112+
module_function :verify_wildcard
113+
91114
class SSLSocket
92115
include Buffering
93116
include SocketForwarder
94117
include Nonblock
95118

119+
##
120+
# Perform hostname verification after an SSL connection is established
121+
#
122+
# This method MUST be called after calling #connect to ensure that the
123+
# hostname of a remote peer has been verified.
96124
def post_connection_check(hostname)
97125
unless OpenSSL::SSL.verify_certificate_identity(peer_cert, hostname)
98126
raise SSLError, "hostname \"#{hostname}\" does not match the server certificate"
@@ -141,7 +169,10 @@ def shutdown(how=Socket::SHUT_RDWR)
141169

142170
# Works similar to TCPServer#accept.
143171
def accept
144-
sock = @svr.accept
172+
# Socket#accept returns [socket, addrinfo].
173+
# TCPServer#accept returns a socket.
174+
# The following comma strips addrinfo.
175+
sock, = @svr.accept
145176
begin
146177
ssl = OpenSSL::SSL::SSLSocket.new(sock, @ctx)
147178
ssl.sync_close = true

0 commit comments

Comments
 (0)