summaryrefslogtreecommitdiff
path: root/lib/chef/mixin/openssl_helper.rb
blob: f12a559097829788ae70ea5b6526e920512fbc18 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#
# Copyright:: Copyright 2013-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.
#

class Chef
  module Mixin
    # various helpers for use with openssl. Currently used by the openssl_* resources
    module OpenSSLHelper
      def self.included(_base)
        require "openssl" unless defined?(::OpenSSL)
      end

      # determine the key filename from the cert filename
      # @param [String] cert_filename the path to the certfile
      # @return [String] the path to the keyfile
      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

      # is the key length a valid key length
      # @param [Integer] number
      # @return [Boolean] is length valid
      def key_length_valid?(number)
        number >= 1024 && ( number & (number - 1) == 0 )
      end

      # validate a dhparam file from path
      # @param [String] dhparam_pem_path the path to the pem file
      # @return [Boolean] is the key valid
      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

      # given either a key file path or key file content see if it's actually
      # a private key
      # @param [String] key_file the path to the keyfile or the key contents
      # @param [String] key_password optional password to the keyfile
      # @return [Boolean] is the key valid?
      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::RSA.new key_content, key_password
        rescue ::OpenSSL::PKey::RSAError
          return false
        end
        key.private?
      end

      # generate a dhparam file
      # @param [String] key_length the length of the key
      # @param [Integer] generator the dhparam generator to use
      # @return [OpenSSL::PKey::DH]
      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

      # generate an RSA private key given key length
      # @param [Integer] key_length the key length of the private key
      # @return [OpenSSL::PKey::DH]
      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

      # 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_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

      # generate a pem file given a cipher, key, an optional key_password
      # @param [OpenSSL::PKey::RSA] rsa_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_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
    end
  end
end