@@ -143,8 +143,7 @@ def verify_certificate_identity(cert, hostname)
143143 case san . tag
144144 when 2 # dNSName in GeneralName (RFC5280)
145145 should_verify_common_name = false
146- reg = Regexp . escape ( san . value ) . gsub ( /\\ \* / , "[^.]+" )
147- return true if /\A #{ reg } \z /i =~ hostname
146+ return true if verify_hostname ( hostname , san . value )
148147 when 7 # iPAddress in GeneralName (RFC5280)
149148 should_verify_common_name = false
150149 # follows GENERAL_NAME_print() in x509v3/v3_alt.c
@@ -159,20 +158,75 @@ def verify_certificate_identity(cert, hostname)
159158 if should_verify_common_name
160159 cert . subject . to_a . each { |oid , value |
161160 if oid == "CN"
162- reg = Regexp . escape ( value ) . gsub ( /\\ \* / , "[^.]+" )
163- return true if /\A #{ reg } \z /i =~ hostname
161+ return true if verify_hostname ( hostname , value )
164162 end
165163 }
166164 end
167165 return false
168166 end
169167 module_function :verify_certificate_identity
170168
169+ def verify_hostname ( hostname , san ) # :nodoc:
170+ # RFC 5280, IA5String is limited to the set of ASCII characters
171+ return false unless san . ascii_only?
172+ return false unless hostname . ascii_only?
173+
174+ # See RFC 6125, section 6.4.1
175+ # Matching is case-insensitive.
176+ san_parts = san . downcase . split ( "." )
177+
178+ # TODO: this behavior should probably be more strict
179+ return san == hostname if san_parts . size < 2
180+
181+ # Matching is case-insensitive.
182+ host_parts = hostname . downcase . split ( "." )
183+
184+ # RFC 6125, section 6.4.3, subitem 2.
185+ # If the wildcard character is the only character of the left-most
186+ # label in the presented identifier, the client SHOULD NOT compare
187+ # against anything but the left-most label of the reference
188+ # identifier (e.g., *.example.com would match foo.example.com but
189+ # not bar.foo.example.com or example.com).
190+ return false unless san_parts . size == host_parts . size
191+
192+ # RFC 6125, section 6.4.3, subitem 1.
193+ # The client SHOULD NOT attempt to match a presented identifier in
194+ # which the wildcard character comprises a label other than the
195+ # left-most label (e.g., do not match bar.*.example.net).
196+ return false unless verify_wildcard ( host_parts . shift , san_parts . shift )
197+
198+ san_parts . join ( "." ) == host_parts . join ( "." )
199+ end
200+ module_function :verify_hostname
201+
202+ def verify_wildcard ( domain_component , san_component ) # :nodoc:
203+ parts = san_component . split ( "*" , -1 )
204+
205+ return false if parts . size > 2
206+ return san_component == domain_component if parts . size == 1
207+
208+ # RFC 6125, section 6.4.3, subitem 3.
209+ # The client SHOULD NOT attempt to match a presented identifier
210+ # where the wildcard character is embedded within an A-label or
211+ # U-label of an internationalized domain name.
212+ return false if domain_component . start_with? ( "xn--" ) && san_component != "*"
213+
214+ parts [ 0 ] . length + parts [ 1 ] . length < domain_component . length &&
215+ domain_component . start_with? ( parts [ 0 ] ) &&
216+ domain_component . end_with? ( parts [ 1 ] )
217+ end
218+ module_function :verify_wildcard
219+
171220 class SSLSocket
172221 include Buffering
173222 include SocketForwarder
174223 include Nonblock
175224
225+ ##
226+ # Perform hostname verification after an SSL connection is established
227+ #
228+ # This method MUST be called after calling #connect to ensure that the
229+ # hostname of a remote peer has been verified.
176230 def post_connection_check ( hostname )
177231 unless OpenSSL ::SSL . verify_certificate_identity ( peer_cert , hostname )
178232 raise SSLError , "hostname \" #{ hostname } \" does not match the server certificate"
0 commit comments