Module: Chef::Mixin::OpenSSLHelper

Overview

various helpers for use with openssl. Currently used by the openssl_* resources

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(_base) ⇒ Object



22
23
24
# File 'lib/chef/mixin/openssl_helper.rb', line 22

def self.included(_base)
  require "openssl" unless defined?(::OpenSSL)
end

Instance Method Details

#crl_file_valid?(crl_file) ⇒ Boolean

given a crl file path see if it’s actually a crl

Parameters:

  • crl_file (String)

    the path to the crlfile

Returns:

  • (Boolean)

    is the key valid?



80
81
82
83
84
85
86
87
# File 'lib/chef/mixin/openssl_helper.rb', line 80

def crl_file_valid?(crl_file)
  begin
    ::OpenSSL::X509::CRL.new ::File.read(crl_file)
  rescue ::OpenSSL::X509::CRLError, Errno::ENOENT
    return false
  end
  true
end

#dhparam_pem_valid?(dhparam_pem_path) ⇒ Boolean

validate a dhparam file from path

Parameters:

  • dhparam_pem_path (String)

    the path to the pem file

Returns:

  • (Boolean)

    is the key valid



45
46
47
48
49
50
51
52
# File 'lib/chef/mixin/openssl_helper.rb', line 45

def dhparam_pem_valid?(dhparam_pem_path)
  # Check if the dhparam.pem file exists
  # Verify the dhparam.pem file contains a key
  return false unless ::File.exist?(dhparam_pem_path)

  dhparam = ::OpenSSL::PKey::DH.new File.read(dhparam_pem_path)
  dhparam.params_ok?
end

#encrypt_ec_key(ec_key, key_password, key_cipher) ⇒ String

generate a pem file given a cipher, key, an optional key_password

Parameters:

  • ec_key (OpenSSL::PKey::EC)

    the private key object

  • key_password (String)

    the password for the private key

  • key_cipher (String)

    the cipher to use

Returns:

Raises:

  • (TypeError)


195
196
197
198
199
200
201
202
203
# File 'lib/chef/mixin/openssl_helper.rb', line 195

def encrypt_ec_key(ec_key, key_password, key_cipher)
  raise TypeError, "ec_key must be a Ruby OpenSSL::PKey::EC object" unless ec_key.is_a?(::OpenSSL::PKey::EC)
  raise TypeError, "key_password must be a string" unless key_password.is_a?(String)
  raise TypeError, "key_cipher must be a string" unless key_cipher.is_a?(String)
  raise ArgumentError, "Specified key_cipher is not available on this system" unless ::OpenSSL::Cipher.ciphers.include?(key_cipher)

  cipher = ::OpenSSL::Cipher.new(key_cipher)
  ec_key.to_pem(cipher, key_password)
end

#encrypt_rsa_key(rsa_key, key_password, key_cipher) ⇒ String

generate a pem file given a cipher, key, an optional key_password

Parameters:

  • rsa_key (OpenSSL::PKey::RSA)

    the private key object

  • key_password (String)

    the password for the private key

  • key_cipher (String)

    the cipher to use

Returns:

Raises:

  • (TypeError)


146
147
148
149
150
151
152
153
154
# File 'lib/chef/mixin/openssl_helper.rb', line 146

def encrypt_rsa_key(rsa_key, key_password, key_cipher)
  raise TypeError, "rsa_key must be a Ruby OpenSSL::PKey::RSA object" unless rsa_key.is_a?(::OpenSSL::PKey::RSA)
  raise TypeError, "key_password must be a string" unless key_password.is_a?(String)
  raise TypeError, "key_cipher must be a string" unless key_cipher.is_a?(String)
  raise ArgumentError, "Specified key_cipher is not available on this system" unless ::OpenSSL::Cipher.ciphers.include?(key_cipher)

  cipher = ::OpenSSL::Cipher.new(key_cipher)
  rsa_key.to_pem(cipher, key_password)
end

#gen_dhparam(key_length, generator) ⇒ OpenSSL::PKey::DH

