fluent-plugin-secure-forward 0.2.6 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,6 +8,7 @@ module Fluent
8
8
  end
9
9
 
10
10
  require_relative 'input_session'
11
+ require_relative './secure_forward/cert_util'
11
12
 
12
13
  module Fluent
13
14
  class SecureForwardInput < Input
@@ -15,36 +16,44 @@ module Fluent
15
16
 
16
17
  Fluent::Plugin.register_input('secure_forward', self)
17
18
 
19
+ config_param :secure, :bool # if secure, cert_path or ca_cert_path required
20
+
18
21
  config_param :self_hostname, :string
19
22
  include Fluent::Mixin::ConfigPlaceholders
20
23
 
21
24
  config_param :shared_key, :string
22
25
 
23
- config_param :bind, :string, :default => '0.0.0.0'
24
- config_param :port, :integer, :default => DEFAULT_SECURE_LISTEN_PORT
25
- config_param :allow_keepalive, :bool, :default => true #TODO: implement
26
+ config_param :bind, :string, default: '0.0.0.0'
27
+ config_param :port, :integer, default: DEFAULT_SECURE_LISTEN_PORT
28
+ config_param :allow_keepalive, :bool, default: true #TODO: implement
29
+
30
+ config_param :allow_anonymous_source, :bool, default: true
31
+ config_param :authentication, :bool, default: false
26
32
 
27
- config_param :allow_anonymous_source, :bool, :default => true
28
- config_param :authentication, :bool, :default => false
33
+ config_param :ssl_version, :string, default: 'TLSv1_2'
34
+ config_param :ssl_ciphers, :string, default: nil
29
35
 
30
- ## meaningless for security...? not implemented yet
31
- # config_param :dns_reverse_lookup_check, :bool, :default => false
36
+ # Cert signed by public CA
37
+ config_param :cert_path, :string, default: nil
38
+ config_param :private_key_path, :string, default: nil
39
+ config_param :private_key_passphrase, :string, default: nil
32
40
 
33
- config_param :cert_auto_generate, :bool, :default => false
34
- config_param :generate_private_key_length, :integer, :default => 2048
41
+ # Cert automatically generated and signed by private CA
42
+ config_param :ca_cert_path, :string, default: nil
43
+ config_param :ca_private_key_path, :string, default: nil
44
+ config_param :ca_private_key_passphrase, :string, default: nil
35
45
 
36
- config_param :generate_cert_country, :string, :default => 'US'
37
- config_param :generate_cert_state, :string, :default => 'CA'
38
- config_param :generate_cert_locality, :string, :default => 'Mountain View'
39
- config_param :generate_cert_common_name, :string, :default => nil
46
+ # Otherwise: Cert automatically generated and signed by itself (for without any verification)
40
47
 
41
- config_param :cert_file_path, :string, :default => nil
42
- config_param :private_key_file, :string, :default => nil
43
- config_param :private_key_passphrase, :string, :default => nil
48
+ config_param :generate_private_key_length, :integer, default: 2048
49
+ config_param :generate_cert_country, :string, default: 'US'
50
+ config_param :generate_cert_state, :string, default: 'CA'
51
+ config_param :generate_cert_locality, :string, default: 'Mountain View'
52
+ config_param :generate_cert_common_name, :string, default: nil
44
53
 
45
- config_param :read_length, :size, :default => 8*1024*1024 # 8MB
46
- config_param :read_interval_msec, :integer, :default => 50 # 50ms
47
- config_param :socket_interval_msec, :integer, :default => 200 # 200ms
54
+ config_param :read_length, :size, default: 8*1024*1024 # 8MB
55
+ config_param :read_interval_msec, :integer, default: 50 # 50ms
56
+ config_param :socket_interval_msec, :integer, default: 200 # 200ms
48
57
 
49
58
  attr_reader :read_interval, :socket_interval
50
59
 
@@ -80,8 +89,19 @@ module Fluent
80
89
  def configure(conf)
81
90
  super
