summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Smith <tsmith@chef.io>2018-08-16 19:06:50 -0700
committerGitHub <noreply@github.com>2018-08-16 19:06:50 -0700
commit9971a5b13528d89c2d7ea5c1e2a04ee5bc7d3cb5 (patch)
tree0b49021c2a03239e023c47cc946ab7fabfb0728e
parent49fd808d7e290a72de1d45c258fde78acf8782d8 (diff)
parent567b82e91cb4ae1b72eec8632d1712dd8608ebcc (diff)
downloadchef-9971a5b13528d89c2d7ea5c1e2a04ee5bc7d3cb5.tar.gz
Merge pull request #7513 from chef/openssl_new_stuff
Add new openssl resources: ec_private_key, ec_public_key, certificate, and x509_request
-rw-r--r--kitchen-tests/kitchen.travis.yml3
-rw-r--r--lib/chef/mixin/openssl_helper.rb289
-rw-r--r--lib/chef/resource/openssl_dhparam.rb2
-rw-r--r--lib/chef/resource/openssl_ec_private_key.rb92
-rw-r--r--lib/chef/resource/openssl_ec_public_key.rb74
-rw-r--r--lib/chef/resource/openssl_rsa_private_key.rb8
-rw-r--r--lib/chef/resource/openssl_rsa_public_key.rb4
-rw-r--r--lib/chef/resource/openssl_x509_certificate.rb220
-rw-r--r--lib/chef/resource/openssl_x509_request.rb149
-rw-r--r--lib/chef/resources.rb4
-rw-r--r--spec/unit/mixin/openssl_helper_spec.rb627
-rw-r--r--spec/unit/resource/openssl_ec_private_key_spec.rb64
-rw-r--r--spec/unit/resource/openssl_ec_public_key_spec.rb43
-rw-r--r--spec/unit/resource/openssl_openssl_x509_certificate_spec.rb71
-rw-r--r--spec/unit/resource/openssl_openssl_x509_request.rb67
15 files changed, 1691 insertions, 26 deletions
diff --git a/kitchen-tests/kitchen.travis.yml b/kitchen-tests/kitchen.travis.yml
index 83c6ee839d..aa15da8795 100644
--- a/kitchen-tests/kitchen.travis.yml
+++ b/kitchen-tests/kitchen.travis.yml
@@ -35,7 +35,6 @@ platforms:
pid_one_command: /sbin/init
intermediate_instructions:
- RUN sed -i -e "s/Defaults.*requiretty.*/Defaults !requiretty/g" /etc/sudoers
- - RUN yum install -y cronie # TODO: remove this anytime past 8/13
- name: amazonlinux-2
driver:
@@ -64,7 +63,6 @@ platforms:
pid_one_command: /sbin/init
intermediate_instructions:
- RUN sed -i -e "s/Defaults.*requiretty.*/Defaults !requiretty/g" /etc/sudoers
- - RUN yum install -y cronie # TODO: remove this anytime past 8/13
- name: centos-7
driver:
@@ -80,7 +78,6 @@ platforms:
pid_one_command: /usr/lib/systemd/systemd
intermediate_instructions:
- RUN sed -i -e "s/Defaults.*requiretty.*/Defaults !requiretty/g" /etc/sudoers
- - RUN yum install -y cronie # TODO: remove this anytime past 8/13
- name: ubuntu-14.04
driver:
diff --git a/lib/chef/mixin/openssl_helper.rb b/lib/chef/mixin/openssl_helper.rb
index f12a559097..79ab9596e5 100644
--- a/lib/chef/mixin/openssl_helper.rb
+++ b/lib/chef/mixin/openssl_helper.rb
@@ -61,11 +61,48 @@ class Chef
key_content = ::File.exist?(key_file) ? File.read(key_file) : key_file
begin
- key = ::OpenSSL::PKey::RSA.new key_content, key_password
- rescue ::OpenSSL::PKey::RSAError
+ key = ::OpenSSL::PKey.read key_content, key_password
+ rescue ::OpenSSL::PKey::PKeyError, ArgumentError
return false
end
- key.private?
+
+ if key.is_a?(::OpenSSL::PKey::EC)
+ key.private_key?
+ else
+ key.private?
+ end
+ end
+
+ # given a crl file path see if it's actually a crl
+ # @param [String] crl_file the path to the crlfile
+ # @return [Boolean] is the key valid?
+ 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
+
+ # check is a serial given is revoked in a crl given
+ # @param [OpenSSL::X509::CRL] crl X509 CRL to check
+ # @param [String, Integer] serial X509 Certificate Serial Number
+ # @return [true, false]
+ 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
# generate a dhparam file
@@ -114,6 +151,252 @@ class Chef
cipher = ::OpenSSL::Cipher.new(key_cipher)
rsa_key.to_pem(cipher, key_password)
end
+
+ # generate an ec private key given curve type
+ # @param [String] curve the kind of curve to use
+ # @return [OpenSSL::PKey::DH]
+ 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
+
+ # generate pem format of the public key given a private key
+ # @param [String] priv_key either the contents of the private key or the path to the file
+ # @param [String] priv_key_password optional password for the private key
+ # @return [String] pem format of the public key
+ 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
+
+ # generate a pem file given a cipher, key, an optional key_password
+ # @param [OpenSSL::PKey::EC] ec_key the private key object
+ # @param [String] key_password the password for the private key
+ # @param [String] key_cipher the cipher to use
+ # @return [String] pem contents
+ 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
+
+ # generate a csr pem file given a subject and a private key
+ # @param [OpenSSL::X509::Name] subject the x509 subject object
+ # @param [OpenSSL::PKey::EC, OpenSSL::PKey::RSA] key the private key object
+ # @return [OpenSSL::X509::Request]
+ 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
+
+ # generate an array of X509 Extensions given a hash of extensions
+ # @param [Hash] extensions hash of extensions
+ # @return [Array]
+ 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
+
+ # generate a random Serial
+ # @return [Integer]
+ def gen_serial
+ ::OpenSSL::BN.generate_prime(160)
+ end
+
+ # generate a Certificate given a X509 request
+ # @param [OpenSSL::X509::Request] request X509 Certificate Request
+ # @param [Array] extension Array of X509 Certificate Extension
+ # @param [Hash] info issuer & validity
+ # @param [OpenSSL::PKey::EC, OpenSSL::PKey::RSA] key private key to sign with
+ # @return [OpenSSL::X509::Certificate]
+ 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
+
+ # generate a X509 CRL given a CA
+ # @param [OpenSSL::PKey::EC, OpenSSL::PKey::RSA] ca_private_key private key from the CA
+ # @param [Hash] info issuer & validity
+ # @return [OpenSSL::X509::CRL]
+ 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
+
+ # generate the next CRL number available for a X509 CRL given
+ # @param [OpenSSL::X509::CRL] crl x509 CRL
+ # @return [Integer]
+ 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
+
+ # add a serial given in the crl given
+ # @param [Hash] revoke_info serial to revoke & revokation reason
+ # @param [OpenSSL::X509::CRL] crl X509 CRL
+ # @param [OpenSSL::PKey::EC, OpenSSL::PKey::RSA] ca_private_key private key from the CA
+ # @param [Hash] info issuer & validity
+ # @return [OpenSSL::X509::CRL]
+ 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
+
+ # renew a X509 crl given
+ # @param [OpenSSL::X509::CRL] crl CRL to renew
+ # @param [OpenSSL::PKey::EC, OpenSSL::PKey::RSA] ca_private_key private key from the CA
+ # @param [Hash] info issuer & validity
+ # @return [OpenSSL::X509::CRL]
+ 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
end
end
end
diff --git a/lib/chef/resource/openssl_dhparam.rb b/lib/chef/resource/openssl_dhparam.rb
index b7bc8438f2..a1fa3af7e4 100644
--- a/lib/chef/resource/openssl_dhparam.rb
+++ b/lib/chef/resource/openssl_dhparam.rb
@@ -65,7 +65,7 @@ class Chef
converge_by("Create a dhparam file #{new_resource.path}") do
dhparam_content = gen_dhparam(new_resource.key_length, new_resource.generator).to_pem
- declare_resource(:file, new_resource.path) do
+ file new_resource.path do
action :create
owner new_resource.owner unless new_resource.owner.nil?
group new_resource.group unless new_resource.group.nil?
diff --git a/lib/chef/resource/openssl_ec_private_key.rb b/lib/chef/resource/openssl_ec_private_key.rb
new file mode 100644
index 0000000000..37dd61162f
--- /dev/null
+++ b/lib/chef/resource/openssl_ec_private_key.rb
@@ -0,0 +1,92 @@
+#
+# Copyright:: Copyright 2018, Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/resource"
+
+class Chef
+ class Resource
+ class OpensslEcPrivateKey < Chef::Resource
+ require "chef/mixin/openssl_helper"
+ include Chef::Mixin::OpenSSLHelper
+
+ preview_resource true
+ resource_name :openssl_ec_private_key
+
+ description "Use the openssl_ec_private_key resource to generate generate ec private key files. If a valid ec key file can be opened at the specified location, no new file will be created. If the EC key file cannot be opened, either because it does not exist or because the password to the EC key file does not match the password in the recipe, it will be overwritten."
+ introduced "14.4"
+
+ property :path, String,
+ description: "The path to write the file to it's different than the resource name.",
+ name_property: true
+
+ property :key_curve, String,
+ equal_to: %w{secp384r1 secp521r1 prime256v1 secp224r1 secp256k1},
+ description: "The desired curve of the generated key (if key_type is equal to 'ec'). Run openssl ecparam -list_curves to see available options.",
+ default: "prime256v1"
+
+ property :key_pass, String,
+ description: "The desired passphrase for the key."
+
+ property :key_cipher, String,
+ equal_to: OpenSSL::Cipher.ciphers,
+ validation_message: "key_cipher must be a cipher known to openssl. Run `openssl list-cipher-algorithms` to see available options.",
+ description: "The designed cipher to use when generating your key. Run `openssl list-cipher-algorithms` to see available options.",
+ default: "des3"
+
+ property :owner, String,
+ description: "The owner of all files created by the resource."
+
+ property :group, String,
+ description: "The group of all files created by the resource."
+
+ property :mode, [Integer, String],
+ description: "The permission mode of all files created by the resource.",
+ default: "0600"
+
+ property :force, [TrueClass, FalseClass],
+ description: "Force creating the key even if the existing key exists.",
+ default: false, desired_state: false
+
+ action :create do
+ description "Generate the ec private key"
+
+ unless new_resource.force || priv_key_file_valid?(new_resource.path, new_resource.key_pass)
+ converge_by("Create an EC private key #{new_resource.path}") do
+ log "Generating an #{new_resource.key_curve} "\
+ "EC key file at #{new_resource.name}, this may take some time"
+
+ if new_resource.key_pass
+ unencrypted_ec_key = gen_ec_priv_key(new_resource.key_curve)
+ ec_key_content = encrypt_ec_key(unencrypted_ec_key, new_resource.key_pass, new_resource.key_cipher)
+ else
+ ec_key_content = gen_ec_priv_key(new_resource.key_curve).to_pem
+ end
+
+ file new_resource.path do
+ action :create
+ owner new_resource.owner unless new_resource.owner.nil?
+ group new_resource.group unless new_resource.group.nil?
+ mode new_resource.mode
+ sensitive true
+ content ec_key_content
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/openssl_ec_public_key.rb b/lib/chef/resource/openssl_ec_public_key.rb
new file mode 100644
index 0000000000..50c3bd192c
--- /dev/null
+++ b/lib/chef/resource/openssl_ec_public_key.rb
@@ -0,0 +1,74 @@
+#
+# Copyright:: Copyright 2018, Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/resource"
+
+class Chef
+ class Resource
+ class OpensslEcPublicKey < Chef::Resource
+ require "chef/mixin/openssl_helper"
+ include Chef::Mixin::OpenSSLHelper
+
+ preview_resource true
+ resource_name :openssl_ec_public_key
+
+ description "Use the openssl_ec_public_key resource to generate ec public key files given a private key."
+ introduced "14.4"
+
+ property :path, String,
+ description: "The path to write the file to if different than the resource's name.",
+ name_property: true
+
+ property :private_key_path, String,
+ description: "The path to the private key."
+
+ property :private_key_content, String,
+ description: "The content of the private key including new lines. Used instead of private_key_path to avoid having to first write a key to disk."
+
+ property :private_key_pass, String,
+ description: "The passphrase of the provided private key."
+
+ property :owner, String,
+ description: "The owner of all files created by the resource."
+
+ property :group, String,
+ description: "The group of all files created by the resource."
+
+ property :mode, [Integer, String],
+ description: "The permission mode of all files created by the resource.",
+ default: "0640"
+
+ action :create do
+ description "Generate the ec public key from a private key"
+
+ raise ArgumentError, "You cannot specify both 'private_key_path' and 'private_key_content' properties at the same time." if new_resource.private_key_path && new_resource.private_key_content
+ raise ArgumentError, "You must specify the private key with either 'private_key_path' or 'private_key_content' properties." unless new_resource.private_key_path || new_resource.private_key_content
+ raise "#{new_resource.private_key_path} not a valid private EC key or password is invalid" unless priv_key_file_valid?((new_resource.private_key_path || new_resource.private_key_content), new_resource.private_key_pass)
+
+ ec_key_content = gen_ec_pub_key((new_resource.private_key_path || new_resource.private_key_content), new_resource.private_key_pass)
+
+ file new_resource.path do
+ action :create
+ owner new_resource.owner unless new_resource.owner.nil?
+ group new_resource.group unless new_resource.group.nil?
+ mode new_resource.mode
+ content ec_key_content
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/openssl_rsa_private_key.rb b/lib/chef/resource/openssl_rsa_private_key.rb
index 1bc8f73f47..add8587f3c 100644
--- a/lib/chef/resource/openssl_rsa_private_key.rb
+++ b/lib/chef/resource/openssl_rsa_private_key.rb
@@ -27,11 +27,7 @@ class Chef
provides(:openssl_rsa_private_key) { true }
provides(:openssl_rsa_key) { true } # legacy cookbook resource name
- description "Use the openssl_rsa_private_key resource to generate RSA private key files."\
- " If a valid RSA key file can be opened at the specified location, no new file"\
- " will be created. If the RSA key file cannot be opened, either because it does"\
- " not exist or because the password to the RSA key file does not match the"\
- " password in the recipe, it will be overwritten."
+ description "Use the openssl_rsa_private_key resource to generate RSA private key files. If a valid RSA key file can be opened at the specified location, no new file will be created. If the RSA key file cannot be opened, either because it does not exist or because the password to the RSA key file does not match the password in the recipe, it will be overwritten."
introduced "14.0"
property :path, String,
@@ -80,7 +76,7 @@ class Chef
rsa_key_content = gen_rsa_priv_key(new_resource.key_length).to_pem
end
- declare_resource(:file, new_resource.path) do
+ file new_resource.path do
action :create
owner new_resource.owner unless new_resource.owner.nil?
group new_resource.group unless new_resource.group.nil?
diff --git a/lib/chef/resource/openssl_rsa_public_key.rb b/lib/chef/resource/openssl_rsa_public_key.rb
index fa00404999..05c38d70df 100644
--- a/lib/chef/resource/openssl_rsa_public_key.rb
+++ b/lib/chef/resource/openssl_rsa_public_key.rb
@@ -26,7 +26,7 @@ class Chef
resource_name :openssl_rsa_public_key
provides(:openssl_rsa_public_key) { true }
- description "Use the openssl_rsa_public_key resource to generate RSA public key files given a RSA private key"
+ description "Use the openssl_rsa_public_key resource to generate RSA public key files given a RSA private key."
introduced "14.0"
property :path, String,
@@ -61,7 +61,7 @@ class Chef
rsa_key_content = gen_rsa_pub_key((new_resource.private_key_path || new_resource.private_key_content), new_resource.private_key_pass)
- declare_resource(:file, new_resource.path) do
+ file new_resource.path do
action :create
owner new_resource.owner unless new_resource.owner.nil?
group new_resource.group unless new_resource.group.nil?
diff --git a/lib/chef/resource/openssl_x509_certificate.rb b/lib/chef/resource/openssl_x509_certificate.rb
new file mode 100644
index 0000000000..04641e877b
--- /dev/null
+++ b/lib/chef/resource/openssl_x509_certificate.rb
@@ -0,0 +1,220 @@
+#
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/resource"
+
+class Chef
+ class Resource
+ class OpensslX509Certificate < Chef::Resource
+ require "chef/mixin/openssl_helper"
+ include Chef::Mixin::OpenSSLHelper
+
+ preview_resource true
+ resource_name :openssl_x509_certificate
+ provides(:openssl_x509) { true } # legacy cookbook name. Cookbook will win. @todo Make this true in Chef 15
+
+ description "Use the openssl_x509_certificate resource to generate signed or self-signed, PEM-formatted x509 certificates. If no existing key is specified, the resource will automatically generate a passwordless key with the certificate. If a CA private key and certificate are provided, the certificate will be signed with them. Note: This resource was renamed from openssl_x509 to openssl_x509_certificate. The legacy name will continue to function, but cookbook code should be updated for the new resource name."
+ introduced "14.4"
+
+ property :path, String,
+ description: "Optional path to write the file to if you'd like to specify it here instead of in the resource name.",
+ name_property: true
+
+ property :owner, String,
+ description: "The owner of all files created by the resource."
+
+ property :group, String,
+ description: "The group of all files created by the resource."
+
+ property :expire, Integer,
+ description: "Value representing the number of days from now through which the issued certificate cert will remain valid. The certificate will expire after this period.",
+ default: 365
+
+ property :mode, [Integer, String],
+ description: "The permission mode of all files created by the resource.",
+ default: "0644"
+
+ property :country, String,
+ description: "Value for the C ssl field."
+
+ property :state, String,
+ description: "Value for the ST certificate field."
+
+ property :city, String,
+ description: "Value for the L certificate field."
+
+ property :org, String,
+ description: "Value for the O certificate field."
+
+ property :org_unit, String,
+ description: "Value for the OU certificate field."
+
+ property :common_name, String,
+ description: "Value for the CN certificate field."
+
+ property :email, String,
+ description: "Value for the email ssl field."
+
+ property :extensions, Hash,
+ description: "Hash of X509 Extensions entries, in format { 'keyUsage' => { 'values' => %w( keyEncipherment digitalSignature), 'critical' => true } }.",
+ default: lazy { Hash.new }
+
+ property :subject_alt_name, Array,
+ description: "Array of Subject Alternative Name entries, in format DNS:example.com or IP:1.2.3.4.",
+ default: lazy { [] }
+
+ property :key_file, String,
+ description: "The path to a certificate key file on the filesystem. If the key_file attribute is specified, the resource will attempt to source a key from this location. If no key file is found, the resource will generate a new key file at this location. If the key_file attribute is not specified, the resource will generate a key file in the same directory as the generated certificate, with the same name as the generated certificate."
+
+ property :key_pass, String,
+ description: "The passphrase for an existing key's passphrase."
+
+ property :key_type, String,
+ equal_to: %w{rsa ec},
+ description: "The desired type of the generated key (rsa or ec).",
+ default: "rsa"
+
+ property :key_length, Integer,
+ equal_to: [1024, 2048, 4096, 8192],
+ description: "The desired Bit Length of the generated key (if key_type is equal to 'rsa').",
+ default: 2048
+
+ property :key_curve, String,
+ description: "The desired curve of the generated key (if key_type is equal to 'ec'). Run openssl ecparam -list_curves to see available options.",
+ equal_to: %w{secp384r1 secp521r1 prime256v1},
+ default: "prime256v1"
+
+ property :csr_file, String,
+ description: "The path to a X509 Certificate Request (CSR) on the filesystem. If the csr_file attribute is specified, the resource will attempt to source a CSR from this location. If no CSR file is found, the resource will generate a Self-Signed Certificate and the certificate fields must be specified (common_name at last)."
+
+ property :ca_cert_file, String,
+ description: "The path to the CA X509 Certificate on the filesystem. If the ca_cert_file attribute is specified, the ca_key_file attribute must also be specified, the certificate will be signed with them."
+
+ property :ca_key_file, String,
+ description: "The path to the CA private key on the filesystem. If the ca_key_file attribute is specified, the `ca_cert_file' attribute must also be specified, the certificate will be signed with them."
+
+ property :ca_key_pass, String,
+ description: "The passphrase for CA private key's passphrase."
+
+ action :create do
+ description "Generate a certificate"
+
+ unless ::File.exist? new_resource.path
+ converge_by("Create #{@new_resource}") do
+ file new_resource.path do
+ action :create_if_missing
+ mode new_resource.mode
+ owner new_resource.owner unless new_resource.owner.nil?
+ group new_resource.group unless new_resource.group.nil?
+ sensitive true
+ content cert.to_pem
+ end
+
+ if new_resource.csr_file.nil?
+ file new_resource.key_file do
+ action :create_if_missing
+ mode new_resource.mode
+ owner new_resource.owner unless new_resource.owner.nil?
+ group new_resource.group unless new_resource.group.nil?
+ sensitive true
+ content key.to_pem
+ end
+ end
+ end
+ end
+ end
+
+ action_class do
+ def generate_key_file
+ unless new_resource.key_file
+ path, file = ::File.split(new_resource.path)
+ filename = ::File.basename(file, ::File.extname(file))
+ new_resource.key_file path + "/" + filename + ".key"
+ end
+ new_resource.key_file
+ end
+
+ def key
+ @key ||= if priv_key_file_valid?(generate_key_file, new_resource.key_pass)
+ OpenSSL::PKey.read ::File.read(generate_key_file), new_resource.key_pass
+ elsif new_resource.key_type == "rsa"
+ gen_rsa_priv_key(new_resource.key_length)
+ else
+ gen_ec_priv_key(new_resource.key_curve)
+ end
+ @key
+ end
+
+ def request
+ request = if new_resource.csr_file.nil?
+ gen_x509_request(subject, key)
+ else
+ OpenSSL::X509::Request.new ::File.read(new_resource.csr_file)
+ end
+ request
+ end
+
+ def subject
+ subject = OpenSSL::X509::Name.new()
+ subject.add_entry("C", new_resource.country) unless new_resource.country.nil?
+ subject.add_entry("ST", new_resource.state) unless new_resource.state.nil?
+ subject.add_entry("L", new_resource.city) unless new_resource.city.nil?
+ subject.add_entry("O", new_resource.org) unless new_resource.org.nil?
+ subject.add_entry("OU", new_resource.org_unit) unless new_resource.org_unit.nil?
+ subject.add_entry("CN", new_resource.common_name)
+ subject.add_entry("emailAddress", new_resource.email) unless new_resource.email.nil?
+ subject
+ end
+
+ def ca_private_key
+ ca_private_key = if new_resource.csr_file.nil?
+ key
+ else
+ OpenSSL::PKey.read ::File.read(new_resource.ca_key_file), new_resource.ca_key_pass
+ end
+ ca_private_key
+ end
+
+ def ca_info
+ # Will contain issuer (if any) & expiration
+ ca_info = {}
+
+ unless new_resource.ca_cert_file.nil?
+ ca_info["issuer"] = OpenSSL::X509::Certificate.new ::File.read(new_resource.ca_cert_file)
+ end
+ ca_info["validity"] = new_resource.expire
+
+ ca_info
+ end
+
+ def extensions
+ extensions = gen_x509_extensions(new_resource.extensions)
+
+ unless new_resource.subject_alt_name.empty?
+ extensions += gen_x509_extensions("subjectAltName" => { "values" => new_resource.subject_alt_name, "critical" => false })
+ end
+
+ extensions
+ end
+
+ def cert
+ cert = gen_x509_cert(request, extensions, ca_info, ca_private_key)
+ cert
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/openssl_x509_request.rb b/lib/chef/resource/openssl_x509_request.rb
new file mode 100644
index 0000000000..a7b989d96d
--- /dev/null
+++ b/lib/chef/resource/openssl_x509_request.rb
@@ -0,0 +1,149 @@
+#
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/resource"
+
+class Chef
+ class Resource
+ class OpensslX509Request < Chef::Resource
+ require "chef/mixin/openssl_helper"
+ include Chef::Mixin::OpenSSLHelper
+
+ preview_resource true
+ resource_name :openssl_x509_request
+
+ description "Use the openssl_x509_request resource to generate PEM-formatted x509 certificates requests. If no existing key is specified, the resource will automatically generate a passwordless key with the certificate."
+ introduced "14.4"
+
+ property :path, String, name_property: true,
+ description: "The optional path to write the file to if you'd like to specify it here instead of in the resource name."
+
+ property :owner, String,
+ description: "The owner of all files created by the resource."
+
+ property :group, String,
+ description: "The group of all files created by the resource."
+
+ property :mode, [Integer, String], default: "0644",
+ description: ""
+
+ property :country, String,
+ description: "Value for the C ssl field."
+
+ property :state, String,
+ description: "Value for the ST certificate field."
+
+ property :city, String,
+ description: "Value for the L certificate field."
+
+ property :org, String,
+ description: "Value for the O certificate field."
+
+ property :org_unit, String,
+ description: "Value for the OU certificate field."
+
+ property :common_name, String,
+ required: true,
+ description: "Value for the CN certificate field."
+
+ property :email, String,
+ description: "Value for the email ssl field."
+
+ property :key_file, String,
+ description: "The path to a certificate key file on the filesystem. If the key_file attribute is specified, the resource will attempt to source a key from this location. If no key file is found, the resource will generate a new key file at this location. If the key_file attribute is not specified, the resource will generate a key file in the same directory as the generated certificate, with the same name as the generated certificate."
+
+ property :key_pass, String,
+ description: "The passphrase for an existing key's passphrase."
+
+ property :key_type, String,
+ equal_to: %w{rsa ec}, default: "ec",
+ description: "The desired type of the generated key (rsa or ec)."
+
+ property :key_length, Integer,
+ equal_to: [1024, 2048, 4096, 8192], default: 2048,
+ description: "The desired Bit Length of the generated key (if key_type is equal to 'rsa')."
+
+ property :key_curve, String,
+ equal_to: %w{secp384r1 secp521r1 prime256v1}, default: "prime256v1",
+ description: "The desired curve of the generated key (if key_type is equal to 'ec'). Run openssl ecparam -list_curves to see available options."
+
+ default_action :create
+
+ action :create do
+ description "Generate a certificate request."
+
+ unless ::File.exist? new_resource.path
+ converge_by("Create CSR #{@new_resource}") do
+ file new_resource.name do
+ owner new_resource.owner unless new_resource.owner.nil?
+ group new_resource.group unless new_resource.group.nil?
+ mode new_resource.mode
+ content csr.to_pem
+ action :create
+ end
+
+ file new_resource.key_file do
+ mode new_resource.mode
+ owner new_resource.owner unless new_resource.owner.nil?
+ group new_resource.group unless new_resource.group.nil?
+ content key.to_pem
+ sensitive true
+ action :create_if_missing
+ end
+ end
+ end
+ end
+
+ action_class do
+ def generate_key_file
+ unless new_resource.key_file
+ path, file = ::File.split(new_resource.path)
+ filename = ::File.basename(file, ::File.extname(file))
+ new_resource.key_file path + "/" + filename + ".key"
+ end
+ new_resource.key_file
+ end
+
+ def key
+ @key ||= if priv_key_file_valid?(generate_key_file, new_resource.key_pass)
+ OpenSSL::PKey.read ::File.read(generate_key_file), new_resource.key_pass
+ elsif new_resource.key_type == "rsa"
+ gen_rsa_priv_key(new_resource.key_length)
+ else
+ gen_ec_priv_key(new_resource.key_curve)
+ end
+ @key
+ end
+
+ def subject
+ csr_subject = OpenSSL::X509::Name.new()
+ csr_subject.add_entry("C", new_resource.country) unless new_resource.country.nil?
+ csr_subject.add_entry("ST", new_resource.state) unless new_resource.state.nil?
+ csr_subject.add_entry("L", new_resource.city) unless new_resource.city.nil?
+ csr_subject.add_entry("O", new_resource.org) unless new_resource.org.nil?
+ csr_subject.add_entry("OU", new_resource.org_unit) unless new_resource.org_unit.nil?
+ csr_subject.add_entry("CN", new_resource.common_name)
+ csr_subject.add_entry("emailAddress", new_resource.email) unless new_resource.email.nil?
+ csr_subject
+ end
+
+ def csr
+ gen_x509_request(subject, key)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb
index 069c35691f..3a4822e8f4 100644
--- a/lib/chef/resources.rb
+++ b/lib/chef/resources.rb
@@ -66,8 +66,12 @@ require "chef/resource/ohai"
require "chef/resource/ohai_hint"
require "chef/resource/openbsd_package"
require "chef/resource/openssl_dhparam"
+require "chef/resource/openssl_ec_private_key"
+require "chef/resource/openssl_ec_public_key"
require "chef/resource/openssl_rsa_private_key"
require "chef/resource/openssl_rsa_public_key"
+require "chef/resource/openssl_x509_certificate"
+require "chef/resource/openssl_x509_request"
require "chef/resource/package"
require "chef/resource/pacman_package"
require "chef/resource/paludis_package"
diff --git a/spec/unit/mixin/openssl_helper_spec.rb b/spec/unit/mixin/openssl_helper_spec.rb
index 6873fd8cf2..a6a6fb0e71 100644
--- a/spec/unit/mixin/openssl_helper_spec.rb
+++ b/spec/unit/mixin/openssl_helper_spec.rb
@@ -126,15 +126,15 @@ describe Chef::Mixin::OpenSSLHelper do
end
end
- context "When the key file does exist, but does not contain a valid rsa private key" do
- it "Throws an OpenSSL::PKey::RSAError exception" do
+ context "When the key file does exist, but does not contain a valid rsa/ec private key" do
+ it "Throws an OpenSSL::PKey::PKeyError exception" do
@keyfile.write("I_am_not_a_key_I_am_a_free_man")
@keyfile.close
expect(instance.priv_key_file_valid?(@keyfile.path)).to be_falsey
end
end
- context "When the key file does exist, and does contain a vaild rsa private key" do
+ context "When the rsa key file does exist, and does contain a vaild rsa private key" do
it "returns true" do
@keyfile.write(::OpenSSL::PKey::RSA.new(1024).to_pem)
@keyfile.close
@@ -142,17 +142,41 @@ describe Chef::Mixin::OpenSSLHelper do
end
end
- context "When a valid keyfile requires a passphrase, and an invalid passphrase is supplied" do
+ context "When the ec key file does exist, and does contain a vaild ec private key" do
+ it "returns true" do
+ @keyfile.write(OpenSSL::PKey::EC.generate("prime256v1").to_pem)
+ @keyfile.close
+ expect(instance.priv_key_file_valid?(@keyfile.path)).to be_truthy
+ end
+ end
+
+ context "When a valid rsa keyfile requires a passphrase, and an invalid passphrase is supplied" do
+ it "returns false" do
+ @keyfile.write(OpenSSL::PKey::RSA.new(1024).to_pem(cipher, "oink"))
+ @keyfile.close
+ expect(instance.priv_key_file_valid?(@keyfile.path, "poml")).to be_falsey
+ end
+ end
+
+ context "When a valid ec keyfile requires a passphrase, and an invalid passphrase is supplied" do
it "returns false" do
- @keyfile.write(::OpenSSL::PKey::RSA.new(1024).to_pem(cipher, "oink"))
+ @keyfile.write(OpenSSL::PKey::EC.generate("prime256v1").to_pem(cipher, "oink"))
@keyfile.close
expect(instance.priv_key_file_valid?(@keyfile.path, "poml")).to be_falsey
end
end
- context "When a valid keyfile requires a passphrase, and a valid passphrase is supplied" do
+ context "When a valid rsa keyfile requires a passphrase, and a valid passphrase is supplied" do
it "returns true" do
- @keyfile.write(::OpenSSL::PKey::RSA.new(1024).to_pem(cipher, "oink"))
+ @keyfile.write(OpenSSL::PKey::RSA.new(1024).to_pem(cipher, "oink"))
+ @keyfile.close
+ expect(instance.priv_key_file_valid?(@keyfile.path, "oink")).to be_truthy
+ end
+ end
+
+ context "When a valid ec keyfile requires a passphrase, and a valid passphrase is supplied" do
+ it "returns true" do
+ @keyfile.write(OpenSSL::PKey::EC.generate("prime256v1").to_pem(cipher, "oink"))
@keyfile.close
expect(instance.priv_key_file_valid?(@keyfile.path, "oink")).to be_truthy
end
@@ -163,6 +187,40 @@ describe Chef::Mixin::OpenSSLHelper do
end
end
+ describe "#crl_file_valid?" do
+ require "tempfile"
+
+ before(:each) do
+ @crlfile = Tempfile.new("crlfile")
+ end
+
+ context "When the crl file doesnt not exist" do
+ it "returns false" do
+ expect(instance.crl_file_valid?("/tmp/bad_filename")).to be_falsey
+ end
+ end
+
+ context "When the crl file does exist, but does not contain a valid CRL" do
+ it "returns false" do
+ @crlfile.write("I_am_not_a_crl_I_am_a_free_man")
+ @crlfile.close
+ expect(instance.crl_file_valid?(@crlfile.path)).to be_falsey
+ end
+ end
+
+ context "When the crl file does exist, and does contain a vaild CRL" do
+ it "returns true" do
+ @crlfile.write("-----BEGIN X509 CRL-----\nMIIBbjCB0QIBATAKBggqhkjOPQQDAjAOMQwwCgYDVQQDDANDQTIXDTE4MDgwMTE3\nMjg1NVoXDTE4MDgwOTE3Mjg1NVowNjA0AhUAx7y2YCouQlHvTignoijLUrwM6i8X\nDTE4MDgwMTE3Mjg1NVowDDAKBgNVHRUEAwoBAKBaMFgwCgYDVR0UBAMCAQQwSgYD\nVR0jBEMwQYAUCqE8XxFIFys0LTVPvsO1UtmrlyOhEqQQMA4xDDAKBgNVBAMMA0NB\nMoIVAPneTuAa1LzrK0wiZrxE8/1lSTp3MAoGCCqGSM49BAMCA4GLADCBhwJBct+Z\nZV3IZkPNevQv2S8lZ6kAMudN8R4QSzIQfM354Uk880RyQStP2S5Mb4gW3aFzwAy2\n/+rbx0bn2WmwoQv17I8CQgDtbvhf9chyPgMwAGCF7al04fve90fU1zRNH0zX1j9H\niDA2q1uBX+3TcTWcN+xgNimeRpvJFJ3uOB6w7jtwqGf1YQ==\n-----END X509 CRL-----\n")
+ @crlfile.close
+ expect(instance.crl_file_valid?(@crlfile.path)).to be_truthy
+ end
+ end
+
+ after(:each) do
+ @crlfile.unlink
+ end
+ end
+
# Generators
describe "#gen_dhparam" do
context "When given an invalid key length" do
@@ -183,7 +241,7 @@ describe Chef::Mixin::OpenSSLHelper do
context "When a proper key length and generator id are given" do
it "Generates a dhparam object" do
- expect(instance.gen_dhparam(1024, 2)).to be_kind_of(::OpenSSL::PKey::DH)
+ expect(instance.gen_dhparam(1024, 2)).to be_kind_of(OpenSSL::PKey::DH)
end
end
end
@@ -199,14 +257,14 @@ describe Chef::Mixin::OpenSSLHelper do
context "When a proper key length is given" do
it "Generates an RSA key object" do
- expect(instance.gen_rsa_priv_key(1024)).to be_kind_of(::OpenSSL::PKey::RSA)
+ expect(instance.gen_rsa_priv_key(1024)).to be_kind_of(OpenSSL::PKey::RSA)
end
end
end
describe "#encrypt_rsa_key" do
before(:all) do
- @rsa_key = ::OpenSSL::PKey::RSA.new(1024)
+ @rsa_key = OpenSSL::PKey::RSA.new(1024)
end
context "When given anything other than an RSA key object to encrypt" do
@@ -245,7 +303,554 @@ describe Chef::Mixin::OpenSSLHelper do
it "Generates a valid encrypted PEM" do
@encrypted_key = instance.encrypt_rsa_key(@rsa_key, "oink", "des3")
expect(@encrypted_key).to be_kind_of(String)
- expect(::OpenSSL::PKey::RSA.new(@encrypted_key, "oink").private?).to be_truthy
+ expect(OpenSSL::PKey::RSA.new(@encrypted_key, "oink").private?).to be_truthy
+ end
+ end
+ end
+
+ describe "#gen_ec_priv_key" do
+ context "When given an invalid curve" do
+ it "Raises a TypeError" do
+ expect do
+ instance.gen_ec_priv_key(2048)
+ end.to raise_error(TypeError)
+ end
+
+ it "Throws an ArgumentError" do
+ expect do
+ instance.gen_ec_priv_key("primeFromTheFuture")
+ end.to raise_error(ArgumentError)
+ end
+ end
+
+ context "When a proper curve is given" do
+ it "Generates an ec key object" do
+ expect(instance.gen_ec_priv_key("prime256v1")).to be_kind_of(OpenSSL::PKey::EC)
+ end
+ end
+ end
+
+ describe "#encrypt_ec_key" do
+ before(:all) do
+ @ec_key = OpenSSL::PKey::EC.generate("prime256v1")
+ end
+
+ context "When given anything other than an EC key object to encrypt" do
+ it "Raises a TypeError" do
+ expect do
+ instance.encrypt_ec_key("abcd", "efgh", "des3")
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When given anything other than a string as the passphrase" do
+ it "Raises a TypeError" do
+ expect do
+ instance.encrypt_ec_key(@ec_key, 1234, "des3")
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When given anything other than a string as the cipher" do
+ it "Raises a TypeError" do
+ expect do
+ instance.encrypt_ec_key(@ec_key, "1234", 1234)
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When given an invalid cipher string" do
+ it "Raises an ArgumentError" do
+ expect do
+ instance.encrypt_ec_key(@ec_key, "1234", "des3_bogus")
+ end.to raise_error(ArgumentError)
+ end
+ end
+
+ context "When given a valid ec key and a valid passphrase string" do
+ it "Generates a valid encrypted PEM" do
+ @encrypted_key = instance.encrypt_ec_key(@ec_key, "oink", "des3")
+ expect(@encrypted_key).to be_kind_of(String)
+ expect(OpenSSL::PKey::EC.new(@encrypted_key, "oink").private?).to be_truthy
+ end
+ end
+ end
+
+ describe "#gen_x509_request" do
+ before(:all) do
+ @subject = OpenSSL::X509::Name.new [%w{CN x509request}]
+ @ec_key = OpenSSL::PKey::EC.generate("prime256v1")
+ @rsa_key = OpenSSL::PKey::RSA.new(2048)
+ end
+
+ context "When given anything other than an RSA/EC key object" do
+ it "Raises a TypeError" do
+ expect do
+ instance.gen_x509_request(@subject, "abc")
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When given anything other than an X509 Name object" do
+ it "Raises a TypeError" do
+ expect do
+ instance.gen_x509_request("abc", @key)
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When given a valid EC key and a valid subject" do
+ it "Generates a valid x509 request PEM" do
+ @x509_request = instance.gen_x509_request(@subject, @ec_key)
+ expect(@x509_request).to be_kind_of(OpenSSL::X509::Request)
+ expect(OpenSSL::X509::Request.new(@x509_request).verify(@ec_key)).to be_truthy
+ end
+ end
+
+ context "When given a valid RSA key and a valid subject" do
+ it "Generates a valid x509 request PEM" do
+ @x509_request = instance.gen_x509_request(@subject, @rsa_key)
+ expect(@x509_request).to be_kind_of(OpenSSL::X509::Request)
+ expect(OpenSSL::X509::Request.new(@x509_request).verify(@rsa_key)).to be_truthy
+ end
+ end
+ end
+
+ describe "#gen_x509_extensions" do
+ context "When given anything other than an Ruby Hash object" do
+ it "Raises a TypeError" do
+ expect do
+ instance.gen_x509_extensions("abc")
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When a misformatted ruby Hash is given" do
+ it "Raises a TypeError" do
+ expect do
+ instance.gen_x509_extensions("pouet" => "plop")
+ end.to raise_error(TypeError)
+ end
+
+ it "Raises a ArgumentError" do
+ expect do
+ instance.gen_x509_extensions("pouet" => { "values" => [ "keyCertSign" ], "wrong_key" => true })
+ end.to raise_error(ArgumentError)
+ end
+
+ it "Raises a TypeError" do
+ expect do
+ instance.gen_x509_extensions("keyUsage" => { "values" => "keyCertSign", "critical" => true })
+ end.to raise_error(TypeError)
+ end
+
+ it "Raises a TypeError" do
+ expect do
+ instance.gen_x509_extensions("keyUsage" => { "values" => [ "keyCertSign" ], "critical" => "yes" })
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When given a well formatted ruby Hash" do
+ it "Generates a valid Array of X509 Extensions" do
+ @x509_extension = instance.gen_x509_extensions("keyUsage" => { "values" => [ "keyCertSign" ], "critical" => true })
+ expect(@x509_extension).to be_kind_of(Array)
+ @x509_extension.each { |e| expect(e).to be_kind_of(OpenSSL::X509::Extension) }
+ end
+ end
+ end
+
+ describe "#gen_x509_cert" do
+ before(:all) do
+ @instance = Class.new { include Chef::Mixin::OpenSSLHelper }.new
+ @rsa_key = OpenSSL::PKey::RSA.new(2048)
+ @ec_key = OpenSSL::PKey::EC.generate("prime256v1")
+
+ @rsa_request = @instance.gen_x509_request(OpenSSL::X509::Name.new([%w{CN RSACert}]), @rsa_key)
+ @ec_request = @instance.gen_x509_request(OpenSSL::X509::Name.new([%w{CN ECCert}]), @ec_key)
+
+ @x509_extension = @instance.gen_x509_extensions("keyUsage" => { "values" => [ "keyCertSign" ], "critical" => true })
+
+ # Generating CA
+ @ca_key = OpenSSL::PKey::RSA.new(2048)
+ @ca_cert = OpenSSL::X509::Certificate.new
+ @ca_cert.version = 2
+ @ca_cert.serial = 1
+ @ca_cert.subject = OpenSSL::X509::Name.new [%w{CN TestCA}]
+ @ca_cert.issuer = @ca_cert.subject
+ @ca_cert.public_key = @ca_key.public_key
+ @ca_cert.not_before = Time.now
+ @ca_cert.not_after = @ca_cert.not_before + 365 * 24 * 60 * 60
+ ef = OpenSSL::X509::ExtensionFactory.new
+ ef.subject_certificate = @ca_cert
+ ef.issuer_certificate = @ca_cert
+ @ca_cert.add_extension(ef.create_extension("basicConstraints", "CA:TRUE", true))
+ @ca_cert.add_extension(ef.create_extension("keyUsage", "keyCertSign, cRLSign", true))
+ @ca_cert.add_extension(ef.create_extension("subjectKeyIdentifier", "hash", false))
+ @ca_cert.add_extension(ef.create_extension("authorityKeyIdentifier", "keyid:always", false))
+ @ca_cert.sign(@ca_key, OpenSSL::Digest::SHA256.new)
+
+ @info_with_issuer = { "validity" => 365, "issuer" => @ca_cert }
+ @info_without_issuer = { "validity" => 365 }
+ end
+
+ context "When the request given is anything other then a Ruby OpenSSL::X509::Request" do
+ it "Raises a TypeError" do
+ expect do
+ @instance.gen_x509_cert("abc", @x509_extension, @info_without_issuer, @rsa_key)
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When the extension given is anything other then a Ruby Array" do
+ it "Raises a TypeError" do
+ expect do
+ @instance.gen_x509_cert(@rsa_request, "abc", @info_without_issuer, @rsa_key)
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When the info given is anything other then a Ruby Hash" do
+ it "Raises a TypeError" do
+ expect do
+ @instance.gen_x509_cert(@rsa_request, @x509_extension, "abc", @rsa_key)
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When the key given is anything other then a Ruby OpenSSL::Pkey::EC or OpenSSL::Pkey::RSA object" do
+ it "Raises a TypeError" do
+ expect do
+ @instance.gen_x509_cert(@rsa_request, @x509_extension, @info_without_issuer, "abc")
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When given valid parameters to generate a self signed certificate" do
+ it "Generates a valid x509 Certificate" do
+ @x509_certificate = @instance.gen_x509_cert(@rsa_request, @x509_extension, @info_without_issuer, @rsa_key)
+ expect(@x509_certificate).to be_kind_of(OpenSSL::X509::Certificate)
+ expect(OpenSSL::X509::Certificate.new(@x509_certificate).verify(@rsa_key)).to be_truthy
+ end
+ end
+
+ context "When given valid parameters to generate a CA signed certificate" do
+ it "Generates a valid x509 Certificate" do
+ @x509_certificate = @instance.gen_x509_cert(@ec_request, @x509_extension, @info_with_issuer, @ca_key)
+ expect(@x509_certificate).to be_kind_of(OpenSSL::X509::Certificate)
+ expect(OpenSSL::X509::Certificate.new(@x509_certificate).verify(@ca_key)).to be_truthy
+ end
+ end
+ end
+
+ describe "#get_next_crl_number" do
+ before(:all) do
+ @crl = OpenSSL::X509::CRL. new "-----BEGIN X509 CRL-----\nMIIBbTCB0QIBATAKBggqhkjOPQQDAjAOMQwwCgYDVQQDDANDQTIXDTE4MDgwMjA5\nMzc0OFoXDTE4MDgxMDA5Mzc0OFowNjA0AhUAx7y2YCouQlHvTignoijLUrwM6i8X\nDTE4MDgwMjA5Mzc0OFowDDAKBgNVHRUEAwoBAKBaMFgwCgYDVR0UBAMCAQQwSgYD\nVR0jBEMwQYAUxRlLNQUIOeWVaYm6HS0qFIbNCs2hEqQQMA4xDDAKBgNVBAMMA0NB\nMoIVAN1nyw8cj7IbhRLBu2CfS9Q8ILmDMAoGCCqGSM49BAMCA4GKADCBhgJBNR3o\njo/PzFwFGJKxIMa09pU+jprLG2CWehpZ4tGDjwiDCfZBztkg3H15eu+hyWmDp0U9\neAP5iJHVb12/3KZP0YUCQSgmaoLF68+Gh7ha+hcDjwFhzqdgmh/UlGPaxFBJ1BiQ\nQq9uBn0IT4o7v1Tv2WRZNDk7oiuRaZG+R9IodiZPsGKv\n-----END X509 CRL-----\n"
+ end
+
+ context "When the CRL given is anything other then a Ruby OpenSSL::X509::CRL object" do
+ it "Raises a TypeError" do
+ expect do
+ instance.get_next_crl_number("abc")
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When given valid parameter to get the next crlNumber" do
+ it "Get 5" do
+ @next_crl = instance.get_next_crl_number(@crl)
+ expect(@next_crl).to be_kind_of(Integer)
+ expect(@next_crl == 5).to be_truthy
+ end
+ end
+ end
+
+ describe "#serial_revoked?" do
+ before(:all) do
+ @crl = OpenSSL::X509::CRL. new "-----BEGIN X509 CRL-----\nMIIBbTCB0QIBATAKBggqhkjOPQQDAjAOMQwwCgYDVQQDDANDQTIXDTE4MDgwMjA5\nMzc0OFoXDTE4MDgxMDA5Mzc0OFowNjA0AhUAx7y2YCouQlHvTignoijLUrwM6i8X\nDTE4MDgwMjA5Mzc0OFowDDAKBgNVHRUEAwoBAKBaMFgwCgYDVR0UBAMCAQQwSgYD\nVR0jBEMwQYAUxRlLNQUIOeWVaYm6HS0qFIbNCs2hEqQQMA4xDDAKBgNVBAMMA0NB\nMoIVAN1nyw8cj7IbhRLBu2CfS9Q8ILmDMAoGCCqGSM49BAMCA4GKADCBhgJBNR3o\njo/PzFwFGJKxIMa09pU+jprLG2CWehpZ4tGDjwiDCfZBztkg3H15eu+hyWmDp0U9\neAP5iJHVb12/3KZP0YUCQSgmaoLF68+Gh7ha+hcDjwFhzqdgmh/UlGPaxFBJ1BiQ\nQq9uBn0IT4o7v1Tv2WRZNDk7oiuRaZG+R9IodiZPsGKv\n-----END X509 CRL-----\n"
+ end
+
+ context "When the CRL given is anything other then a Ruby OpenSSL::X509::CRL object" do
+ it "Raises a TypeError" do
+ expect do
+ instance.serial_revoked?("abc", "C7BCB6602A2E4251EF4E2827A228CB52BC0CEA2F")
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When the serial given is anything other then a Ruby String or Integer object" do
+ it "Raises a TypeError" do
+ expect do
+ instance.serial_revoked?(@crl, [])
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When given valid parameters to know if the serial is revoked" do
+ it "get true" do
+ @serial_revoked = instance.serial_revoked?(@crl, "C7BCB6602A2E4251EF4E2827A228CB52BC0CEA2F")
+ expect(@serial_revoked).to be_kind_of(TrueClass)
+ end
+ end
+ end
+
+ describe "#gen_x509_crl" do
+ before(:all) do
+ # Generating CA
+
+ @ca_key = OpenSSL::PKey::RSA.new(2048)
+ @ca_cert = OpenSSL::X509::Certificate.new
+ @ca_cert.version = 2
+ @ca_cert.serial = 1
+ @ca_cert.subject = OpenSSL::X509::Name.new [%w{CN TestCA}]
+ @ca_cert.issuer = @ca_cert.subject
+ @ca_cert.public_key = @ca_key.public_key
+ @ca_cert.not_before = Time.now
+ @ca_cert.not_after = @ca_cert.not_before + 365 * 24 * 60 * 60
+ ef = OpenSSL::X509::ExtensionFactory.new
+ ef.subject_certificate = @ca_cert
+ ef.issuer_certificate = @ca_cert
+ @ca_cert.add_extension(ef.create_extension("basicConstraints", "CA:TRUE", true))
+ @ca_cert.add_extension(ef.create_extension("keyUsage", "keyCertSign, cRLSign", true))
+ @ca_cert.add_extension(ef.create_extension("subjectKeyIdentifier", "hash", false))
+ @ca_cert.add_extension(ef.create_extension("authorityKeyIdentifier", "keyid:always", false))
+ @ca_cert.sign(@ca_key, OpenSSL::Digest::SHA256.new)
+
+ @info = { "validity" => 8, "issuer" => @ca_cert }
+ end
+
+ context "When the CA private key given is anything other then a Ruby OpenSSL::PKey::EC object or a OpenSSL::PKey::RSA object" do
+ it "Raises a TypeError" do
+ expect do
+ instance.gen_x509_crl("abc", @info)
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When the info given is anything other then a Ruby Hash" do
+ it "Raises a TypeError" do
+ expect do
+ instance.gen_x509_crl(@ca_key, "abc")
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When a misformatted info Ruby Hash is given" do
+ it "Raises a ArgumentError" do
+ expect do
+ instance.gen_x509_crl(@ca_key, "abc" => "def", "validity" => 8)
+ end.to raise_error(ArgumentError)
+ end
+
+ it "Raises a TypeError" do
+ expect do
+ instance.gen_x509_crl(@ca_key, "issuer" => "abc", "validity" => 8)
+ end.to raise_error(TypeError)
+ end
+
+ it "Raises a TypeError" do
+ expect do
+ instance.gen_x509_crl(@ca_key, "issuer" => @ca_cert, "validity" => "abc")
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When given valid parameters to generate a CRL" do
+ it "Generates a valid x509 CRL" do
+ @x509_crl = instance.gen_x509_crl(@ca_key, @info)
+ expect(@x509_crl).to be_kind_of(OpenSSL::X509::CRL)
+ expect(OpenSSL::X509::CRL.new(@x509_crl).verify(@ca_key)).to be_truthy
+ end
+ end
+ end
+
+ describe "#renew_x509_crl" do
+ before(:all) do
+ # Generating CA
+ @instance = Class.new { include Chef::Mixin::OpenSSLHelper }.new
+ @ca_key = OpenSSL::PKey::RSA.new(2048)
+ @ca_cert = OpenSSL::X509::Certificate.new
+ @ca_cert.version = 2
+ @ca_cert.serial = 1
+ @ca_cert.subject = OpenSSL::X509::Name.new [%w{CN TestCA}]
+ @ca_cert.issuer = @ca_cert.subject
+ @ca_cert.public_key = @ca_key.public_key
+ @ca_cert.not_before = Time.now
+ @ca_cert.not_after = @ca_cert.not_before + 365 * 24 * 60 * 60
+ ef = OpenSSL::X509::ExtensionFactory.new
+ ef.subject_certificate = @ca_cert
+ ef.issuer_certificate = @ca_cert
+ @ca_cert.add_extension(ef.create_extension("basicConstraints", "CA:TRUE", true))
+ @ca_cert.add_extension(ef.create_extension("keyUsage", "keyCertSign, cRLSign", true))
+ @ca_cert.add_extension(ef.create_extension("subjectKeyIdentifier", "hash", false))
+ @ca_cert.add_extension(ef.create_extension("authorityKeyIdentifier", "keyid:always", false))
+ @ca_cert.sign(@ca_key, OpenSSL::Digest::SHA256.new)
+
+ @info = { "validity" => 8, "issuer" => @ca_cert }
+
+ @crl = @instance.gen_x509_crl(@ca_key, @info)
+ end
+
+ context "When the CRL given is anything other then a Ruby OpenSSL::X509::CRL object" do
+ it "Raises a TypeError" do
+ expect do
+ instance.renew_x509_crl("abc", @ca_key, @info)
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When the CA private key given is anything other then a Ruby OpenSSL::PKey::EC object or a OpenSSL::PKey::RSA object" do
+ it "Raises a TypeError" do
+ expect do
+ instance.renew_x509_crl(@crl, "abc", @info)
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When the info given is anything other then a Ruby Hash" do
+ it "Raises a TypeError" do
+ expect do
+ instance.renew_x509_crl(@crl, @ca_key, "abc")
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When a misformatted info Ruby Hash is given" do
+ it "Raises a ArgumentError" do
+ expect do
+ instance.renew_x509_crl(@crl, @ca_key, "abc" => "def", "validity" => 8)
+ end.to raise_error(ArgumentError)
+ end
+
+ it "Raises a TypeError" do
+ expect do
+ instance.renew_x509_crl(@crl, @ca_key, "issuer" => "abc", "validity" => 8)
+ end.to raise_error(TypeError)
+ end
+
+ it "Raises a TypeError" do
+ expect do
+ instance.renew_x509_crl(@crl, @ca_key, "issuer" => @ca_cert, "validity" => "abc")
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When given valid parameters to renew a CRL" do
+ it "Renew a valid x509 CRL" do
+ @renewed_crl = instance.renew_x509_crl(@crl, @ca_key, @info)
+ expect(@renewed_crl).to be_kind_of(OpenSSL::X509::CRL)
+ expect(OpenSSL::X509::CRL.new(@renewed_crl).verify(@ca_key)).to be_truthy
+ end
+ end
+ end
+
+ describe "#revoke_x509_crl" do
+ before(:all) do
+ # Generating CA
+
+ @instance = Class.new { include Chef::Mixin::OpenSSLHelper }.new
+ @ca_key = OpenSSL::PKey::RSA.new(2048)
+ @ca_cert = OpenSSL::X509::Certificate.new
+ @ca_cert.version = 2
+ @ca_cert.serial = 1
+ @ca_cert.subject = OpenSSL::X509::Name.new [%w{CN TestCA}]
+ @ca_cert.issuer = @ca_cert.subject
+ @ca_cert.public_key = @ca_key.public_key
+ @ca_cert.not_before = Time.now
+ @ca_cert.not_after = @ca_cert.not_before + 365 * 24 * 60 * 60
+ ef = OpenSSL::X509::ExtensionFactory.new
+ ef.subject_certificate = @ca_cert
+ ef.issuer_certificate = @ca_cert
+ @ca_cert.add_extension(ef.create_extension("basicConstraints", "CA:TRUE", true))
+ @ca_cert.add_extension(ef.create_extension("keyUsage", "keyCertSign, cRLSign", true))
+ @ca_cert.add_extension(ef.create_extension("subjectKeyIdentifier", "hash", false))
+ @ca_cert.add_extension(ef.create_extension("authorityKeyIdentifier", "keyid:always", false))
+ @ca_cert.sign(@ca_key, OpenSSL::Digest::SHA256.new)
+
+ @info = { "validity" => 8, "issuer" => @ca_cert }
+
+ @crl = @instance.gen_x509_crl(@ca_key, @info)
+ @revoke_info = { "serial" => 1, "reason" => 0 }
+ end
+
+ context "When the revoke_info given is anything other then a Ruby Hash" do
+ it "Raises a TypeError" do
+ expect do
+ instance.revoke_x509_crl("abc", @crl, @ca_key, @info)
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When the CRL given is anything other then a Ruby OpenSSL::X509::CRL object" do
+ it "Raises a TypeError" do
+ expect do
+ instance.revoke_x509_crl(@revoke_info, "abc", @ca_key, @info)
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When the CA private key given is anything other then a Ruby OpenSSL::PKey::EC object or a OpenSSL::PKey::RSA object" do
+ it "Raises a TypeError" do
+ expect do
+ instance.revoke_x509_crl(@revoke_info, @crl, "abc", @info)
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When the info given is anything other then a Ruby Hash" do
+ it "Raises a TypeError" do
+ expect do
+ instance.revoke_x509_crl(@revoke_info, @crl, @ca_key, "abc")
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When a misformatted revoke_info Ruby Hash is given" do
+ it "Raises a ArgumentError" do
+ expect do
+ instance.revoke_x509_crl({ "abc" => "def", "ghi" => "jkl" }, @crl, @ca_key, @info)
+ end.to raise_error(ArgumentError)
+ end
+
+ it "Raises a TypeError" do
+ expect do
+ instance.revoke_x509_crl({ "serial" => [], "reason" => 0 }, @crl, @ca_key, @info)
+ end.to raise_error(TypeError)
+ end
+
+ it "Raises a TypeError" do
+ expect do
+ instance.revoke_x509_crl({ "serial" => 1, "reason" => "abc" }, @crl, @ca_key, @info)
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When a misformatted info Ruby Hash is given" do
+ it "Raises a ArgumentError" do
+ expect do
+ instance.revoke_x509_crl(@revoke_info, @crl, @ca_key, "abc" => "def", "validity" => 8)
+ end.to raise_error(ArgumentError)
+ end
+
+ it "Raises a TypeError" do
+ expect do
+ instance.revoke_x509_crl(@revoke_info, @crl, @ca_key, "issuer" => "abc", "validity" => 8)
+ end.to raise_error(TypeError)
+ end
+
+ it "Raises a TypeError" do
+ expect do
+ instance.revoke_x509_crl(@revoke_info, @crl, @ca_key, "issuer" => @ca_cert, "validity" => "abc")
+ end.to raise_error(TypeError)
+ end
+ end
+
+ context "When given valid parameters to revoke a Serial in a CRL" do
+ it "Revoke a Serial in a CRL" do
+ @crl_with_revoked_serial = instance.revoke_x509_crl(@revoke_info, @crl, @ca_key, @info)
+ expect(@crl_with_revoked_serial).to be_kind_of(OpenSSL::X509::CRL)
+ expect(OpenSSL::X509::CRL.new(@crl_with_revoked_serial).verify(@ca_key)).to be_truthy
+ expect(instance.serial_revoked?(@crl_with_revoked_serial, 1)).to be_truthy
end
end
end
diff --git a/spec/unit/resource/openssl_ec_private_key_spec.rb b/spec/unit/resource/openssl_ec_private_key_spec.rb
new file mode 100644
index 0000000000..c5cac33257
--- /dev/null
+++ b/spec/unit/resource/openssl_ec_private_key_spec.rb
@@ -0,0 +1,64 @@
+#
+# Copyright:: Copyright 2018, Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "spec_helper"
+
+describe Chef::Resource::OpensslEcPrivateKey do
+
+ let(:resource) { Chef::Resource::OpensslEcPrivateKey.new("fakey_fakerton") }
+
+ it "has a resource name of :openssl_ec_private_key" do
+ expect(resource.resource_name).to eql(:openssl_ec_private_key)
+ end
+
+ it "the path property is the name_property" do
+ expect(resource.path).to eql("fakey_fakerton")
+ end
+
+ it "sets the default action as :create" do
+ expect(resource.action).to eql([:create])
+ end
+
+ it "supports :create action" do
+ expect { resource.action :create }.not_to raise_error
+ end
+
+ it "has a default mode of '0600'" do
+ expect(resource.mode).to eql("0600")
+ end
+
+ it "has a default key_cipher of 'des3'" do
+ expect(resource.key_cipher).to eql("des3")
+ end
+
+ it "only accepts valid key_cipher values" do
+ expect { resource.key_cipher "fako" }.to raise_error(ArgumentError)
+ end
+
+ it "has a default key_curve of 'prime256v1'" do
+ expect(resource.key_curve).to eql("prime256v1")
+ end
+
+ it "only accepts valid key_curve values" do
+ expect { resource.key_curve "fako" }.to raise_error(ArgumentError)
+ end
+
+ it "has a default force value of of false" do
+ expect(resource.force).to eql(false)
+ end
+
+end
diff --git a/spec/unit/resource/openssl_ec_public_key_spec.rb b/spec/unit/resource/openssl_ec_public_key_spec.rb
new file mode 100644
index 0000000000..dcc6b20103
--- /dev/null
+++ b/spec/unit/resource/openssl_ec_public_key_spec.rb
@@ -0,0 +1,43 @@
+#
+# Copyright:: Copyright 2018, Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "spec_helper"
+
+describe Chef::Resource::OpensslEcPublicKey do
+
+ let(:resource) { Chef::Resource::OpensslEcPublicKey.new("key") }
+
+ it "has a resource name of :openssl_ec_public_key" do
+ expect(resource.resource_name).to eql(:openssl_ec_public_key)
+ end
+
+ it "the path property is the name_property" do
+ expect(resource.path).to eql("key")
+ end
+
+ it "sets the default action as :create" do
+ expect(resource.action).to eql([:create])
+ end
+
+ it "supports :create action" do
+ expect { resource.action :create }.not_to raise_error
+ end
+
+ it "has a default mode of '0640'" do
+ expect(resource.mode).to eql("0640")
+ end
+end
diff --git a/spec/unit/resource/openssl_openssl_x509_certificate_spec.rb b/spec/unit/resource/openssl_openssl_x509_certificate_spec.rb
new file mode 100644
index 0000000000..b8e49db164
--- /dev/null
+++ b/spec/unit/resource/openssl_openssl_x509_certificate_spec.rb
@@ -0,0 +1,71 @@
+#
+# Copyright:: Copyright 2018, Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "spec_helper"
+
+describe Chef::Resource::OpensslX509Certificate do
+
+ let(:resource) { Chef::Resource::OpensslX509Certificate.new("fakey_fakerton") }
+
+ it "has a resource name of :openssl_x509_certificate" do
+ expect(resource.resource_name).to eql(:openssl_x509_certificate)
+ end
+
+ it "the path property is the name_property" do
+ expect(resource.path).to eql("fakey_fakerton")
+ end
+
+ it "sets the default action as :create" do
+ expect(resource.action).to eql([:create])
+ end
+
+ it "supports :create action" do
+ expect { resource.action :create }.not_to raise_error
+ end
+
+ it "has a default expiration of 365" do
+ expect(resource.expire).to eql(365)
+ end
+
+ it "has a default mode of '0644'" do
+ expect(resource.mode).to eql("0644")
+ end
+
+ it "has a default key_type of 'rsa'" do
+ expect(resource.key_type).to eql("rsa")
+ end
+
+ it "only accepts valid key_type values" do
+ expect { resource.key_type "fako" }.to raise_error(ArgumentError)
+ end
+
+ it "has a default key_length of '2048'" do
+ expect(resource.key_length).to eql(2048)
+ end
+
+ it "only accepts valid key_length values" do
+ expect { resource.key_length 1023 }.to raise_error(ArgumentError)
+ end
+
+ it "has a default key_curve of 'prime256v1'" do
+ expect(resource.key_curve).to eql("prime256v1")
+ end
+
+ it "only accepts valid key_curve values" do
+ expect { resource.key_curve "fako" }.to raise_error(ArgumentError)
+ end
+end
diff --git a/spec/unit/resource/openssl_openssl_x509_request.rb b/spec/unit/resource/openssl_openssl_x509_request.rb
new file mode 100644
index 0000000000..59abdc666e
--- /dev/null
+++ b/spec/unit/resource/openssl_openssl_x509_request.rb
@@ -0,0 +1,67 @@
+#
+# Copyright:: Copyright 2018, Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "spec_helper"
+
+describe Chef::Resource::OpensslX509Request do
+
+ let(:resource) { Chef::Resource::OpensslX509Request.new("fakey_fakerton") }
+
+ it "has a resource name of :openssl_x509_request" do
+ expect(resource.resource_name).to eql(:openssl_x509_request)
+ end
+
+ it "the path property is the name_property" do
+ expect(resource.path).to eql("fakey_fakerton")
+ end
+
+ it "sets the default action as :create" do
+ expect(resource.action).to eql([:create])
+ end
+
+ it "supports :create action" do
+ expect { resource.action :create }.not_to raise_error
+ end
+
+ it "has a default mode of '0644'" do
+ expect(resource.mode).to eql("0644")
+ end
+
+ it "has a default key_type of 'ec'" do
+ expect(resource.key_type).to eql("ec")
+ end
+
+ it "only accepts valid key_type values" do
+ expect { resource.key_type "fako" }.to raise_error(ArgumentError)
+ end
+
+ it "has a default key_length of '2048'" do
+ expect(resource.key_length).to eql(2048)
+ end
+
+ it "only accepts valid key_length values" do
+ expect { resource.key_length 1023 }.to raise_error(ArgumentError)
+ end
+
+ it "has a default key_curve of 'prime256v1'" do
+ expect(resource.key_curve).to eql("prime256v1")
+ end
+
+ it "only accepts valid key_curve values" do
+ expect { resource.key_curve "fako" }.to raise_error(ArgumentError)
+ end
+end