generate a dhparam file

Parameters:

  • key_length (String)

    the length of the key

  • generator (Integer)

    the dhparam generator to use

Returns:

  • (OpenSSL::PKey::DH)

Raises:

  • (ArgumentError)


113
114
115
116
117
118
# File 'lib/chef/mixin/openssl_helper.rb', line 113

def gen_dhparam(key_length, generator)
  raise ArgumentError, "Key length must be a power of 2 greater than or equal to 1024" unless key_length_valid?(key_length)
  raise TypeError, "Generator must be an integer" unless generator.is_a?(Integer)

  ::OpenSSL::PKey::DH.new(key_length, generator)
end

#gen_ec_priv_key(curve) ⇒ OpenSSL::PKey::DH

generate an ec private key given curve type

Parameters:

  • curve (String)

    the kind of curve to use

Returns:

  • (OpenSSL::PKey::DH)

Raises:

  • (TypeError)


159
160
161
162
163
164
# File 'lib/chef/mixin/openssl_helper.rb', line 159

def gen_ec_priv_key(curve)
  raise TypeError, "curve must be a string" unless curve.is_a?(String)
  raise ArgumentError, "Specified curve is not available on this system" unless curve == "prime256v1" || curve == "secp384r1" || curve == "secp521r1"

  ::OpenSSL::PKey::EC.new(curve).generate_key
end

#gen_ec_pub_key(priv_key, priv_key_password = nil) ⇒ String

generate pem format of the public key given a private key

Parameters:

  • priv_key (String)

    either the contents of the private key or the path to the file

  • priv_key_password (String) (defaults to: nil)

    optional password for the private key

Returns:

  • (String)

    pem format of the public key



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/chef/mixin/openssl_helper.rb', line 170

def gen_ec_pub_key(priv_key, priv_key_password = nil)
  # if the file exists try to read the content
  # if not assume we were passed the key and set the string to the content
  key_content = ::File.exist?(priv_key) ? File.read(priv_key) : priv_key
  key = ::OpenSSL::PKey::EC.new key_content, priv_key_password

  # Get curve type (prime256v1...)
  group = ::OpenSSL::PKey::EC::Group.new(key.group.curve_name)
  # Get Generator point & public point (priv * generator)
  generator = group.generator
  pub_point = generator.mul(key.private_key)
  key.public_key = pub_point

  # Public Key in pem
  public_key = ::OpenSSL::PKey::EC.new
  public_key.group = group
  public_key.public_key = pub_point
  public_key.to_pem
end

#gen_rsa_priv_key(key_length) ⇒ OpenSSL::PKey::DH

generate an RSA private key given key length

Parameters:

  • key_length (Integer)

    the key length of the private key

Returns:

  • (OpenSSL::PKey::DH)

Raises:

  • (ArgumentError)


123
124
125
126
127
# File 'lib/chef/mixin/openssl_helper.rb', line 123

def gen_rsa_priv_key(key_length)
  raise ArgumentError, "Key length must be a power of 2 greater than or equal to 1024" unless key_length_valid?(key_length)

  ::OpenSSL::PKey::RSA.new(key_length)
end

#gen_rsa_pub_key(priv_key, priv_key_password = nil) ⇒ String

generate pem format of the public key given a private key

Parameters:

  • priv_key (String)

    either the contents of the private key or the path to the file

  • priv_key_password (String) (defaults to: nil)

    optional password for the private key

Returns:

  • (String)

    pem format of the public key



133
134
135
136
137
138
139
# File 'lib/chef/mixin/openssl_helper.rb', line 133

def gen_rsa_pub_key(priv_key, priv_key_password = nil)
  # if the file exists try to read the content
  # if not assume we were passed the key and set the string to the content
  key_content = ::File.exist?(priv_key) ? File.read(priv_key) : priv_key
  key = ::OpenSSL::PKey::RSA.new key_content, priv_key_password
  key.public_key.to_pem
end

#gen_serialInteger

generate a random Serial

Returns:

  • (Integer)