82
91
 
83
- unless @cert_auto_generate || @cert_file_path
84
- raise Fluent::ConfigError, "One of 'cert_auto_generate' or 'cert_file_path' must be specified"
92
+ if @secure
93
+ unless @cert_path || @ca_cert_path
94
+ raise Fluent::ConfigError, "cert_path or ca_cert_path required for secure communication"
95
+ end
96
+ if @cert_path
97
+ raise Fluent::ConfigError, "private_key_path required" unless @private_key_path
98
+ raise Fluent::ConfigError, "private_key_passphrase required" unless @private_key_passphrase
99
+ else # @ca_cert_path
100
+ raise Fluent::ConfigError, "ca_private_key_path required" unless @ca_private_key_path
101
+ raise Fluent::ConfigError, "ca_private_key_passphrase required" unless @ca_private_key_passphrase
102
+ end
103
+ else
104
+ log.warn "'insecure' mode has vulnerability for man-in-the-middle attacks for clients (output plugins)."
85
105
  end
86
106
 
87
107
  @read_interval = @read_interval_msec / 1000.0
@@ -117,7 +137,10 @@ module Fluent
117
137
  end
118
138
 
119
139
  @generate_cert_common_name ||= @self_hostname
140
+
141
+ # To check whether certificates are successfully generated/loaded at startup time
120
142
  self.certificate
143
+
121
144
  true
122
145
  end
123
146
 
@@ -127,6 +150,7 @@ module Fluent
127
150
  @sessions = []
128
151
  @sock = nil
129
152
  @listener = Thread.new(&method(:run))
153
+ @listener.abort_on_exception
130
154
  end
131
155
 
132
156
  def shutdown
@@ -147,44 +171,59 @@ module Fluent
147
171
  def certificate
148
172
  return @cert, @key if @cert && @key
149
173
 
150
- if @cert_auto_generate
151
- key = OpenSSL::PKey::RSA.generate(@generate_private_key_length)
152
-
153
- digest = OpenSSL::Digest::SHA1.new
154
- issuer = subject = OpenSSL::X509::Name.new
155
- subject.add_entry('C', @generate_cert_country)
156
- subject.add_entry('ST', @generate_cert_state)
157
- subject.add_entry('L', @generate_cert_locality)
158
- subject.add_entry('CN', @generate_cert_common_name)
159
-
160
- cer = OpenSSL::X509::Certificate.new
161
- cer.not_before = Time.at(0)
162
- cer.not_after = Time.at(0)
163
- cer.public_key = key
164
- cer.serial = 1
165
- cer.issuer = issuer
166
- cer.subject = subject
167
- cer.sign(key, digest)
168
-
169
- @cert = cer
170
- @key = key
171
- return @cert, @key
174
+ if @cert_path
175
+ @key = OpenSSL::PKey::RSA.new(File.read(@private_key_path), @private_key_passphrase)
176
+ @cert = OpenSSL::X509::Certificate.new(File.read(@cert_path))
177
+ elsif @ca_cert_path
178
+ opts = {
179
+ ca_cert_path: @ca_cert_path,
180
+ ca_key_path: @ca_private_key_path,
181
+ ca_key_passphrase: @ca_private_key_passphrase,
182
+ private_key_length: @generate_private_key_length,
183
+ country: @generate_cert_country,
184
+ state: @generate_cert_state,
185
+ locality: @generate_cert_locality,
186
+ common_name: @generate_cert_common_name,
187
+ }
188
+ @cert, @key = Fluent::SecureForward::CertUtil.generate_server_pair(opts)
189
+ else
190
+ opts = {
191
+ private_key_length: @generate_private_key_length,
192
+ country: @generate_cert_country,
193
+ state: @generate_cert_state,
194
+ locality: @generate_cert_locality,
195
+ common_name: @generate_cert_common_name,
196
+ }
197
+ @cert, @key = Fluent::SecureForward::CertUtil.generate_self_signed_server_pair(opts)
172
198
  end
