Unverified Commit 35f58acc authored by Tim Smith's avatar Tim Smith Committed by GitHub
Browse files

Merge pull request #74 from juju482/master

X509 resource remaining
parents fc8cdf6b ad74f29d
......@@ -82,26 +82,36 @@ Note that node attributes are widely accessible. Storing unencrypted passwords i
## Resources
### openssl_x509
### openssl_x509_certificate
This resource generates self-signed, PEM-formatted x509 certificates. If no existing key is specified, the resource will automatically generate a passwordless key with the certificate.
This resource generates 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.
#### Properties
Name | Type | Description
------------------ | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
`path` | String (Optional) | Optional path to write the file to if you'd like to specify it here instead of in the resource name
`common_name` | String (Required) | Value for the `CN` certificate field.
`org` | String (Required) | Value for the `O` certificate field.
`org_unit` | String (Required) | Value for the `OU` certificate field.
`common_name` | String (Optional) | Value for the `CN` certificate field.
`org` | String (Optional) | Value for the `O` certificate field.
`org_unit` | String (Optional) | Value for the `OU` certificate field.
`city` | String (Optional) | Value for the `L` certificate field.
`state` | String (Optional) | Value for the `ST` certificate field.
`country` | String (Required) | Value for the `C` ssl field.
`expire` | Integer (Optional) | Value representing the number of days from _now_ through which the issued certificate cert will remain valid. The certificate will expire after this period.
`country` | String (Optional) | Value for the `C` ssl field.
`email` | String (Optional) | Value for the `email` ssl field.
`expire` | Integer (Optional) | 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
`extensions` | Hash (Optional) | Hash of X509 Extensions entries, in format `{ 'keyUsage' => { 'values' => %w( keyEncipherment digitalSignature), 'critical' => true } }`
`subject_alt_name` | Array (Optional) | Array of _Subject Alternative Name_ entries, in format `DNS:example.com` or `IP:1.2.3.4` _Default: empty_
`key_file` | String (Optional) | 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.
`key_pass` | String (Optional) | The passphrase for an existing key's passphrase
`key_length` | Integer (Optional) | The desired Bit Length of the generated key. _Default: 2048_
`key_type` | String (Optional) | The desired type of the generated key (rsa or ec). _Default: ec_
`key_length` | Integer (Optional) | The desired Bit Length of the generated key (if key_type is equal to 'rsa'). _Default: 2048_
`key_curve` | String (Optional) | 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
`csr_file` | String (Optional) | 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).
`ca_cert_file` | String (Optionel) | 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.
`ca_key_file` | String (Optionel) | 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.
`ca_key_pass` | String (Optionel) | The passphrase for CA private key's passphrase
`owner` | String (optional) | The owner of all files created by the resource. _Default: "root"_
`group` | String (optional) | The group of all files created by the resource. _Default: "root"_
`mode` | String or Integer (Optional) | The permission mode of all files created by the resource. _Default: "0400"_
......@@ -121,6 +131,33 @@ end
When executed, this recipe will generate a key certificate at `/etc/httpd/ssl/mycert.key`. It will then use that key to generate a new certificate file at `/etc/httpd/ssl/mycert.pem`.
In this example, an administrator wishes to create a x509 certificate signed with a CA certificate and key. In order to create the certificate, the administrator crafts this recipe:
```ruby
openssl_x509_certificate '/etc/ssl_test/my_signed_cert.crt' do
common_name 'www.f00bar.com'
ca_key_file '/etc/ssl_test/my_ca.key'
ca_cert_file '/etc/ssl_test/my_ca.crt'
expire 365
extensions(
'keyUsage' => {
'values' => %w(
keyEncipherment
digitalSignature),
'critical' => true,
},
'extendedKeyUsage' => {
'values' => %w(serverAuth),
'critical' => false,
}
)
subject_alt_name ['IP:127.0.0.1', 'DNS:localhost.localdomain']
end
```
When executed, this recipe will generate a key certificate at `/etc/ssl_test/my_signed_cert.key`. It will then use that key to generate a CSR and signed it with `my_ca.key/my_ca.crt`. A new certificate file at `/etc/ssl_test/my_signed_cert.cert` will be created as a result.
### openssl_x509_request
......@@ -159,6 +196,8 @@ openssl_x509_request '/etc/ssl_test/my_ec_request.csr' do
end
```
When executed, this recipe will generate a key certificate at `/etc/httpd/ssl/my_ec_request.key`. It will then use that key to generate a new csr file at `/etc/ssl_test/my_ec_request.csr`.
### openssl_dhparam
This resource generates dhparam.pem files. If a valid dhparam.pem file is found at the specified location, no new file will be created. If a file is found at the specified location but it is not a valid dhparam file, it will be overwritten.
......
driver:
name: dokken
privileged: true # because Docker and SystemD/Upstart
chef_version: <%= ENV['CHEF_VERSION'] || '13.10.0' %>
chef_version: <%= ENV['CHEF_VERSION'] || 'latest' %>
transport:
name: dokken
......
......@@ -161,5 +161,71 @@ module OpenSSLCookbook
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)
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
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
end
end
provides :openssl_x509 # legacy_name
provides :openssl_x509_cert
provides :openssl_x509_certificate
include OpenSSLCookbook::Helpers
property :path, String, name_property: true
property :owner, String, default: node['platform'] == 'windows' ? 'Administrator' : 'root'
property :group, String, default: node['root_group']
property :expire, Integer
property :expire, Integer, default: 365
property :mode, [Integer, String], default: '0644'
property :org, String, required: true
property :org_unit, String, required: true
property :country, String, required: true
property :common_name, String, required: true
property :country, String
property :state, String
property :city, String
property :org, String
property :org_unit, String
property :common_name, String
property :email, String
property :extensions, Hash, default: {}
property :subject_alt_name, Array, default: []
property :key_file, String
property :key_pass, String
property :key_type, equal_to: %w(rsa ec), default: 'rsa'
property :key_length, equal_to: [1024, 2048, 4096, 8192], default: 2048
property :key_curve, equal_to: %w(secp384r1 secp521r1 prime256v1), default: 'prime256v1'
property :csr_file, String
property :ca_cert_file, String
property :ca_key_file, String
property :ca_key_pass, String
action :create do
unless ::File.exist? new_resource.path
converge_by("Create #{@new_resource}") do
create_keys
cert_content = cert.to_pem
key_content = key.to_pem
file new_resource.path do
action :create_if_missing
mode new_resource.mode
owner new_resource.owner
group new_resource.group
sensitive true
content cert_content
content cert.to_pem
end
file new_resource.key_file do
action :create_if_missing
mode new_resource.mode
owner new_resource.owner
group new_resource.group
sensitive true
content key_content
if new_resource.csr_file.nil?
file new_resource.key_file do
action :create_if_missing
mode new_resource.mode
owner new_resource.owner
group new_resource.group
sensitive true
content key.to_pem
end
end
end
end
......@@ -56,67 +66,69 @@ action_class do
def key
@key ||= if priv_key_file_valid?(generate_key_file, new_resource.key_pass)
OpenSSL::PKey::RSA.new ::File.read(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
OpenSSL::PKey::RSA.new(new_resource.key_length)
gen_ec_priv_key(new_resource.key_curve)
end
@key
end
def cert
@cert ||= OpenSSL::X509::Certificate.new
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 gen_cert
cert
cert.subject = cert.issuer = OpenSSL::X509::Name.parse(subject)
cert.not_before = Time.now
cert.not_after = Time.now + (new_resource.expire.to_i * 24 * 60 * 60)
cert.public_key = key.public_key
cert.serial = 0x0
cert.version = 2
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 subject
@subject ||= '/C=' + new_resource.country +
'/ST=' + (new_resource.state ? new_resource.state : ' ') +
'/L=' + (new_resource.city ? new_resource.city : ' ') +
'/O=' + new_resource.org +
'/OU=' + new_resource.org_unit +
'/CN=' + new_resource.common_name
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
exts = []
exts << @ef.create_extension('basicConstraints', 'CA:TRUE', true)
exts << @ef.create_extension('subjectKeyIdentifier', 'hash')
extensions = gen_x509_extensions(new_resource.extensions)
unless new_resource.subject_alt_name.empty?
san = {}
counters = {}
new_resource.subject_alt_name.each do |an|
kind, value = an.split(':', 2)
counters[kind] ||= 0
counters[kind] += 1
san["#{kind}.#{counters[kind]}"] = value
end
@ef.config['alt_names'] = san
exts << @ef.create_extension('subjectAltName', '@alt_names')
extensions += gen_x509_extensions('subjectAltName' => { 'values' => new_resource.subject_alt_name, 'critical' => false })
end
exts
extensions
end
def create_keys
gen_cert
@ef ||= OpenSSL::X509::ExtensionFactory.new
@ef.subject_certificate = cert
@ef.issuer_certificate = cert
@ef.config = OpenSSL::Config.load(OpenSSL::Config::DEFAULT_CONFIG_FILE)
cert.extensions = extensions
cert.add_extension @ef.create_extension('authorityKeyIdentifier',
'keyid:always,issuer:always')
cert.sign key, OpenSSL::Digest::SHA256.new
def cert
cert = gen_x509_cert(request, extensions, ca_info, ca_private_key)
cert
end
end
......@@ -4,7 +4,7 @@ property :path, String, name_property: true
property :owner, String, default: node['platform'] == 'windows' ? 'Administrator' : 'root'
property :group, String, default: node['root_group']
property :mode, [Integer, String], default: '0644'
property :country, String, required: true
property :country, String
property :state, String
property :city, String
property :org, String
......@@ -36,7 +36,7 @@ action :create do
group new_resource.group
content key.to_pem
sensitive true
action :create
action :create_if_missing
end
end
end
......
......@@ -365,4 +365,131 @@ describe OpenSSLCookbook::Helpers do
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
include OpenSSLCookbook::Helpers
before(:all) do
@rsa_key = OpenSSL::PKey::RSA.new(2048)
@ec_key = OpenSSL::PKey::EC.generate('prime256v1')
@rsa_request = gen_x509_request(OpenSSL::X509::Name.new([%w(CN RSACert)]), @rsa_key)
@ec_request = gen_x509_request(OpenSSL::X509::Name.new([%w(CN ECCert)]), @ec_key)
@x509_extension = 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
end
......@@ -41,7 +41,7 @@ describe 'test::resources' do
end
end
context 'the openssl_x509 resource:' do
context 'the openssl_x509_certificate resource:' do
cached(:chef_run) do
runner = ChefSpec::ServerRunner.new(platform: 'ubuntu', version: '16.04', step_into: ['openssl_x509'])
runner.converge(described_recipe)
......@@ -58,6 +58,18 @@ describe 'test::resources' do
it 'The resource adds a file resource \'/etc/ssl_test/mycert.key\' with action create_if_missing' do
expect(chef_run).to create_file_if_missing('/etc/ssl_test/mycert.key')
end
it 'The resource adds a file resource \'/etc/ssl_test/mycert2.crt\' with action create_if_missing' do
expect(chef_run).to create_file_if_missing('/etc/ssl_test/mycert2.crt')
end
it 'The resource adds a file resource \'/etc/ssl_test/my_ca.crt\' with action create_if_missing' do
expect(chef_run).to create_file_if_missing('/etc/ssl_test/my_ca.crt')
end
it 'The resource adds a file resource \'/etc/ssl_test/my_ca.key\' with action create_if_missing' do
expect(chef_run).to create_file_if_missing('/etc/ssl_test/my_ca.crt')
end
end
context 'the openssl_x509_request resource:' do
......@@ -75,7 +87,7 @@ describe 'test::resources' do
end
it 'The resource adds a file resource \'/etc/ssl_test/my_ec_request.key\' with action create' do
expect(chef_run).to create_file('/etc/ssl_test/my_ec_request.key')
expect(chef_run).to create_file_if_missing('/etc/ssl_test/my_ec_request.key')
end
it 'The resource adds a file resource \'/etc/ssl_test/my_ec_request2.csr\' with action create' do
......@@ -87,7 +99,7 @@ describe 'test::resources' do
end
it 'The resource adds a file resource \'/etc/ssl_test/my_rsa_request.key\' with action create' do
expect(chef_run).to create_file('/etc/ssl_test/my_rsa_request.key')
expect(chef_run).to create_file_if_missing('/etc/ssl_test/my_rsa_request.key')
end
it 'The resource adds a file resource \'/etc/ssl_test/my_rsa_request2.csr\' with action create' do
......
......@@ -26,6 +26,16 @@
/etc/ssl_test/mycert.crt
/etc/ssl_test/mycert.key
/etc/ssl_test/mycert2.crt
/etc/ssl_test/my_ca.crt
/etc/ssl_test/my_ca.key
/etc/ssl_test/my_signed_cert.crt
/etc/ssl_test/my_signed_cert.key
/etc/ssl_test/my_ca2.key
/etc/ssl_test/my_ca2.csr
/etc/ssl_test/my_ca2.crt
/etc/ssl_test/my_signed_cert2.key
/etc/ssl_test/my_signed_cert2.csr
/etc/ssl_test/my_signed_cert2.crt
/etc/ssl_test/my_ec_request.csr
/etc/ssl_test/my_ec_request.key
/etc/ssl_test/my_ec_request2.csr
......@@ -101,7 +111,7 @@ openssl_ec_private_key '/etc/ssl_test/eckey_prime256v1_des3.pem' do
end
#
# X509 HERE
# X509_CERTIFICATE HERE
#
# Generate new key and certificate
......@@ -122,6 +132,102 @@ openssl_x509 '/etc/ssl_test/mycert2.crt' do
key_file '/etc/ssl_test/mycert.key'
end
# Generate a new CA certificate
openssl_x509 '/etc/ssl_test/my_ca.crt' do
common_name 'CA'
expire 3650
extensions(
'keyUsage' => {
'values' => %w(
keyCertSign
keyEncipherment
digitalSignature
cRLSign),
'critical' => true,
}
)
end
# Generate and sign a certificate with the CA
openssl_x509_certificate '/etc/ssl_test/my_signed_cert.crt' do
common_name 'mysignedcert.example.com'
ca_key_file '/etc/ssl_test/my_ca.key'
ca_cert_file '/etc/ssl_test/my_ca.crt'
expire 365
extensions(
'keyUsage' => {
'values' => %w(
keyEncipherment
digitalSignature),
'critical' => true,
},