diff options
-rw-r--r-- | kitchen-tests/cookbooks/base/recipes/default.rb | 2 | ||||
-rw-r--r-- | lib/chef/mixin/openssl.rb | 20 | ||||
-rw-r--r-- | spec/unit/mixin/openssl_spec.rb | 252 |
3 files changed, 263 insertions, 11 deletions
diff --git a/kitchen-tests/cookbooks/base/recipes/default.rb b/kitchen-tests/cookbooks/base/recipes/default.rb index ad1bba8903..1811245b13 100644 --- a/kitchen-tests/cookbooks/base/recipes/default.rb +++ b/kitchen-tests/cookbooks/base/recipes/default.rb @@ -63,7 +63,7 @@ include_recipe "git" directory "/etc/ssl" # Generate new key and certificate -openssl_dhparam "/etc/ssl_test/dhparam.pem" do +openssl_dhparam "/etc/ssl/dhparam.pem" do key_length 1024 action :create end diff --git a/lib/chef/mixin/openssl.rb b/lib/chef/mixin/openssl.rb index 923ca75377..848f5a6088 100644 --- a/lib/chef/mixin/openssl.rb +++ b/lib/chef/mixin/openssl.rb @@ -19,7 +19,7 @@ class Chef module Mixin module OpenSSL def self.included(_base) - require "openssl" unless defined?(OpenSSL) + require "openssl" unless defined?(::OpenSSL) end # determine the key filename from the cert filename @@ -45,7 +45,7 @@ class Chef # 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 = ::OpenSSL::PKey::DH.new File.read(dhparam_pem_path) dhparam.params_ok? end @@ -60,8 +60,8 @@ 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::RSA.new key_content, key_password + rescue ::OpenSSL::PKey::RSAError return false end key.private? @@ -75,7 +75,7 @@ class Chef 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) + ::OpenSSL::PKey::DH.new(key_length, generator) end # generate an RSA private key given key length @@ -84,7 +84,7 @@ class Chef 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) + ::OpenSSL::PKey::RSA.new(key_length) end # generate pem format of the public key given a private key @@ -95,7 +95,7 @@ class Chef # 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 = ::OpenSSL::PKey::RSA.new key_content, priv_key_password key.public_key.to_pem end @@ -105,12 +105,12 @@ class Chef # @param [String] key_cipher the cipher to use # @return [String] pem contents 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, "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) + raise ArgumentError, "Specified key_cipher is not available on this system" unless ::OpenSSL::Cipher.ciphers.include?(key_cipher) - cipher = OpenSSL::Cipher.new(key_cipher) + cipher = ::OpenSSL::Cipher.new(key_cipher) rsa_key.to_pem(cipher, key_password) end end diff --git a/spec/unit/mixin/openssl_spec.rb b/spec/unit/mixin/openssl_spec.rb new file mode 100644 index 0000000000..8a0206116c --- /dev/null +++ b/spec/unit/mixin/openssl_spec.rb @@ -0,0 +1,252 @@ +# +# Copyright 2009-2018, Chef Software, Inc <legal@chef.io> +# +# 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" +require "chef/mixin/openssl" + +describe Chef::Mixin::OpenSSL do + let(:instance) do + Class.new { include Chef::Mixin::OpenSSL }.new + end + + describe ".included" do + it "requires openssl" do + instance + expect(defined?(OpenSSL)).to_not be(false) + end + end + + # Path helpers + describe "#get_key_filename" do + context "When the input is not a string" do + it "Throws a TypeError" do + expect do + instance.get_key_filename(33) + end.to raise_error(TypeError) + end + end + + context "when the input is a string" do + it "Generates valid keyfile names" do + expect(instance.get_key_filename("/etc/temp.crt")).to match("/etc/temp.key") + end + end + end + + # Validation helpers + describe "#key_length_valid?" do + context "When the number is less than 1024" do + it "returns false" do + expect(instance.key_length_valid?(1023)).to be_falsey + expect(instance.key_length_valid?(2)).to be_falsey + expect(instance.key_length_valid?(64)).to be_falsey + expect(instance.key_length_valid?(512)).to be_falsey + end + end + + context "When the number is greater than 1024 but is not a power of 2" do + it "returns false" do + expect(instance.key_length_valid?(1025)).to be_falsey + expect(instance.key_length_valid?(6666)).to be_falsey + expect(instance.key_length_valid?(8191)).to be_falsey + end + end + + context "When the number is a power of 2, equal to or greater than 1024" do + it "returns true" do + expect(instance.key_length_valid?(1024)).to be_truthy + expect(instance.key_length_valid?(2048)).to be_truthy + expect(instance.key_length_valid?(4096)).to be_truthy + expect(instance.key_length_valid?(8192)).to be_truthy + end + end + end + + describe "#dhparam_pem_valid?" do + require "tempfile" + + before(:each) do + @dhparam_file = Tempfile.new("dhparam") + end + + context "When the dhparam.pem file does not exist" do + it "returns false" do + expect(instance.dhparam_pem_valid?("/tmp/bad_filename")).to be_falsey + end + end + + context "When the dhparam.pem file does exist, but does not contain a valid dhparam key" do + it "Throws an OpenSSL::PKey::DHError exception" do + expect do + @dhparam_file.puts("I_am_not_a_key_I_am_a_free_man") + @dhparam_file.close + instance.dhparam_pem_valid?(@dhparam_file.path) + end.to raise_error(::OpenSSL::PKey::DHError) + end + end + + context "When the dhparam.pem file does exist, and does contain a vaild dhparam key" do + it "returns true" do + @dhparam_file.puts(::OpenSSL::PKey::DH.new(1024).to_pem) + @dhparam_file.close + expect(instance.dhparam_pem_valid?(@dhparam_file.path)).to be_truthy + end + end + + after(:each) do + @dhparam_file.unlink + end + end + + describe "#priv_key_file_valid?" do + require "tempfile" + require "openssl" unless defined?(OpenSSL) + + cipher = ::OpenSSL::Cipher.new("des3") + + before(:each) do + @keyfile = Tempfile.new("keyfile") + end + + context "When the key file does not exist" do + it "returns false" do + expect(instance.priv_key_file_valid?("/tmp/bad_filename")).to be_falsey + 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 + @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 + it "returns true" do + @keyfile.write(::OpenSSL::PKey::RSA.new(1024).to_pem) + @keyfile.close + expect(instance.priv_key_file_valid?(@keyfile.path)).to be_truthy + end + end + + context "When a valid 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 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.close + expect(instance.priv_key_file_valid?(@keyfile.path, "oink")).to be_truthy + end + end + + after(:each) do + @keyfile.unlink + end + end + + # Generators + describe "#gen_dhparam" do + context "When given an invalid key length" do + it "Throws an ArgumentError" do + expect do + instance.gen_dhparam(2046, 2) + end.to raise_error(ArgumentError) + end + end + + context "When given an invalid generator id" do + it "Throws a TypeError" do + expect do + instance.gen_dhparam(2048, "bob") + end.to raise_error(TypeError) + end + end + + 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) + end + end + end + + describe "#gen_rsa_priv_key" do + context "When given an invalid key length" do + it "Throws an ArgumentError" do + expect do + instance.gen_rsa_priv_key(4093) + end.to raise_error(ArgumentError) + end + end + + 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) + end + end + end + + describe "#encrypt_rsa_key" do + before(:all) do + @rsa_key = ::OpenSSL::PKey::RSA.new(1024) + end + + context "When given anything other than an RSA key object to encrypt" do + it "Raises a TypeError" do + expect do + instance.encrypt_rsa_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_rsa_key(@rsa_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_rsa_key(@rsa_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_rsa_key(@rsa_key, "1234", "des3_bogus") + end.to raise_error(ArgumentError) + end + end + + context "When given a valid RSA key and a valid passphrase string" 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 + end + end + end +end |