1#
2# ssl.rb -- SSL/TLS enhancement for GenericServer
3#
4# Copyright (c) 2003 GOTOU Yuuzou All rights reserved.
5#
6# $Id: ssl.rb 38945 2013-01-26 01:12:54Z drbrain $
7
8require 'webrick'
9require 'openssl'
10
11module WEBrick
12  module Config
13    svrsoft = General[:ServerSoftware]
14    osslv = ::OpenSSL::OPENSSL_VERSION.split[1]
15
16    ##
17    # Default SSL server configuration.
18    #
19    # WEBrick can automatically create a self-signed certificate if
20    # <code>:SSLCertName</code> is set.  For more information on the various
21    # SSL options see OpenSSL::SSL::SSLContext.
22    #
23    # :ServerSoftware       ::
24    #   The server software name used in the Server: header.
25    # :SSLEnable            :: false,
26    #   Enable SSL for this server.  Defaults to false.
27    # :SSLCertificate       ::
28    #   The SSL certificate for the server.
29    # :SSLPrivateKey        ::
30    #   The SSL private key for the server certificate.
31    # :SSLClientCA          :: nil,
32    #   Array of certificates that will be sent to the client.
33    # :SSLExtraChainCert    :: nil,
34    #   Array of certificates that willbe added to the certificate chain
35    # :SSLCACertificateFile :: nil,
36    #   Path to a CA certificate file
37    # :SSLCACertificatePath :: nil,
38    #   Path to a directory containing CA certificates
39    # :SSLCertificateStore  :: nil,
40    #   OpenSSL::X509::Store used for certificate validation of the client
41    # :SSLTmpDhCallback     :: nil,
42    #   Callback invoked when DH parameters are required.
43    # :SSLVerifyClient      ::
44    #   Sets whether the client is verified.  This defaults to VERIFY_NONE
45    #   which is typical for an HTTPS server.
46    # :SSLVerifyDepth       ::
47    #   Number of CA certificates to walk when verifying a certificate chain
48    # :SSLVerifyCallback    ::
49    #   Custom certificate verification callback
50    # :SSLTimeout           ::
51    #   Maximum session lifetime
52    # :SSLOptions           ::
53    #   Various SSL options
54    # :SSLStartImmediately  ::
55    #   Immediately start SSL upon connection?  Defaults to true
56    # :SSLCertName          ::
57    #   SSL certificate name.  Must be set to enable automatic certificate
58    #   creation.
59    # :SSLCertComment       ::
60    #   Comment used during automatic certificate creation.
61
62    SSL = {
63      :ServerSoftware       => "#{svrsoft} OpenSSL/#{osslv}",
64      :SSLEnable            => false,
65      :SSLCertificate       => nil,
66      :SSLPrivateKey        => nil,
67      :SSLClientCA          => nil,
68      :SSLExtraChainCert    => nil,
69      :SSLCACertificateFile => nil,
70      :SSLCACertificatePath => nil,
71      :SSLCertificateStore  => nil,
72      :SSLTmpDhCallback     => nil,
73      :SSLVerifyClient      => ::OpenSSL::SSL::VERIFY_NONE,
74      :SSLVerifyDepth       => nil,
75      :SSLVerifyCallback    => nil,   # custom verification
76      :SSLTimeout           => nil,
77      :SSLOptions           => nil,
78      :SSLStartImmediately  => true,
79      # Must specify if you use auto generated certificate.
80      :SSLCertName          => nil,
81      :SSLCertComment       => "Generated by Ruby/OpenSSL"
82    }
83    General.update(SSL)
84  end
85
86  module Utils
87    ##
88    # Creates a self-signed certificate with the given number of +bits+,
89    # the issuer +cn+ and a +comment+ to be stored in the certificate.
90
91    def create_self_signed_cert(bits, cn, comment)
92      rsa = OpenSSL::PKey::RSA.new(bits){|p, n|
93        case p
94        when 0; $stderr.putc "."  # BN_generate_prime
95        when 1; $stderr.putc "+"  # BN_generate_prime
96        when 2; $stderr.putc "*"  # searching good prime,
97                                  # n = #of try,
98                                  # but also data from BN_generate_prime
99        when 3; $stderr.putc "\n" # found good prime, n==0 - p, n==1 - q,
100                                  # but also data from BN_generate_prime
101        else;   $stderr.putc "*"  # BN_generate_prime
102        end
103      }
104      cert = OpenSSL::X509::Certificate.new
105      cert.version = 2
106      cert.serial = 1
107      name = OpenSSL::X509::Name.new(cn)
108      cert.subject = name
109      cert.issuer = name
110      cert.not_before = Time.now
111      cert.not_after = Time.now + (365*24*60*60)
112      cert.public_key = rsa.public_key
113
114      ef = OpenSSL::X509::ExtensionFactory.new(nil,cert)
115      ef.issuer_certificate = cert
116      cert.extensions = [
117        ef.create_extension("basicConstraints","CA:FALSE"),
118        ef.create_extension("keyUsage", "keyEncipherment"),
119        ef.create_extension("subjectKeyIdentifier", "hash"),
120        ef.create_extension("extendedKeyUsage", "serverAuth"),
121        ef.create_extension("nsComment", comment),
122      ]
123      aki = ef.create_extension("authorityKeyIdentifier",
124                                "keyid:always,issuer:always")
125      cert.add_extension(aki)
126      cert.sign(rsa, OpenSSL::Digest::SHA1.new)
127
128      return [ cert, rsa ]
129    end
130    module_function :create_self_signed_cert
131  end
132
133  ##
134  #--
135  # Updates WEBrick::GenericServer with SSL functionality
136
137  class GenericServer
138
139    ##
140    # SSL context for the server when run in SSL mode
141
142    def ssl_context # :nodoc:
143      @ssl_context ||= nil
144    end
145
146    undef listen
147
148    ##
149    # Updates +listen+ to enable SSL when the SSL configuration is active.
150
151    def listen(address, port) # :nodoc:
152      listeners = Utils::create_listeners(address, port, @logger)
153      if @config[:SSLEnable]
154        unless ssl_context
155          @ssl_context = setup_ssl_context(@config)
156          @logger.info("\n" + @config[:SSLCertificate].to_text)
157        end
158        listeners.collect!{|svr|
159          ssvr = ::OpenSSL::SSL::SSLServer.new(svr, ssl_context)
160          ssvr.start_immediately = @config[:SSLStartImmediately]
161          ssvr
162        }
163      end
164      @listeners += listeners
165    end
166
167    ##
168    # Sets up an SSL context for +config+
169
170    def setup_ssl_context(config) # :nodoc:
171      unless config[:SSLCertificate]
172        cn = config[:SSLCertName]
173        comment = config[:SSLCertComment]
174        cert, key = Utils::create_self_signed_cert(1024, cn, comment)
175        config[:SSLCertificate] = cert
176        config[:SSLPrivateKey] = key
177      end
178      ctx = OpenSSL::SSL::SSLContext.new
179      ctx.key = config[:SSLPrivateKey]
180      ctx.cert = config[:SSLCertificate]
181      ctx.client_ca = config[:SSLClientCA]
182      ctx.extra_chain_cert = config[:SSLExtraChainCert]
183      ctx.ca_file = config[:SSLCACertificateFile]
184      ctx.ca_path = config[:SSLCACertificatePath]
185      ctx.cert_store = config[:SSLCertificateStore]
186      ctx.tmp_dh_callback = config[:SSLTmpDhCallback]
187      ctx.verify_mode = config[:SSLVerifyClient]
188      ctx.verify_depth = config[:SSLVerifyDepth]
189      ctx.verify_callback = config[:SSLVerifyCallback]
190      ctx.timeout = config[:SSLTimeout]
191      ctx.options = config[:SSLOptions]
192      ctx
193    end
194  end
195end
196