245
246
247
# File 'lib/chef/mixin/openssl_helper.rb', line 245

def gen_serial
  ::OpenSSL::BN.generate_prime(160)
end

#gen_x509_cert(request, extension, info, key) ⇒ OpenSSL::X509::Certificate

generate a Certificate given a X509 request

Parameters:

  • request (OpenSSL::X509::Request)

    X509 Certificate Request

  • extension (Array)

    Array of X509 Certificate Extension

  • info (Hash)

    issuer & validity

  • key (OpenSSL::PKey::EC, OpenSSL::PKey::RSA)

    private key to sign with

Returns:

  • (OpenSSL::X509::Certificate)

Raises:

  • (TypeError)


255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/chef/mixin/openssl_helper.rb', line 255

def gen_x509_cert(request, extension, info, key)
  raise TypeError, "request must be a Ruby OpenSSL::X509::Request" unless request.is_a?(::OpenSSL::X509::Request)
  raise TypeError, "extension must be a Ruby Array" unless extension.is_a?(Array)
  raise TypeError, "info must be a Ruby Hash" unless info.is_a?(Hash)
  raise TypeError, "key must be a Ruby OpenSSL::PKey::EC object or a Ruby OpenSSL::PKey::RSA object" unless key.is_a?(::OpenSSL::PKey::EC) || key.is_a?(::OpenSSL::PKey::RSA)

  raise ArgumentError, "info must contain a validity" unless info.key?("validity")
  raise TypeError, "info['validity'] must be a Ruby Integer object" unless info["validity"].is_a?(Integer)

  cert = ::OpenSSL::X509::Certificate.new
  ef = ::OpenSSL::X509::ExtensionFactory.new

  cert.serial = gen_serial
  cert.version = 2
  cert.subject = request.subject
  cert.public_key = request.public_key
  cert.not_before = Time.now
  cert.not_after = cert.not_before + info["validity"] * 24 * 60 * 60

  if info["issuer"].nil?
    cert.issuer = request.subject
    ef.issuer_certificate = cert
    extension << ef.create_extension("basicConstraints", "CA:TRUE", true)
  else
    raise TypeError, "info['issuer'] must be a Ruby OpenSSL::X509::Certificate object" unless info["issuer"].is_a?(::OpenSSL::X509::Certificate)

    cert.issuer = info["issuer"].subject
    ef.issuer_certificate = info["issuer"]
  end
  ef.subject_certificate = cert
  ef.config = ::OpenSSL::Config.load(::OpenSSL::Config::DEFAULT_CONFIG_FILE)

  cert.extensions = extension
  cert.add_extension ef.create_extension("subjectKeyIdentifier", "hash")
  cert.add_extension ef.create_extension("authorityKeyIdentifier",
    "keyid:always,issuer:always")

  cert.sign(key, ::OpenSSL::Digest::SHA256.new)
  cert
end

#gen_x509_crl(ca_private_key, info) ⇒ OpenSSL::X509::CRL

generate a X509 CRL given a CA

Parameters:

  • ca_private_key (OpenSSL::PKey::EC, OpenSSL::PKey::RSA)

    private key from the CA

  • info (Hash)

    issuer & validity

Returns:

  • (OpenSSL::X509::CRL)

Raises:

  • (TypeError)


300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/chef/mixin/openssl_helper.rb', line 300

