Imported File#ftype spec from rubyspecs.
[rbx.git] / lib / drb / ssl.rb
blob58d6b7d1e0bd537d759decb73268ed4771e8a970
1 require 'socket'
2 require 'openssl'
3 require 'drb/drb'
4 require 'singleton'
6 module DRb
8   class DRbSSLSocket < DRbTCPSocket
10     class SSLConfig
12       DEFAULT = {
13         :SSLCertificate       => nil,
14         :SSLPrivateKey        => nil,
15         :SSLClientCA          => nil,
16         :SSLCACertificatePath => nil,
17         :SSLCACertificateFile => nil,
18         :SSLVerifyMode        => ::OpenSSL::SSL::VERIFY_NONE, 
19         :SSLVerifyDepth       => nil,
20         :SSLVerifyCallback    => nil,   # custom verification
21         :SSLCertificateStore  => nil,
22         # Must specify if you use auto generated certificate.
23         :SSLCertName          => nil,   # e.g. [["CN","fqdn.example.com"]]
24         :SSLCertComment       => "Generated by Ruby/OpenSSL"
25       }
27       def initialize(config)
28         @config  = config
29         @cert    = config[:SSLCertificate]
30         @pkey    = config[:SSLPrivateKey]
31         @ssl_ctx = nil
32       end
34       def [](key); 
35         @config[key] || DEFAULT[key]
36       end
38       def connect(tcp)
39         ssl = ::OpenSSL::SSL::SSLSocket.new(tcp, @ssl_ctx)
40         ssl.sync = true
41         ssl.connect
42         ssl
43       end
44       
45       def accept(tcp)
46         ssl = OpenSSL::SSL::SSLSocket.new(tcp, @ssl_ctx)
47         ssl.sync = true
48         ssl.accept
49         ssl
50       end
51       
52       def setup_certificate
53         if @cert && @pkey
54           return
55         end
57         rsa = OpenSSL::PKey::RSA.new(512){|p, n|
58           next unless self[:verbose]
59           case p
60           when 0; $stderr.putc "."  # BN_generate_prime
61           when 1; $stderr.putc "+"  # BN_generate_prime
62           when 2; $stderr.putc "*"  # searching good prime,
63                                     # n = #of try,
64                                     # but also data from BN_generate_prime
65           when 3; $stderr.putc "\n" # found good prime, n==0 - p, n==1 - q,
66                                     # but also data from BN_generate_prime
67           else;   $stderr.putc "*"  # BN_generate_prime
68           end
69         }
71         cert = OpenSSL::X509::Certificate.new
72         cert.version = 3
73         cert.serial = 0
74         name = OpenSSL::X509::Name.new(self[:SSLCertName])
75         cert.subject = name
76         cert.issuer = name
77         cert.not_before = Time.now
78         cert.not_after = Time.now + (365*24*60*60)
79         cert.public_key = rsa.public_key
80         
81         ef = OpenSSL::X509::ExtensionFactory.new(nil,cert)
82         cert.extensions = [
83           ef.create_extension("basicConstraints","CA:FALSE"),
84           ef.create_extension("subjectKeyIdentifier", "hash") ]
85         ef.issuer_certificate = cert
86         cert.add_extension(ef.create_extension("authorityKeyIdentifier",
87                                                "keyid:always,issuer:always"))
88         if comment = self[:SSLCertComment]
89           cert.add_extension(ef.create_extension("nsComment", comment))
90         end
91         cert.sign(rsa, OpenSSL::Digest::SHA1.new)
92         
93         @cert = cert
94         @pkey = rsa
95       end
97       def setup_ssl_context
98         ctx = ::OpenSSL::SSL::SSLContext.new
99         ctx.cert            = @cert
100         ctx.key             = @pkey
101         ctx.client_ca       = self[:SSLClientCA]
102         ctx.ca_path         = self[:SSLCACertificatePath]
103         ctx.ca_file         = self[:SSLCACertificateFile]
104         ctx.verify_mode     = self[:SSLVerifyMode]
105         ctx.verify_depth    = self[:SSLVerifyDepth]
106         ctx.verify_callback = self[:SSLVerifyCallback]
107         ctx.cert_store      = self[:SSLCertificateStore]
108         @ssl_ctx = ctx
109       end
110     end
112     def self.parse_uri(uri)
113       if uri =~ /^drbssl:\/\/(.*?):(\d+)(\?(.*))?$/
114         host = $1
115         port = $2.to_i
116         option = $4
117         [host, port, option]
118       else
119         raise(DRbBadScheme, uri) unless uri =~ /^drbssl:/
120         raise(DRbBadURI, 'can\'t parse uri:' + uri)
121       end
122     end
124     def self.open(uri, config)
125       host, port, option = parse_uri(uri)
126       host.untaint
127       port.untaint
128       soc = TCPSocket.open(host, port)
129       ssl_conf = SSLConfig::new(config)
130       ssl_conf.setup_ssl_context
131       ssl = ssl_conf.connect(soc)
132       self.new(uri, ssl, ssl_conf, true)
133     end
135     def self.open_server(uri, config)
136       uri = 'drbssl://:0' unless uri
137       host, port, opt = parse_uri(uri)
138       if host.size == 0
139         host = getservername
140         soc = open_server_inaddr_any(host, port)
141       else
142         soc = TCPServer.open(host, port)
143       end
144       port = soc.addr[1] if port == 0
145       @uri = "drbssl://#{host}:#{port}"
146       
147       ssl_conf = SSLConfig.new(config)
148       ssl_conf.setup_certificate
149       ssl_conf.setup_ssl_context
150       self.new(@uri, soc, ssl_conf, false)
151     end
153     def self.uri_option(uri, config)
154       host, port, option = parse_uri(uri)
155       return "drbssl://#{host}:#{port}", option
156     end
158     def initialize(uri, soc, config, is_established)
159       @ssl = is_established ? soc : nil
160       super(uri, soc.to_io, config)
161     end
162     
163     def stream; @ssl; end
165     def close
166       if @ssl
167         @ssl.close
168         @ssl = nil
169       end
170       super
171     end
172       
173     def accept
174       begin
175       while true
176         soc = @socket.accept
177         break if (@acl ? @acl.allow_socket?(soc) : true) 
178         soc.close
179       end
180       ssl = @config.accept(soc)
181       self.class.new(uri, ssl, @config, true)
182       rescue OpenSSL::SSL::SSLError
183         warn("#{__FILE__}:#{__LINE__}: warning: #{$!.message} (#{$!.class})") if @config[:verbose]
184         retry
185       end
186     end
187   end
188   
189   DRbProtocol.add_protocol(DRbSSLSocket)