173
-
174
- @cert = OpenSSL::X509::Certificate.new(File.read(@cert_file_path))
175
- @key = OpenSSL::PKey::RSA.new(File.read(@private_key_file), @private_key_passphrase)
199
+ return @cert, @key
176
200
  end
177
201
 
178
202
  def run # sslsocket server thread
179
203
  log.trace "setup for ssl sessions"
180
204
  cert, key = self.certificate
181
- ctx = OpenSSL::SSL::SSLContext.new
205
+
206
+ ctx = OpenSSL::SSL::SSLContext.new(@ssl_version)
207
+ if @secure
208
+ # inject OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
209
+ # https://bugs.ruby-lang.org/issues/9424
210
+ ctx.set_params({})
211
+
212
+ if @ssl_ciphers
213
+ ctx.ciphers = @ssl_ciphers
214
+ else
215
+ ### follow httpclient configuration by nahi
216
+ # OpenSSL 0.9.8 default: "ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH"
217
+ ctx.ciphers = "ALL:!aNULL:!eNULL:!SSLv2" # OpenSSL >1.0.0 default
218
+ end
219
+ end
220
+
182
221
  ctx.cert = cert
183
222
  ctx.key = key
184
223
 
185
- log.trace "start to listen", :bind => @bind, :port => @port
224
+ log.trace "start to listen", bind: @bind, port: @port
186
225
  server = TCPServer.new(@bind, @port)
187
- log.trace "starting SSL server", :bind => @bind, :port => @port
226
+ log.trace "starting SSL server", bind: @bind, port: @port
188
227
  @sock = OpenSSL::SSL::SSLServer.new(server, ctx)
189
228
  @sock.start_immediately = false
190
229
  begin
@@ -196,7 +235,7 @@ module Fluent
196
235
 
197
236
  # cleanup closed session instance
198
237
  @sessions.delete_if(&:closed?)
199
- log.trace "session instances:", :all => @sessions.size, :closed => @sessions.select(&:closed?).size
238
+ log.trace "session instances:", all: @sessions.size, closed: @sessions.select(&:closed?).size
200
239
  end
201
240
  end
202
241
  rescue OpenSSL::SSL::SSLError => e
@@ -151,7 +151,7 @@ class Fluent::SecureForwardInput::Session
151
151
  begin
152
152
  @socket.accept
153
153
  rescue OpenSSL::SSL::SSLError => e
154
- log.debug "failed to establish ssl session"
154
+ log.debug "failed to establish ssl session", error_class: e.class, error: e
155
155
  self.shutdown
156
156
  return
157
157
  end
@@ -195,7 +195,7 @@ class Fluent::SecureForwardInput::Session
195
195
  rescue Errno::ECONNRESET => e
196
196
  # disconnected from client
197
197
  rescue => e
198
- log.warn "unexpected error in in_secure_forward", :error_class => e.class, :error => e
198
+ log.warn "unexpected error in in_secure_forward", error_class: e.class, error: e
199
199
  ensure
200
200
  self.shutdown
201
201
  end
@@ -15,26 +15,31 @@ module Fluent
15
15
 
16
16
  Fluent::Plugin.register_output('secure_forward', self)
17
17
 
18
+ config_param :secure, :bool
19
+
18
20
  config_param :self_hostname, :string
19
21
  include Fluent::Mixin::ConfigPlaceholders
20
22
 
21
23
  config_param :shared_key, :string
22
24
 
23
- config_param :keepalive, :time, :default => nil # nil/0 means disable keepalive expiration
25
+ config_param :keepalive, :time, default: nil # nil/0 means disable keepalive expiration
24
26
 
25
- config_param :send_timeout, :time, :default => 60
27
+ config_param :send_timeout, :time, default: 60
26
28
  # config_param :hard_timeout, :time, :default => 60
27
29
  # config_param :expire_dns_cache, :time, :default => 0 # 0 means disable cache
28
30
 