def gen_x509_crl(ca_private_key, info)
  raise TypeError, "ca_private_key must be a Ruby OpenSSL::PKey::EC object or a Ruby OpenSSL::PKey::RSA object" unless ca_private_key.is_a?(::OpenSSL::PKey::EC) || ca_private_key.is_a?(::OpenSSL::PKey::RSA)
  raise TypeError, "info must be a Ruby Hash" unless info.is_a?(Hash)

  raise ArgumentError, "info must contain a issuer and a validity" unless info.key?("issuer") && info.key?("validity")
  raise TypeError, "info['issuer'] must be a Ruby OpenSSL::X509::Certificate object" unless info["issuer"].is_a?(::OpenSSL::X509::Certificate)
  raise TypeError, "info['validity'] must be a Ruby Integer object" unless info["validity"].is_a?(Integer)

  crl = ::OpenSSL::X509::CRL.new
  ef = ::OpenSSL::X509::ExtensionFactory.new

  crl.version = 1
  crl.issuer = info["issuer"].subject
  crl.last_update = Time.now
  crl.next_update = Time.now + 3600 * 24 * info["validity"]

  ef.config = ::OpenSSL::Config.load(::OpenSSL::Config::DEFAULT_CONFIG_FILE)
  ef.issuer_certificate = info["issuer"]

  crl.add_extension ::OpenSSL::X509::Extension.new("crlNumber", ::OpenSSL::ASN1::Integer(1))
  crl.add_extension ef.create_extension("authorityKeyIdentifier",
    "keyid:always,issuer:always")
  crl.sign(ca_private_key, ::OpenSSL::Digest::SHA256.new)
  crl
end

#gen_x509_extensions(extensions) ⇒ Array

generate an array of X509 Extensions given a hash of extensions

Parameters:

  • extensions (Hash)

    hash of extensions

Returns:

  • (Array)

Raises:

  • (TypeError)


228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/chef/mixin/openssl_helper.rb', line 228

def gen_x509_extensions(extensions)
  raise TypeError, "extensions must be a Ruby Hash object" unless extensions.is_a?(Hash)

  exts = []
  extensions.each do |ext_name, ext_prop|
    raise TypeError, "#{ext_name} must contain a Ruby Hash" unless ext_prop.is_a?(Hash)
    raise ArgumentError, "keys in #{ext_name} must be 'values' and 'critical'" unless ext_prop.key?("values") && ext_prop.key?("critical")
    raise TypeError, "the key 'values' must contain a Ruby Arrays" unless ext_prop["values"].is_a?(Array)
    raise TypeError, "the key 'critical' must be a Ruby Boolean true/false" unless ext_prop["critical"].is_a?(TrueClass) || ext_prop["critical"].is_a?(FalseClass)

    exts << ::OpenSSL::X509::ExtensionFactory.new.create_extension(ext_name, ext_prop["values"].join(","), ext_prop["critical"])
  end
  exts
end

#gen_x509_request(subject, key) ⇒ OpenSSL::X509::Request

generate a csr pem file given a subject and a private key

Parameters:

  • subject (OpenSSL::X509::Name)

    the x509 subject object

  • key (OpenSSL::PKey::EC, OpenSSL::PKey::RSA)

    the private key object

Returns:

  • (OpenSSL::X509::Request)

Raises:

  • (TypeError)


209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/chef/mixin/openssl_helper.rb', line 209

def gen_x509_request(subject, key)
  raise TypeError, "subject must be a Ruby OpenSSL::X509::Name object" unless subject.is_a?(::OpenSSL::X509::Name)
  raise TypeError, "key must be a Ruby OpenSSL::PKey::EC or a Ruby OpenSSL::PKey::RSA object" unless key.is_a?(::OpenSSL::PKey::EC) || key.is_a?(::OpenSSL::PKey::RSA)

  request = ::OpenSSL::X509::Request.new
  request.version = 0
  request.subject = subject
  request.public_key = key

  # Chef 12 backward compatibility
  ::OpenSSL::PKey::EC.send(:alias_method, :private?, :private_key?)

  request.sign(key, ::OpenSSL::Digest::SHA256.new)
  request
end

#get_key_filename(cert_filename) ⇒ String

determine the key filename from the cert filename

Parameters:

  • cert_filename (String)

    the path to the certfile

Returns:

  • (String)

    the path to the keyfile



29
30
31
32
33
# File 'lib/chef/mixin/openssl_helper.rb', line 29

def get_key_filename(cert_filename)
  cert_file_path, cert_filename = ::File.split(cert_filename)
  cert_filename = ::File.basename(cert_filename, ::File.extname(cert_filename))
  cert_file_path + ::File::SEPARATOR + cert_filename + ".key"
