1 | require 'socket'
|
---|
2 | require 'openssl'
|
---|
3 | require 'drb/drb'
|
---|
4 | require 'singleton'
|
---|
5 |
|
---|
6 | module DRb
|
---|
7 |
|
---|
8 | class DRbSSLSocket < DRbTCPSocket
|
---|
9 |
|
---|
10 | class SSLConfig
|
---|
11 |
|
---|
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 | }
|
---|
26 |
|
---|
27 | def initialize(config)
|
---|
28 | @config = config
|
---|
29 | @cert = config[:SSLCertificate]
|
---|
30 | @pkey = config[:SSLPrivateKey]
|
---|
31 | @ssl_ctx = nil
|
---|
32 | end
|
---|
33 |
|
---|
34 | def [](key);
|
---|
35 | @config[key] || DEFAULT[key]
|
---|
36 | end
|
---|
37 |
|
---|
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
|
---|
56 |
|
---|
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 | }
|
---|
70 |
|
---|
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
|
---|
96 |
|
---|
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
|
---|
111 |
|
---|
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
|
---|
123 |
|
---|
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
|
---|
134 |
|
---|
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
|
---|
152 |
|
---|
153 | def self.uri_option(uri, config)
|
---|
154 | host, port, option = parse_uri(uri)
|
---|
155 | return "drbssl://#{host}:#{port}", option
|
---|
156 | end
|
---|
157 |
|
---|
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
|
---|
164 |
|
---|
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)
|
---|
190 | end
|
---|