From 65652ecdb5145fae92e3c86611a4425a476da6ec Mon Sep 17 00:00:00 2001 From: gotoyuzo Date: Tue, 19 Aug 2003 06:00:36 +0000 Subject: * lib/webrick/ssl.rb: new file; SSL/TLS enhancement for GenericServer. * lib/webrick/https.rb: SSLSocket handling is moved to webrick/ssl.rb. * lib/webrick/compat.rb (File::fnmatch): remove old migration code. * lib/webrick/httpserver.rb (HTTPServer#run): ditto. * lib/webrick/server.rb (GenericServer#listen): the body of this method is pull out as Utils::create_lisnteners. * lib/webrick/utils.rb (Utils::create_lisnteners): new method. * lib/webrick/server.rb (GenericServer#start): should not through unknown errors. and refine comments. * ext/openssl/lib/openssl/ssl.rb (SSLServer#accept): should close socket if SSLSocket raises error. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@4409 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- lib/webrick/compat.rb | 15 ------ lib/webrick/https.rb | 127 ++++------------------------------------------ lib/webrick/httpserver.rb | 2 +- lib/webrick/server.rb | 38 ++++---------- lib/webrick/ssl.rb | 124 ++++++++++++++++++++++++++++++++++++++++++++ lib/webrick/utils.rb | 24 +++++++++ 6 files changed, 170 insertions(+), 160 deletions(-) create mode 100644 lib/webrick/ssl.rb (limited to 'lib') diff --git a/lib/webrick/compat.rb b/lib/webrick/compat.rb index a972204ff1..ad7760b640 100644 --- a/lib/webrick/compat.rb +++ b/lib/webrick/compat.rb @@ -13,18 +13,3 @@ module Errno class ECONNRESET < SystemCallError; end class ECONNABORTED < SystemCallError; end end - -unless File.respond_to?(:fnmatch) - def File.fnmatch(pat, str) - case pat[0] - when nil - not str[0] - when ?* - fnmatch(pat[1..-1], str) || str[0] && fnmatch(pat, str[1..-1]) - when ?? - str[0] && fnmatch(pat[1..-1], str[1..-1]) - else - pat[0] == str[0] && fnmatch(pat[1..-1], str[1..-1]) - end - end -end diff --git a/lib/webrick/https.rb b/lib/webrick/https.rb index fa8c667d2a..4e44cfab32 100644 --- a/lib/webrick/https.rb +++ b/lib/webrick/https.rb @@ -8,31 +8,11 @@ # # $IPR: https.rb,v 1.15 2003/07/22 19:20:42 gotoyuzo Exp $ -require 'webrick' -require 'openssl' +require 'webrick/ssl' module WEBrick module Config - HTTP.update( - :SSLEnable => true, - :SSLCertificate => nil, - :SSLPrivateKey => nil, - :SSLClientCA => nil, - :SSLCACertificateFile => nil, - :SSLCACertificatePath => nil, - :SSLCertificateStore => nil, - :SSLVerifyClient => ::OpenSSL::SSL::VERIFY_NONE, - :SSLVerifyDepth => nil, - :SSLVerifyCallback => nil, # custom verification - :SSLTimeout => nil, - :SSLOptions => nil, - # Must specify if you use auto generated certificate. - :SSLCertName => nil, - :SSLCertComment => "Generated by Ruby/OpenSSL" - ) - - osslv = ::OpenSSL::OPENSSL_VERSION.split[1] - HTTP[:ServerSoftware] << " OpenSSL/#{osslv}" + HTTP.update(SSL) end class HTTPRequest @@ -41,16 +21,18 @@ module WEBrick alias orig_parse parse def parse(socket=nil) + if socket && socket.is_a?(OpenSSL::SSL::SSLSocket) + @server_cert = @config[:SSLCertificate] + @client_cert = socket.peer_cert + @cipher = socket.cipher + end orig_parse(socket) - @cipher = socket.respond_to?(:cipher) ? socket.cipher : nil - @client_cert = socket.respond_to?(:peer_cert) ? socket.peer_cert : nil - @server_cert = @config[:SSLCertificate] end alias orig_parse_uri parse_uri def parse_uri(str, scheme="https") - if @config[:SSLEnable] + if @server_cert return orig_parse_uri(str, scheme) end return orig_parse_uri(str) @@ -60,100 +42,13 @@ module WEBrick def meta_vars meta = orig_meta_vars - if @config[:SSLEnable] + if @server_cert meta["HTTPS"] = "on" - meta["SSL_CIPHER"] = @cipher ? @cipher[0] : "" + meta["SSL_SERVER_CERT"] = @server_cert.to_pem meta["SSL_CLIENT_CERT"] = @client_cert ? @client_cert.to_pem : "" - meta["SSL_SERVER_CERT"] = @server_cert ? @server_cert.to_pem : "" + meta["SSL_CIPHER"] = @cipher[0] end meta end end - - class HTTPServer - alias orig_init initialize - - def initialize(*args) - orig_init(*args) - - if @config[:SSLEnable] - unless @config[:SSLCertificate] - rsa = OpenSSL::PKey::RSA.new(512){|p, n| - case p - when 0; $stderr.putc "." # BN_generate_prime - when 1; $stderr.putc "+" # BN_generate_prime - when 2; $stderr.putc "*" # searching good prime, - # n = #of try, - # but also data from BN_generate_prime - when 3; $stderr.putc "\n" # found good prime, n==0 - p, n==1 - q, - # but also data from BN_generate_prime - else; $stderr.putc "*" # BN_generate_prime - end - } - cert = OpenSSL::X509::Certificate.new - cert.version = 3 - cert.serial = 0 - name = OpenSSL::X509::Name.new(@config[:SSLCertName]) - cert.subject = name - cert.issuer = name - cert.not_before = Time.now - cert.not_after = Time.now + (365*24*60*60) - cert.public_key = rsa.public_key - - ef = OpenSSL::X509::ExtensionFactory.new(nil,cert) - cert.extensions = [ - ef.create_extension("basicConstraints","CA:FALSE"), - ef.create_extension("subjectKeyIdentifier", "hash"), - ef.create_extension("extendedKeyUsage", "serverAuth") - ] - ef.issuer_certificate = cert - ext = ef.create_extension("authorityKeyIdentifier", - "keyid:always,issuer:always") - cert.add_extension(ext) - if comment = @config[:SSLCertComment] - cert.add_extension(ef.create_extension("nsComment", comment)) - end - cert.sign(rsa, OpenSSL::Digest::SHA1.new) - - @config[:SSLPrivateKey] = rsa - @config[:SSLCertificate] = cert - @logger.info cert.to_s - end - @ctx = OpenSSL::SSL::SSLContext.new - set_ssl_context(@ctx, @config) - end - end - - alias orig_run run - - def run(sock) - if @config[:SSLEnable] - ssl = OpenSSL::SSL::SSLSocket.new(sock, @ctx) - ssl.sync = true - ssl.accept - Thread.current[:WEBrickSocket] = ssl - orig_run(ssl) - Thread.current[:WEBrickSocket] = sock - ssl.close - else - orig_run(sock) - end - end - - private - - def set_ssl_context(ctx, config) - ctx.key = config[:SSLPrivateKey] - ctx.cert = config[:SSLCertificate] - ctx.client_ca = config[:SSLClientCA] - ctx.ca_file = config[:SSLCACertificateFile] - ctx.ca_path = config[:SSLCACertificatePath] - ctx.cert_store = config[:SSLCertificateStore] - ctx.verify_mode = config[:SSLVerifyClient] - ctx.verify_depth = config[:SSLVerifyDepth] - ctx.verify_callback = config[:SSLVerifyCallback] - ctx.timeout = config[:SSLTimeout] - ctx.options = config[:SSLOptions] - end - end end diff --git a/lib/webrick/httpserver.rb b/lib/webrick/httpserver.rb index df06e19e2c..a3da99db1a 100644 --- a/lib/webrick/httpserver.rb +++ b/lib/webrick/httpserver.rb @@ -58,7 +58,7 @@ module WEBrick res.set_error(ex) rescue HTTPStatus::Status => ex res.status = ex.code - rescue StandardError, NameError => ex # for Ruby 1.6 + rescue StandardError => ex @logger.error(ex) res.set_error(ex, true) ensure diff --git a/lib/webrick/server.rb b/lib/webrick/server.rb index 911f78b66a..c71b118642 100644 --- a/lib/webrick/server.rb +++ b/lib/webrick/server.rb @@ -55,10 +55,9 @@ module WEBrick @logger.info("WEBrick #{webrickv}") @logger.info("ruby #{rubyv}") - if @config[:DoNotListen] - @listeners = [] - else - @listeners = listen(@config[:BindAddress], @config[:Port]) + @listeners = [] + unless @config[:DoNotListen] + listen(@config[:BindAddress], @config[:Port]) @config[:Listen].each{|addr, port| listen(addr, port).each{|sock| @listeners << sock } } @@ -70,26 +69,7 @@ module WEBrick end def listen(address, port) - res = Socket::getaddrinfo(address, port, - Socket::AF_UNSPEC, # address family - Socket::SOCK_STREAM, # socket type - 0, # protocol - Socket::AI_PASSIVE) # flag - last_error = nil - sockets = [] - res.each{|ai| - begin - @logger.debug("TCPServer.new(#{ai[3]}, #{ai[1]})") - sock = TCPServer.new(ai[3], ai[1]) - Utils::set_close_on_exec(sock) - sockets << sock - rescue => ex - @logger.warn("TCPServer Error: #{ex}") - last_error = ex - end - } - raise last_error if sockets.empty? - return sockets + @listeners += Utils::create_listeners(address, port, @logger) end def start(&block) @@ -117,12 +97,14 @@ module WEBrick } end rescue Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPROTO => ex + # TCP connection was established but RST segment was sent + # from peer before calling TCPServer#accept. + rescue Errno::EBADF => ex + # if the listening socket was closed in GenericServer#shutdown, + # IO::select raise it. + rescue => ex msg = "#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}" @logger.error msg - rescue Errno::EBADF => ex # IO::select causes by shutdown - rescue => ex - @logger.error ex - break end end diff --git a/lib/webrick/ssl.rb b/lib/webrick/ssl.rb new file mode 100644 index 0000000000..402fd5d993 --- /dev/null +++ b/lib/webrick/ssl.rb @@ -0,0 +1,124 @@ +# +# ssl.rb -- SSL/TLS enhancement for GenericServer +# +# Copyright (c) 2003 GOTOU Yuuzou All rights reserved. +# +# $Id$ + +require 'webrick' +require 'openssl' + +module WEBrick + module Config + svrsoft = General[:ServerSoftware] + osslv = ::OpenSSL::OPENSSL_VERSION.split[1] + SSL = { + :ServerSoftware => "#{svrsoft} OpenSSL/#{osslv}", + :SSLEnable => true, + :SSLCertificate => nil, + :SSLPrivateKey => nil, + :SSLClientCA => nil, + :SSLCACertificateFile => nil, + :SSLCACertificatePath => nil, + :SSLCertificateStore => nil, + :SSLVerifyClient => ::OpenSSL::SSL::VERIFY_NONE, + :SSLVerifyDepth => nil, + :SSLVerifyCallback => nil, # custom verification + :SSLTimeout => nil, + :SSLOptions => nil, + :SSLStartImmediately => true, + # Must specify if you use auto generated certificate. + :SSLCertName => nil, + :SSLCertComment => "Generated by Ruby/OpenSSL" + } + General.update(SSL) + end + + module Utils + def create_self_signed_cert(bits, cn, comment) + rsa = OpenSSL::PKey::RSA.new(bits){|p, n| + case p + when 0; $stderr.putc "." # BN_generate_prime + when 1; $stderr.putc "+" # BN_generate_prime + when 2; $stderr.putc "*" # searching good prime, + # n = #of try, + # but also data from BN_generate_prime + when 3; $stderr.putc "\n" # found good prime, n==0 - p, n==1 - q, + # but also data from BN_generate_prime + else; $stderr.putc "*" # BN_generate_prime + end + } + cert = OpenSSL::X509::Certificate.new + cert.version = 3 + cert.serial = 0 + name = OpenSSL::X509::Name.new(cn) + cert.subject = name + cert.issuer = name + cert.not_before = Time.now + cert.not_after = Time.now + (365*24*60*60) + cert.public_key = rsa.public_key + + ef = OpenSSL::X509::ExtensionFactory.new(nil,cert) + ef.issuer_certificate = cert + cert.extensions = [ + ef.create_extension("basicConstraints","CA:FALSE"), + ef.create_extension("keyUsage", "keyEncipherment"), + ef.create_extension("subjectKeyIdentifier", "hash"), + ef.create_extension("extendedKeyUsage", "serverAuth"), + ef.create_extension("nsComment", comment), + ] + aki = ef.create_extension("authorityKeyIdentifier", + "keyid:always,issuer:always") + cert.add_extension(aki) + cert.sign(rsa, OpenSSL::Digest::SHA1.new) + + return [ cert, rsa ] + end + module_function :create_self_signed_cert + end + + class GenericServer + def ssl_context + @ssl_context ||= nil + end + + def listen(address, port) + listeners = Utils::create_listeners(address, port, @logger) + if @config[:SSLEnable] + unless ssl_context + @ssl_context = setup_ssl_context(@config) + @logger.info("\n" + @config[:SSLCertificate].to_text) + end + listeners.collect!{|svr| + ssvr = ::OpenSSL::SSL::SSLServer.new(svr, ssl_context) + ssvr.start_immediately = @config[:SSLStartImmediately] + ssvr + } + end + @listeners += listeners + end + + def setup_ssl_context(config) + unless config[:SSLCertificate] + cn = config[:SSLCertName] + comment = config[:SSLCertComment] + cert, key = Utils::create_self_signed_cert(1024, cn, comment) + config[:SSLCertificate] = cert + config[:SSLPrivateKey] = key + end + ctx = OpenSSL::SSL::SSLContext.new + ctx.key = config[:SSLPrivateKey] + ctx.cert = config[:SSLCertificate] + ctx.client_ca = config[:SSLClientCA] + ctx.ca_file = config[:SSLCACertificateFile] + ctx.ca_path = config[:SSLCACertificatePath] + ctx.cert_store = config[:SSLCertificateStore] + ctx.verify_mode = config[:SSLVerifyClient] + ctx.verify_depth = config[:SSLVerifyDepth] + ctx.verify_callback = config[:SSLVerifyCallback] + ctx.timeout = config[:SSLTimeout] + ctx.options = config[:SSLOptions] + ctx + end + end +end diff --git a/lib/webrick/utils.rb b/lib/webrick/utils.rb index 646880d655..d371f4989f 100644 --- a/lib/webrick/utils.rb +++ b/lib/webrick/utils.rb @@ -48,6 +48,30 @@ module WEBrick end module_function :getservername + def create_listeners(address, port, logger=nil) + res = Socket::getaddrinfo(address, port, + Socket::AF_UNSPEC, # address family + Socket::SOCK_STREAM, # socket type + 0, # protocol + Socket::AI_PASSIVE) # flag + last_error = nil + sockets = [] + res.each{|ai| + begin + logger.debug("TCPServer.new(#{ai[3]}, #{ai[1]})") if logger + sock = TCPServer.new(ai[3], ai[1]) + Utils::set_close_on_exec(sock) + sockets << sock + rescue => ex + logger.warn("TCPServer Error: #{ex}") if logger + last_error = ex + end + } + raise last_error if sockets.empty? + return sockets + end + module_function :create_listeners + RAND_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + "abcdefghijklmnopqrstuvwxyz" -- cgit v1.2.3