end

#get_next_crl_number(crl) ⇒ Integer

generate the next CRL number available for a X509 CRL given

Parameters:

  • crl (OpenSSL::X509::CRL)

    x509 CRL

Returns:

  • (Integer)

Raises:

  • (TypeError)


329
330
331
332
333
334
335
336
337
# File 'lib/chef/mixin/openssl_helper.rb', line 329

def get_next_crl_number(crl)
  raise TypeError, "crl must be a Ruby OpenSSL::X509::CRL object" unless crl.is_a?(::OpenSSL::X509::CRL)

  crlnum = 1
  crl.extensions.each do |e|
    crlnum = e.value if e.oid == "crlNumber"
  end
  crlnum.to_i + 1
end

#key_length_valid?(number) ⇒ Boolean

is the key length a valid key length

Parameters:

  • number (Integer)

Returns:

  • (Boolean)

    is length valid



38
39
40
# File 'lib/chef/mixin/openssl_helper.rb', line 38

def key_length_valid?(number)
  number >= 1024 && ( number & (number - 1) == 0 )
end

#priv_key_file_valid?(key_file, key_password = nil) ⇒ Boolean

given either a key file path or key file content see if it’s actually a private key

Parameters:

  • key_file (String)

    the path to the keyfile or the key contents

  • key_password (String) (defaults to: nil)

    optional password to the keyfile

Returns:

  • (Boolean)

    is the key valid?



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/chef/mixin/openssl_helper.rb', line 59

def priv_key_file_valid?(key_file, key_password = nil)
  # if the file exists try to read the content
  # if not assume we were passed the key and set the string to the content
  key_content = ::File.exist?(key_file) ? File.read(key_file) : key_file

  begin
    key = ::OpenSSL::PKey.read key_content, key_password
  rescue ::OpenSSL::PKey::PKeyError, ArgumentError
    return false
  end

  if key.is_a?(::OpenSSL::PKey::EC)
    key.private_key?
  else
    key.private?
  end
end

#renew_x509_crl(crl, ca_private_key, info) ⇒ OpenSSL::X509::CRL

renew a X509 crl given

Parameters:

  • crl (OpenSSL::X509::CRL)

    CRL to renew

  • ca_private_key (OpenSSL::PKey::EC, OpenSSL::PKey::RSA)

    private key from the CA

  • info (Hash)

    issuer & validity

Returns:

  • (OpenSSL::X509::CRL)

Raises:

  • (TypeError)


381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
# File 'lib/chef/mixin/openssl_helper.rb', line 381

def renew_x509_crl(crl, ca_private_key, info)
  raise TypeError, "crl must be a Ruby OpenSSL::X509::CRL object" unless crl.is_a?(::OpenSSL::X509::CRL)
  raise TypeError, "ca_private_key must be a Ruby OpenSSL::PKey::EC object or a Ruby OpenSSL::PKey::RSA object" unless ca_private_key.is_a?(::OpenSSL::PKey::EC) || ca_private_key.is_a?(::OpenSSL::PKey::RSA)
  raise TypeError, "info must be a Ruby Hash" unless info.is_a?(Hash)

  raise ArgumentError, "info must contain a issuer and a validity" unless info.key?("issuer") && info.key?("validity")
  raise TypeError, "info['issuer'] must be a Ruby OpenSSL::X509::Certificate object" unless info["issuer"].is_a?(::OpenSSL::X509::Certificate)
  raise TypeError, "info['validity'] must be a Ruby Integer object" unless info["validity"].is_a?(Integer)

  crl.last_update = Time.now
  crl.next_update = crl.last_update + 3600 * 24 * info["validity"]

  ef = ::OpenSSL::X509::ExtensionFactory.new
  ef.config = ::OpenSSL::Config.load(::OpenSSL::Config::DEFAULT_CONFIG_FILE)
  ef.issuer_certificate = info["issuer"]

  crl.extensions = [ ::OpenSSL::X509::Extension.new("crlNumber",
    ::OpenSSL::ASN1::Integer(get_next_crl_number(crl)))]
  crl.add_extension ef.create_extension("authorityKeyIdentifier",
    "keyid:always,issuer:always")
  crl.sign(ca_private_key, ::OpenSSL::Digest::SHA256.new)
  crl