29
- config_param :allow_self_signed_certificate, :bool, :default => true
30
- config_param :ca_file_path, :string, :default => nil
31
+ config_param :ca_cert_path, :string, default: nil
32
+
33
+ config_param :enable_strict_verification, :bool, default: nil # FQDN check with hostlabel
34
+ config_param :ssl_version, :string, default: 'TLSv1_2'
35
+ config_param :ssl_ciphers, :string, default: nil
31
36
 
32
- config_param :read_length, :size, :default => 512 # 512bytes
33
- config_param :read_interval_msec, :integer, :default => 50 # 50ms
34
- config_param :socket_interval_msec, :integer, :default => 200 # 200ms
37
+ config_param :read_length, :size, default: 512 # 512bytes
38
+ config_param :read_interval_msec, :integer, default: 50 # 50ms
39
+ config_param :socket_interval_msec, :integer, default: 200 # 200ms
35
40
 
36
- config_param :reconnect_interval, :time, :default => 5
37
- config_param :established_timeout, :time, :default => 10
41
+ config_param :reconnect_interval, :time, default: 5
42
+ config_param :established_timeout, :time, default: 10
38
43
 
39
44
  attr_reader :read_interval, :socket_interval
40
45
 
@@ -68,8 +73,20 @@ module Fluent
68
73
  def configure(conf)
69
74
  super
70
75
 
71
- unless @allow_self_signed_certificate
72
- raise Fluent::ConfigError, "not tested yet!"
76
+ if @secure
77
+ if @ca_cert_path
78
+ raise Fluent::ConfigError, "CA cert file not found nor readable at '#{@ca_cert_path}'" unless File.readable?(@ca_cert_path)
79
+ begin
80
+ OpenSSL::X509::Certificate.new File.read(@ca_cert_path)
81
+ rescue OpenSSL::X509::CertificateError => e
82
+ raise Fluent::ConfigError, "failed to load CA cert file"
83
+ end
84
+ else
85
+ raise Fluent::ConfigError, "FQDN verification required for certificates issued from public CA" unless @enable_strict_verification
86
+ log.info "secure connection with valid certificates issued from public CA"
87
+ end
88
+ else
89
+ log.warn "'insecure' mode has vulnerability for man-in-the-middle attacks."
73
90
  end
74
91
 
75
92
  @read_interval = @read_interval_msec / 1000.0
@@ -89,7 +106,7 @@ module Fluent
89
106
  @next_node = 0
90
107
  @mutex = Mutex.new
91
108
 
92
- @hostname_resolver = Resolve::Hostname.new(:system_resolver => true)
109
+ @hostname_resolver = Resolve::Hostname.new(system_resolver: true)
93
110
 
94
111
  true
95
112
  end
@@ -122,7 +139,7 @@ module Fluent
122
139
  OpenSSL::Random.seed(SecureRandom.random_bytes(16))
123
140
  log.debug "start to connect target nodes"
124
141
  @nodes.each do |node|
125
- log.debug "connecting node", :host => node.host, :port => node.port
142
+ log.debug "connecting node", host: node.host, port: node.port
126
143
  node.start
127
144
  end
128
145
  @nodewatcher = Thread.new(&method(:node_watcher))
@@ -153,13 +170,13 @@ module Fluent
153
170
  end
154
171
 
155
172
  node = @nodes[i]
156
- log.debug "reconnecting to node", :host => node.host, :port => node.port, :expire => node.expire, :expired => node.expired?, :detached => node.detached?
173
+ log.debug "reconnecting to node", host: node.host, port: node.port, expire: node.expire, expired: node.expired?, detached: node.detached?
157
174
 
158
175
  renewed = node.dup
159
176
  renewed.start
160
177
 
161
178
  Thread.pass # to connection thread
162
- reconnectings[i] = { :conn => renewed, :at => Time.now, :reason => reason }
179
+ reconnectings[i] = { conn: renewed, at: Time.now, reason: reason }
163
180
  end
164
181
 
165
182
  (0...nodes_size).each do |i|
