fluent-plugin-secure-forward 0.2.6 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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