end

#revoke_x509_crl(revoke_info, crl, ca_private_key, info) ⇒ OpenSSL::X509::CRL

add a serial given in the crl given

Parameters:

  • revoke_info (Hash)

    serial to revoke & revokation reason

  • crl (OpenSSL::X509::CRL)

    X509 CRL

  • ca_private_key (OpenSSL::PKey::EC, OpenSSL::PKey::RSA)

    private key from the CA

  • info (Hash)

    issuer & validity

Returns:

  • (OpenSSL::X509::CRL)

Raises:

  • (TypeError)


345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/chef/mixin/openssl_helper.rb', line 345

def revoke_x509_crl(revoke_info, crl, ca_private_key, info)
  raise TypeError, "revoke_info must be a Ruby Hash oject" unless revoke_info.is_a?(Hash)
  raise TypeError, "crl must be a Ruby OpenSSL::X509::CRL object" unless crl.is_a?(::OpenSSL::X509::CRL)
  raise TypeError, "ca_private_key must be a Ruby OpenSSL::PKey::EC object or a Ruby OpenSSL::PKey::RSA object" unless ca_private_key.is_a?(::OpenSSL::PKey::EC) || ca_private_key.is_a?(::OpenSSL::PKey::RSA)
  raise TypeError, "info must be a Ruby Hash" unless info.is_a?(Hash)

  raise ArgumentError, "revoke_info must contain a serial and a reason" unless revoke_info.key?("serial") && revoke_info.key?("reason")
  raise TypeError, "revoke_info['serial'] must be a Ruby String or Integer object" unless revoke_info["serial"].is_a?(String) || revoke_info["serial"].is_a?(Integer)
  raise TypeError, "revoke_info['reason'] must be a Ruby Integer object" unless revoke_info["reason"].is_a?(Integer)

  raise ArgumentError, "info must contain a issuer and a validity" unless info.key?("issuer") && info.key?("validity")
  raise TypeError, "info['issuer'] must be a Ruby OpenSSL::X509::Certificate object" unless info["issuer"].is_a?(::OpenSSL::X509::Certificate)
  raise TypeError, "info['validity'] must be a Ruby Integer object" unless info["validity"].is_a?(Integer)

  revoked = ::OpenSSL::X509::Revoked.new
  revoked.serial = if revoke_info["serial"].is_a?(String)
                     revoke_info["serial"].to_i(16)
                   else
                     revoke_info["serial"]
                   end
  revoked.time = Time.now

  ext = ::OpenSSL::X509::Extension.new("CRLReason",
    ::OpenSSL::ASN1::Enumerated(revoke_info["reason"]))
  revoked.add_extension(ext)
  crl.add_revoked(revoked)

  crl = renew_x509_crl(crl, ca_private_key, info)
  crl
end

#serial_revoked?(crl, serial) ⇒ true, false

check is a serial given is revoked in a crl given

Parameters:

  • crl (OpenSSL::X509::CRL)

    X509 CRL to check

  • serial (String, Integer)

    X509 Certificate Serial Number

Returns:

  • (true, false)

Raises:

  • (TypeError)


93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/chef/mixin/openssl_helper.rb', line 93

def serial_revoked?(crl, serial)
  raise TypeError, "crl must be a Ruby OpenSSL::X509::CRL object" unless crl.is_a?(::OpenSSL::X509::CRL)
  raise TypeError, "serial must be a Ruby String or Integer object" unless serial.is_a?(String) || serial.is_a?(Integer)

  serial_to_verify = if serial.is_a?(String)
                       serial.to_i(16)
                     else
                       serial
                     end
  status = false
  crl.revoked.each do |revoked|
    status = true if revoked.serial == serial_to_verify
  end
  status
end