@@ -191,7 +208,7 @@ module Fluent
191
208
 
192
209
  # not connected yet, and timeout
193
210
  timeout_conn = reconnectings[i][:conn]
194
- log.debug "SSL connection is not established until timemout", :host => timeout_conn.host, :port => timeout_conn.port, :timeout => @established_timeout
211
+ log.debug "SSL connection is not established until timemout", host: timeout_conn.host, port: timeout_conn.port, timeout: @established_timeout
195
212
  reconnectings[i] = nil
196
213
  timeout_conn.detach! if timeout_conn # connection object doesn't raise any exceptions
197
214
  end
@@ -215,13 +232,13 @@ module Fluent
215
232
  unless node
216
233
  raise "no one nodes with valid ssl session"
217
234
  end
218
- log.trace "selected node", :host => node.host, :port => node.port, :standby => node.standby
235
+ log.trace "selected node", host: node.host, port: node.port, standby: node.standby
219
236
 
220
237
  begin
221
238
  send_data(node, tag, es)
222
239
  node.release!
223
240
  rescue Errno::EPIPE, IOError, OpenSSL::SSL::SSLError => e
224
- log.warn "Failed to send messages to #{node.host}, parging.", :error_class => e.class, :error => e
241
+ log.warn "Failed to send messages to #{node.host}, parging.", error_class: e.class, error: e
225
242
  node.release!
226
243
  node.detach!
227
244
 
@@ -60,8 +60,6 @@ class Fluent::SecureForwardOutput::Node
60
60
 
61
61
  def start
62
62
  @thread = Thread.new(&method(:connect))
63
- ## If you want to check code bug, turn this line enable
64
- # @thread.abort_on_exception = true
65
63
  end
66
64
 
67
65
  def detach!
@@ -165,6 +163,10 @@ class Fluent::SecureForwardOutput::Node
165
163
  return false, 'authentication failed: ' + reason
166
164
  end
167
165
 
166
+ if hostname == @sender.self_hostname
167
+ return false, 'same hostname between input and output: invalid configuration'
168
+ end
169
+
168
170
  clientside = Digest::SHA512.new.update(@shared_key_salt).update(hostname).update(@shared_key).hexdigest
169
171
  unless shared_key_hexdigest == clientside
170
172
  return false, 'shared key mismatch'
@@ -204,19 +206,20 @@ class Fluent::SecureForwardOutput::Node
204
206
  log.info "connection established to #{@host}" if @first_session
205
207
  @state = :established
206
208
  @expire = Time.now + @keepalive if @keepalive && @keepalive > 0
207
- log.debug "connection established", :host => @host, :port => @port, :expire => @expire
209
+ log.debug "connection established", host: @host, port: @port, expire: @expire
208
210
  end
209
211
  end
210
212
 
211
213
  def connect
214
+ Thread.current.abort_on_exception = true
212
215
  log.debug "starting client"
213
216
 
214
217
  addr = @sender.hostname_resolver.getaddress(@host)
215
- log.debug "create tcp socket to node", :host => @host, :address => addr, :port => @port
218
+ log.debug "create tcp socket to node", host: @host, address: addr, port: @port
216
219
  begin
217
220
  sock = TCPSocket.new(addr, @port)
218
221
  rescue => e
219
- log.warn "failed to connect for secure-forward", :error_class => e.class, :error => e, :host => @host, :address => addr, :port => @port
222
+ log.warn "failed to connect for secure-forward", error_class: e.class, error: e, host: @host, address: addr, port: @port
220
223
  @state = :failed
221
224
  return
222
225
  end
@@ -228,45 +231,65 @@ class Fluent::SecureForwardOutput::Node
228
231
  opt = [@sender.send_timeout.to_i, 0].pack('L!L!') # struct timeval
229
232
  sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, opt)
230
233
 
231
- # TODO: SSLContext constructer parameter (SSL/TLS protocol version)
232
234
  log.trace "initializing SSL contexts"
233
- context = OpenSSL::SSL::SSLContext.new
234
- # TODO: context.ca_file = (ca_file_path)
235
- # TODO: context.ciphers = (SSL Shared key chiper protocols)
236
235
 
237
- log.debug "trying to connect ssl session", :host => @host, :address => addr, :port => @port
236
+ context = OpenSSL::SSL::SSLContext.new(@sender.ssl_version)
237
+
238
+ log.trace "setting SSL verification options"
239
+
240
+ if @sender.secure
241
+ # inject OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
242
+ # https://bugs.ruby-lang.org/issues/9424
243
+ context.set_params({})
244
+
245
+ if @sender.ssl_ciphers
246
+ context.ciphers = @sender.ssl_ciphers
247
+ else
248
+ ### follow httpclient configuration by nahi
249
+ # OpenSSL 0.9.8 default: "ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH"
250
+ context.ciphers = "ALL:!aNULL:!eNULL:!SSLv2" # OpenSSL >1.0.0 default
251
+ end
252
+
253
+ log.trace "set verify_mode VERIFY_PEER"
254
+ context.verify_mode = OpenSSL::SSL::VERIFY_PEER
255
+ if @sender.ca_cert_path
256
+ log.trace "set to use private CA", path: @sender.ca_cert_path
257
+ context.ca_file = @sender.ca_cert_path
258
+ end
259
+ end
260
+
261
+ log.debug "trying to connect ssl session", host: @host, address: addr, port: @port
238
262
  begin
239
263
  sslsession = OpenSSL::SSL::SSLSocket.new(sock, context)
264
+ log.trace "connecting...", host: @host, address: addr, port: @port
265
+ sslsession.connect
240
266
  rescue => e
241
- log.warn "failed to establish SSL connection", :host => @host, :address => addr, :port => @port
242
- end
243
-
244
- unless sslsession.connect
245
- log.debug "failed to connect", :host => @host, :address => addr, :port => @port
267
+ log.warn "failed to establish SSL connection", error_class: e.class, error: e, host: @host, address: addr, port: @port
246
268
  @state = :failed
247
269
  return
248
270
  end
249
- log.debug "ssl session connected", :host => @host, :port => @port
271
+
272
+ log.debug "ssl session connected", host: @host, port: @port
250
273
 
251
274
  begin
252
- unless @sender.allow_self_signed_certificate
253
- log.debug "checking peer's certificate", :subject => sslsession.peer_cert.subject
275
+ if @sender.enable_strict_verification
276
+ log.debug "checking peer's certificate", subject: sslsession.peer_cert.subject
254
277
  sslsession.post_connection_check(@hostlabel)
255
278
  verify = sslsession.verify_result
256
279
  if verify != OpenSSL::X509::V_OK
257
280
  err_name = Fluent::SecureForwardOutput::OpenSSLUtil.verify_result_name(verify)
258
- log.warn "failed to verify certification while connecting host #{@host} as #{@hostlabel} (but not raised, why?)"
259
- log.warn "verify_result: #{err_name}"
260
- raise RuntimeError, "failed to verify certification while connecting host #{@host} as #{@hostlabel}"
281
+ log.warn "BUG: failed to verify certification while connecting host #{@host} as #{@hostlabel} (but not raised, why?)"
282
+ log.warn "BUG: verify_result: #{err_name}"
283
+ raise RuntimeError, "BUG: failed to verify certification and to handle it correctly while connecting host #{@host} as #{@hostlabel}"
261
284
  end
262
285
  end
263
286
  rescue OpenSSL::SSL::SSLError => e
264
- log.warn "failed to verify certification while connecting ssl session", :host => @host, :hostlabel => @hostlabel
287
+ log.warn "failed to verify certification while connecting ssl session", host: @host, hostlabel: @hostlabel
265
288
  self.shutdown
266
289
  raise
267
290
  end
268
291
 
269
- log.debug "ssl sessison connected", :host => @host, :port => @port
292
+ log.debug "ssl session connected", host: @host, port: @port
270
293
  @socket = sock
271
294
  @sslsession = sslsession
272
295
 
@@ -0,0 +1,85 @@
1
+ require 'openssl'
2
+
3
+ module Fluent
4
+ module SecureForward
5
+ module CertUtil
6
+ def self.generate_ca_pair(opts={})
7
+ key = OpenSSL::PKey::RSA.generate(opts[:private_key_length])
8
+
9
+ issuer = subject = OpenSSL::X509::Name.new
10
+ subject.add_entry('C', opts[:cert_country])
11
+ subject.add_entry('ST', opts[:cert_state])
12
+ subject.add_entry('L', opts[:cert_locality])
13
+ subject.add_entry('CN', opts[:cert_common_name])
14
+
15
+ digest = OpenSSL::Digest::SHA1.new
16
+
17
+ cert = OpenSSL::X509::Certificate.new
18
+ cert.not_before = Time.at(0)
19
+ cert.not_after = Time.now + 5 * 365 * 86400 # 5 years after
20
+ cert.public_key = key
21
+ cert.serial = 1
22
+ cert.issuer = issuer
23
+ cert.subject = subject
24
+ cert.add_extension OpenSSL::X509::Extension.new('basicConstraints', OpenSSL::ASN1.Sequence([OpenSSL::ASN1::Boolean(true)]))
25
+ cert.sign(key, digest)
26
+
27
+ return cert, key
28
+ end
29
+
30
+ def self.generate_server_pair(opts={})
31
+ key = OpenSSL::PKey::RSA.generate(opts[:private_key_length])
32
+
33
+ ca_key = OpenSSL::PKey::RSA.new(File.read(opts[:ca_key_path]), opts[:ca_key_passphrase])
34
+ ca_cert = OpenSSL::X509::Certificate.new(File.read(opts[:ca_cert_path]))
35
+ issuer = ca_cert.issuer
36
+
37
+ subject = OpenSSL::X509::Name.new
38
+ subject.add_entry('C', opts[:country])
39
+ subject.add_entry('ST', opts[:state])
40
+ subject.add_entry('L', opts[:locality])
41
+ subject.add_entry('CN', opts[:common_name])
42
+
43
+ digest = OpenSSL::Digest::SHA1.new
44
+
45
+ cert = OpenSSL::X509::Certificate.new
46
+ cert.not_before = Time.at(0)
47
+ cert.not_after = Time.now + 5 * 365 * 86400 # 5 years after
48
+ cert.public_key = key
49
+ cert.serial = 2
50
+ cert.issuer = issuer
51
+ cert.subject = subject
52
+
53
+ cert.add_extension OpenSSL::X509::Extension.new('basicConstraints', OpenSSL::ASN1.Sequence([OpenSSL::ASN1::Boolean(false)]))
54
+ cert.add_extension OpenSSL::X509::Extension.new('nsCertType', 'server')
55
+
56
+ cert.sign ca_key, digest
57
+
58
+ return cert, key
59
+ end
60
+
61
+ def self.generate_self_signed_server_pair(opts={})
62
+ key = OpenSSL::PKey::RSA.generate(opts[:private_key_length])
63
+
64
+ issuer = subject = OpenSSL::X509::Name.new
65
+ subject.add_entry('C', opts[:country])
66
+ subject.add_entry('ST', opts[:state])
67
+ subject.add_entry('L', opts[:locality])
68
+ subject.add_entry('CN', opts[:common_name])
69
+
70
+ digest = OpenSSL::Digest::SHA1.new
71
+
72
+ cert = OpenSSL::X509::Certificate.new
73
+ cert.not_before = Time.at(0)
74
+ cert.not_after = Time.now + 5 * 365 * 86400 # 5 years after
75
+ cert.public_key = key
76
+ cert.serial = 1
77
+ cert.issuer = issuer
78
+ cert.subject = subject
79
+ cert.sign(key, digest)
80
+
81
+ return cert, key
82
+ end
83
+ end
84
+ end
85
+ end