Initial support for x509_certificate and rsa_key

* Includes unit tests
* Includes 2 new resources
* Includes documentation

Signed-off-by: Richard Nixon <richard.nixon@btinternet.com>
This commit is contained in:
Richard Nixon 2017-03-15 23:57:31 +00:00 committed by Christoph Hartmann
parent 2d9d7aa106
commit f66f0b3a18
11 changed files with 569 additions and 0 deletions

41
docs/resources/rsa_key.md Normal file
View file

@ -0,0 +1,41 @@
---
title: The rsa_key Resource
---
# rsa_key
Use the `rsa_key` InSpec audit resource to test RSA public/private keypairs.
This resource is mainly useful when used in conjunction with the x509_certificate resource but it can also be used for checking SSH keys.
## Syntax
An `rsa_key` resource block declares a `key file` to be tested.
describe rsa_key('mycertificate.key') do
its('public_key') { should match "-----BEGIN PUBLIC KEY-----\n3597459df9f3982" }
its('key_length') { should eq 2048 }
end
## Supported Properties
### public_key (String)
The `public_key` property returns the public part of the RSA keypair
describe rsa_key('/etc/pki/www.mywebsite.com.key') do
its('public_key') { should match "-----BEGIN PUBLIC KEY-----\n3597459df9f3982......" }
end
### private_key (String)
See the `public key` property
### key_length
The key_length` property allows testing the number of bits in the keypair.
describe rsa_key('/etc/pki/www.mywebsite.com.key') do
its('key_length') { should eq 2048 }
end

View file

@ -0,0 +1,161 @@
---
title: The x509_certificate Resource
---
# x509_certificate
Use the `x509_certificate` InSpec audit resource to test the fields and validity of an x.509 certificate.
X.509 certificates use public/private key pairs to sign and encrypt documents
or communications over a network. They may also be used for authentication.
Examples include SSL certificates, S/MIME certificates and VPN authentication
certificates.
## Syntax
An `x509_certificate` resource block declares a certificate `key file` to be tested.
describe x509_certificate('mycertificate.pem') do
its('days_remaining') { should be > 30 }
end
It can optionally specify a private key file and a ca public key file for key verification
describe x509_certificate('mycertificate.cert','mycertificate.key','ca_key.pub') do
its('private_key_matches?') { should be true }
its('ca_key_matches?') { should be true }
end
## Supported Properties
### subject (String)
The `subject` string contains several fields seperated by forward slashes. The
field identifiers are the same ones used by OpenSSL to generate CSR's and certs.
e.g. `/C=US/L=Seattle/O=Chef Software Inc/OU=Chefs/CN=Richard Nixon`
describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do
its('subject') { should match "CN=www.mywebsite.com" }
end
### parsed_subject.XX
`parsed_subject` property makes it easier to access individual subject elements.
describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do
its('parsed_subject.CN') { should eq "www.mywebsite.com" }
end
### issuer (String)
The `issuer` string is copied from a CA (certificate authority) during the
certificate signing process. It describes which authority is guaranteeing the
identity of our certificate.
e.g. `/C=US/L=Seattle/CN=Acme Trust CA/emailAddress=support@acmetrust.org`
describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do
its('issuer') { should match "CN=Acme Trust CA" }
end
### parsed_issuer.XX
`parsed_issuer` makes it easier to access individual issuer elements.
describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do
its('parsed_issuer.CN') { should eq "Acme Trust CA" }
end
### public_key (String)
The `public_key` property returns a base64 encoded public key in PEM format.
describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do
its('public_key') { should match "-----BEGIN PUBLIC KEY-----\nblah blah blah..." }
end
### key_length (Integer)
The `key_length` property calculates the number of bits in the public key.
More bits increase security, but at the cost of speed and in extreme cases, compatibility.
describe x509_certificate('mycert.pem') do
its('key_length') { should be 2048 }
end
### signature_algorithm (String)
The `signature_algorithm` property describes which hash function was used by the CA to
sign the certificate.
describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do
its('signature_algorithm') { should be 'sha256WithRSAEncryption' }
end
### private_key_matches? (Boolean) ca_key_matches? (Boolean)
The `private_key_matches?` and `ca_key_matches?` methods check
* If the supplied private key matches the certificate
* If the CA public key belongs to the CA that signed the certificate
describe x509_certificate('mycertificate.cert','mycertificate.key','ca_key.pub') do
its('private_key_matches?') { should be true }
its('ca_key_matches?') { should be true }
end
### days_remaining (Float)
The `days_remaining` property can be used to check that certificates are not in
danger of expiring soon.
describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do
its('days_remaining') { should be > 30 }
end
### not_before and not_after (Time)
The `not_before` and `not_after` properties expose the start and end dates of certificate
validity. They are exposed as ruby Time class so that date arithmetic can be easily performed.
describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do
its('not_before') { should be <= Time.utc.now }
its('not_after') { should be >= Time.utc.now }
end
### serial (Integer)
The `serial` property exposes the serial number of the certificate. The serial number is set by the CA during the signing process and should be unique within that CA.
### version (Integer)
The `version` property exposes the certificate version.
### extensions (Hash)
The `extensions` hash property is mainly used to determine what the certificate can be used for.
describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do
its('extensions').length) {should eq 3 }
# Check what extension categories we have
its('extensions')) { should include 'keyUsage' }
its('extensions')) { should include 'extendedKeyUsage' }
its('extensions')) { should include 'subjectAltName' }
# Check examples of basic 'keyUsage'
its('extensions')['keyUsage']) { should include "Digital Signature" }
its('extensions')['keyUsage']).must_include "Non Repudiation"
its('extensions')['keyUsage']).must_include "Data Encipherment"
# Check examples of newer 'extendedKeyUsage'
its('extensions')['extendedKeyUsage']) { should include "TLS Web Server Authentication" }
its('extensions')['extendedKeyUsage']) { should include "Code Signing" }
# Check examples of 'subjectAltName'
its('extensions')['subjectAltName']) { should include "email:support@chef.io" }
end

View file

@ -119,6 +119,7 @@ require 'resources/postgres_session'
require 'resources/powershell'
require 'resources/processes'
require 'resources/registry_key'
require 'resources/rsa_key'
require 'resources/security_policy'
require 'resources/service'
require 'resources/shadow'
@ -131,6 +132,7 @@ require 'resources/windows_feature'
require 'resources/windows_task'
require 'resources/xinetd'
require 'resources/wmi'
require 'resources/x509_certificate'
require 'resources/yum'
require 'resources/zfs_dataset'
require 'resources/zfs_pool'

57
lib/resources/rsa_key.rb Normal file
View file

@ -0,0 +1,57 @@
# encoding: utf-8
# author: Richard Nixon
require 'openssl'
require 'hashie/mash'
module Inspec::Resources
class RsaKey < Inspec.resource(1)
name 'rsa_key'
desc 'Used to test RSA keys'
example "
describe rsa_key('/etc/pki/www.mywebsite.com.key') do
its('public_key') { should match /BEGIN RSA PUBLIC KEY/ }
end
"
def initialize(keypath)
@keypath = keypath
@keyfile = inspec.backend.file(@keypath)
if @keyfile.exist?
load_key
else
@key = RuntimeError.new('Key file not found')
end
end
private def load_key
@keyraw ||= @keyfile.content
@key ||= OpenSSL::PKey::RSA.new(@keyraw)
rescue OpenSSL::X509::RSAError => error_code
@key ||= error_code
end
def public_key
@key.public_key.to_s
end
def private_key
@key.to_s
end
def key_length
n.num_bytes * 8 # Answer in bits
end
def exist?
@keyfile.exist?
end
def key_length
@key.public_key.n.num_bytes * 8
end
def to_s
"rsa_key #{@keypath}"
end
end
end

View file

@ -0,0 +1,133 @@
# encoding: utf-8
# author: Richard Nixon
require 'openssl'
require 'hashie/mash'
module Inspec::Resources
class X509CertificateResource < Inspec.resource(1)
name 'x509_certificate'
desc 'Used to test x.509 certificates'
example "
describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do
its('subject') { should match /CN=My Website/ }
its('days_remaining') { should be > 30 }
end
"
def initialize(certpath,private_keypath=nil,ca_public_keypath=nil)
@certpath = certpath
@issuer = nil
@parsed_subject = nil
@parsed_issuer = nil
@extensions = nil
@cert = cert_from_file(certpath)
@key = key_from_file(private_keypath)
@cakey = key_from_file(ca_public_keypath)
end
private def cert_from_file(certpath)
certfile = inspec.backend.file(certpath)
if certfile.exist?
certraw = certfile.content
certcooked = OpenSSL::X509::Certificate.new(certraw)
else
certcooked = RuntimeError.new("Certificate #{certpath} not found")
end
certcooked
rescue OpenSSL::X509::CertificateError => error_code
error_code
end
private def key_from_file(keypath)
keyfile = inspec.backend.file(keypath)
if keyfile.exist?
keyraw = keyfile.content
keycooked = OpenSSL::PKey::RSA.new(keyraw)
else
keycooked = RuntimeError.new("Keyfile #{keypath} not found")
end
keycooked
rescue OpenSSL::PKey::RSAError => error_code
error_code
end
# Forward these methods directly to OpenSSL::X509::Certificate instance
%w{serial version not_before not_after signature_algorithm public_key }.each do |m|
define_method m.to_sym do |*args|
@cert.method(m.to_sym).call(*args)
end
end
def exist?
@certfile.exist?
end
def private_key_matches?
@cert.check_private_key(@key)
end
def ca_key_matches?
@cert.verify(@cakey)
end
def subject
@cert.subject.to_s
end
def parsed_subject
# Return cached subject if we have already parsed it
return @parsed_subject if @parsed_subject
# Use a Mash to make it easier to access hash elements in "its('subject') {should ...}"
@parsed_subject = Hashie::Mash.new(Hash[@cert.subject.to_a.map { |k, v, _| [k, v] }])
end
def issuer
@cert.issuer.to_s
end
def parsed_issuer
# Return cached subject if we have already parsed it
return @parsed_issuer if @parsed_issuer
# Use a Mash to make it easier to access hash elements in "its('issuer') {should ...}"
@parsed_issuer = Hashie::Mash.new(Hash[@cert.issuer.to_a.map { |k, v, _| [k, v] }])
end
def key_length
@cert.public_key.n.num_bytes * 8
end
def days_remaining
(@cert.not_after - Time.now.utc) / 86400
end
def extensions
# Return cached Mash if we already parsed the certificate extensions
return @extensions if @extensions
# Return the exception class if we failed to instantiate a Cert from file
return @cert unless @cert.respond_to? :extensions
# Use a Mash to make it easier to access hash elements in "its('entensions') {should ...}"
@extensions = Hashie::Mash.new({})
# Make sure standard extensions exist so we don't get nil for nil:NilClass
# when the user tests for extensions which aren't present
%w{
keyUsage extendedKeyUsage basicConstraints subjectKeyIdentifier
authorityKeyIdentifier subjectAltName issuerAltName authorityInfoAccess
crlDistributionPoints issuingDistributionPoint certificatePolicies
policyConstraints nameConstraints noCheck tlsfeature nsComment
}.each { |extension| @extensions[extension] ||= [] }
# Now parse the extensions into the Mash
extension_array = @cert.extensions.map { |e| e.to_s }
extension_array.each do |extension|
kv = extension.split(/ *= */, 2)
@extensions[kv.first] = kv.last.split(/ *, */)
end
@extensions
end
def to_s
"x509_certificate #{@certpath}"
end
end
end

View file

@ -134,6 +134,10 @@ class MockLoader
'/etc/xinetd.d/echo' => mockfile.call('xinetd.d_echo'),
'/etc/sysctl.conf' => mockfile.call('sysctl.conf'),
'/etc/postgresql/9.4/main/postgresql.conf' => mockfile.call('postgresql.conf'),
# Test certificate/key for x509_certificate using RSA keys in PEM format
'test_certificate.rsa.crt.pem' => mockfile.call('test_certificate.rsa.crt.pem'),
'test_certificate.rsa.key.pem' => mockfile.call('test_certificate.rsa.key.pem'),
'test_ca_public.key.pem' => mockfile.call('test_ca_public.key.pem'),
}
# create all mock commands

View file

@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm2Ceiaf2E+xnv2f0IHl8
SAd9j0CK+zuA1BbMStBaslkAD+XHTZ5vvC0NKyiuEFu/WpPegOavydpMaBD+l01Z
c3pHZxj9Ayl6uDCv27SKYTYmoiRybb1wFGMBuzb8Q2hseJH+XCx8rd1Kjn1VfAeC
5n36QoMJ8qghdnTSFSqVMP8IAz8hI0xKe89FRf5YbqLhYUmk+PMili0XxkwG723D
NXF62spg4runWmk//nsneGB5gqKLEHKxlgFDTYOrG2SgAcVtz5urAI+o24ikzhiN
nlZ5fmHKbpxn0wEHxVqQWieAx1gj5BLk8TUDZUdkWla8q9mJ3ZhxXm3H7OJ9vI65
gwIDAQAB
-----END PUBLIC KEY-----

View file

@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIIEGjCCAwKgAwIBAgIBJTANBgkqhkiG9w0BAQsFADCBljEXMBUGA1UEAwwOSW5z
cGVjIFRlc3QgQ0ExGjAYBgNVBAoMEUNoZWYgU29mdHdhcmUgSW5jMRMwEQYDVQQL
DApDZXJ0aWZpZXJzMQswCQYDVQQIDAJXQTELMAkGA1UEBhMCVVMxEDAOBgNVBAcM
B1NlYXR0bGUxHjAcBgkqhkiG9w0BCQEWD3N1cHBvcnRAY2hlZi5pbzAeFw0xNzAz
MDEwMTI4NTdaFw0xODAzMDEwMTI4NTdaMIGjMSAwHgYDVQQDDBdJbnNwZWMgVGVz
dCBDZXJ0aWZpY2F0ZTEaMBgGA1UECgwRQ2hlZiBTb2Z0d2FyZSBJbmMxFzAVBgNV
BAsMDkluc3BlYyBUZXN0ZXJzMQswCQYDVQQIDAJXQTELMAkGA1UEBhMCVVMxEDAO
BgNVBAcMB1NlYXR0bGUxHjAcBgkqhkiG9w0BCQEWD3N1cHBvcnRAY2hlZi5pbzCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMYtU6eHT0PhlPkYqbILlFk+
dGun7AQqXvkE9GHVv+xhupMp0c0fn80NKtSw8aYKgB/ngyFhABWKEz3PIWgI2bwG
J+yZV1EKXqCu33NgT4GANJa99xHtB/Z6nmZZ8rUPvxFVKqg8etXIHltwEQCtSDg8
Ibs4rk4u1zbvh08d33h+KIavxaaQduj2HyaNYwpre2MR8ZegBq2KxF+c7m0EdALT
wCc4NP2qgx+RiRyRE45aCMYfyi5xpWxtwycpRVPrz8b57NUyjNHXs4PFHn13q0bc
QwMEose7DXLV8GtMU6HOjvnv0rTT0dHi7yC0vIkfPX7jC+e2eABhVjDXak2KI6sC
AwEAAaNkMGIwDgYDVR0PAQH/BAQDAgTQMDQGA1UdJQEB/wQqMCgGCCsGAQUFBwME
BggrBgEFBQcDAgYIKwYBBQUHAwEGCCsGAQUFBwMDMBoGA1UdEQQTMBGBD3N1cHBv
cnRAY2hlZi5pbzANBgkqhkiG9w0BAQsFAAOCAQEAOYrs1VK5AA3kL39wh9PVHbqG
d54YnurpDVPzHBDAt6BS5naMQ5hFPlT9Mb9ksyh86B7m/MtVUCkRmUiKk12Pv3t8
bs05NRYy6efAAAe4lvmQaAxmPoRdHRWQkoX7BM7o6GdM7sJN9Wyz8iKlKwcpg9KL
OsBJ37TTkkMElr/yGFgVmm+uXWLsj5JqOYL+hNXkZBY42bMgcDodiOe7kCoPO3Vm
h0Ygd9TBqMoMSxQNVeD6hgsoej47XIs1K16LU31bExsFV4YW5bLezJNN9U0FW+il
LoDQcg6wxt8BvVuH7qxtzdu4uKDvLebvWQDeATjjdAu9m4t0AMtrkVk7dzDLUQ==
-----END CERTIFICATE-----

View file

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAxi1Tp4dPQ+GU+RipsguUWT50a6fsBCpe+QT0YdW/7GG6kynR
zR+fzQ0q1LDxpgqAH+eDIWEAFYoTPc8haAjZvAYn7JlXUQpeoK7fc2BPgYA0lr33
Ee0H9nqeZlnytQ+/EVUqqDx61cgeW3ARAK1IODwhuziuTi7XNu+HTx3feH4ohq/F
ppB26PYfJo1jCmt7YxHxl6AGrYrEX5zubQR0AtPAJzg0/aqDH5GJHJETjloIxh/K
LnGlbG3DJylFU+vPxvns1TKM0dezg8UefXerRtxDAwSix7sNctXwa0xToc6O+e/S
tNPR0eLvILS8iR89fuML57Z4AGFWMNdqTYojqwIDAQABAoIBAEhPd6u0Mpb5M6tk
dV7S3NFneYFipzqp3zeLuEQOg1YUHsjdxIDNHjjqqgsreTD1ueRqTC2cwDQbyoOO
FYlpWVFDCcRJ+1NFrluBE2V86eW9yvKJ5CH1VCd6fFuqTGYGldgUNFlooAVrXLBO
htWxZJ2oS0KOHwPGEZ8o7T1QEB9dfGpqROJJ444gnAWZ5iUTDqXwy3+pQlPUdAP3
A2VpBG/QyT6UKtUpCcAdkzENJpy8Z1BEIigLp+24fYtLRaIme8yEm/JrLneWqgYr
fNcKqh7z2MlNElOflyaT5381Kj3GgYM7qrPz6SQ92VEmUemg4xhMAsO9lMLEoUly
aT5InUkCgYEA75OXkqY7c12txpZxkRR49dTWc6V97RuSEfkJuTuJQe/bQ7vhKWFt
uRhpBjc6czfr3KcwLNz9axQpnzQmhVfJ7GyYH8VLwaAHgwEAiTcdOf3Xp0fdGe1N
wLcaeSxH+goMff410YWbtJGlyf5AetKl6XKsjeWa/+3vx1JcmnufYY0CgYEA08Mz
nNycP+74X9yfHWRMFi/DNhX3jnOc897yc1WC7TUj3+l9KcOdEz21uVOUZod+ThJJ
vUhmMXzBs7yeQiiv80Wm4ONrPjo0dgttsob3VFo419c5x9uzqJWhg9LqryQRFJKL
GskKIhwN8ErZil8Tm5yg6ihX8xh6L6SesGyR4BcCgYEA2pUci85jG4TzEecdQrMd
EZ3Y87agR/8JrKA9QOWS+7fto8T9UBX2WBRvbh5hk9IHvlBD4grWpCXHO9wG8U4B
i1YhDYui7MwnTl1Rsd+5KLnzUkp87jTW5eepnbjLCtS0RRf03m86eusQClWRWv5q
Ja5cxTIh0zOxu3fnyYLVDdkCgYAMzvXEOyPISjADvFhzcqmXffQUxWdf2mZX6dhI
WZe9uUUeOgU0DXzmuQjQ2NlVCkT9e+Wx6TslKyKcOIBqCAP8du4NFDRcYzDhIvfT
oI49L+fYRlBcYlGPlN1cF9nSFiBiWirHx/kw7vl4204lLHMHKoYhI6eOMKDTWOWw
TiDUqQKBgG+xIdvtIxpjtqddJwLgMlEhpVCdG5Xrpj9TjVfoEL2u7Lmr+beEf6DU
Xx7U8zTSI4RgaK/ckkPbI8s+WzBLvDCY3pWc58m8f+Od8hDKQsIijATE0se2lkPa
pspN5y2nI9dphprCzSrr5VOtffxIt88OX+ecCEwyR64uvoQtcLPj
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,19 @@
# encoding: utf-8
# author: Richard Nixon
require 'helper'
require 'inspec/resource'
describe 'Inspec::Resources::RsaKey' do
let (:resource_key) { load_resource('rsa_key', 'test_certificate.rsa.key.pem')}
it 'parses the public key' do
_(resource_key.send('public_key')).must_match "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxi1Tp4dPQ+GU+RipsguU\nWT50a6fsBCpe+QT0YdW/7GG6kynRzR+fzQ0q1LDxpgqAH+eDIWEAFYoTPc8haAjZ\nvAYn7JlXUQpeoK7fc2BPgYA0lr33Ee0H9nqeZlnytQ+/EVUqqDx61cgeW3ARAK1I\nODwhuziuTi7XNu+HTx3feH4ohq/FppB26PYfJo1jCmt7YxHxl6AGrYrEX5zubQR0\nAtPAJzg0/aqDH5GJHJETjloIxh/KLnGlbG3DJylFU+vPxvns1TKM0dezg8UefXer\nRtxDAwSix7sNctXwa0xToc6O+e/StNPR0eLvILS8iR89fuML57Z4AGFWMNdqTYoj\nqwIDAQAB\n-----END PUBLIC KEY-----\n"
end
it 'decodes the key length' do
_(resource_key.send('key_length')).must_equal 2048
end
end

View file

@ -0,0 +1,92 @@
# encoding: utf-8
# author: Richard Nixon
require 'helper'
require 'inspec/resource'
describe 'Inspec::Resources::X509Certificate' do
let (:resource_cert) {
load_resource(
'x509_certificate',
'test_certificate.rsa.crt.pem',
'test_certificate.rsa.key.pem',
'test_ca_public.key.pem'
)
}
it 'decodes the subject as a string' do
_(resource_cert.send('subject')).must_match 'Inspec Test Certificate'
end
it 'parses the certificate subject' do
_(resource_cert.send('parsed_subject').CN).must_equal 'Inspec Test Certificate'
_(resource_cert.send('parsed_subject').emailAddress).must_equal 'support@chef.io'
end
it 'decodes the issuer as a string' do
_(resource_cert.send('issuer')).must_match 'Inspec Test CA'
end
it 'parses the issuer' do
_(resource_cert.send('parsed_issuer').CN).must_equal 'Inspec Test CA'
end
it 'parses the public key' do
_(resource_cert.send('public_key').to_s).must_match "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxi1Tp4dPQ+GU+RipsguU\nWT50a6fsBCpe+QT0YdW/7GG6kynRzR+fzQ0q1LDxpgqAH+eDIWEAFYoTPc8haAjZ\nvAYn7JlXUQpeoK7fc2BPgYA0lr33Ee0H9nqeZlnytQ+/EVUqqDx61cgeW3ARAK1I\nODwhuziuTi7XNu+HTx3feH4ohq/FppB26PYfJo1jCmt7YxHxl6AGrYrEX5zubQR0\nAtPAJzg0/aqDH5GJHJETjloIxh/KLnGlbG3DJylFU+vPxvns1TKM0dezg8UefXer\nRtxDAwSix7sNctXwa0xToc6O+e/StNPR0eLvILS8iR89fuML57Z4AGFWMNdqTYoj\nqwIDAQAB\n-----END PUBLIC KEY-----\n"
end
it 'can check if the private key matches the certificate' do
_(resource_cert.send('private_key_matches?')).must_equal true
end
it 'can check if a CA key was used to sign this cert' do
_(resource_cert.send('ca_key_matches?')).must_equal true
end
it 'can determine the key length' do
_(resource_cert.send('key_length')).must_equal 2048
end
it 'parses the serial number' do
_(resource_cert.send('serial')).must_equal 37
end
it 'parses the signature algorithm' do
_(resource_cert.send('signature_algorithm')).must_equal 'sha256WithRSAEncryption'
end
it 'parses the x.509 certificate version' do
_(resource_cert.send('version')).must_equal 2
end
it 'includes the standard extensions even if they are not in the certificate' do
_(resource_cert.send('extensions').length).must_equal 16
_(resource_cert.send('extensions')).must_include 'keyUsage'
_(resource_cert.send('extensions')).must_include 'extendedKeyUsage'
_(resource_cert.send('extensions')).must_include 'subjectAltName'
end
it 'parses the x.509 certificate extensions' do
_(resource_cert.send('extensions')['keyUsage']).must_include "Digital Signature"
_(resource_cert.send('extensions')['keyUsage']).must_include "Non Repudiation"
_(resource_cert.send('extensions')['keyUsage']).must_include "Data Encipherment"
_(resource_cert.send('extensions')['extendedKeyUsage']).must_include "TLS Web Server Authentication"
_(resource_cert.send('extensions')['extendedKeyUsage']).must_include "Code Signing"
_(resource_cert.send('extensions')['subjectAltName']).must_include "email:support@chef.io"
end
it 'parses missing x.509 certificate extensions' do
_(resource_cert.send('extensions')['nameConstraints']).wont_include "Fried Chicken"
end
it 'calculates the remaining days of validity' do
# Still valid
Time.stub :now, Time.new(2018, 2, 1, 1, 28, 57, '+00:00') do
_(resource_cert.send('days_remaining')).must_equal 28
end
# Expired
Time.stub :now, Time.new(2018, 4, 1, 1, 28, 57, '+00:00') do
_(resource_cert.send('days_remaining')).must_equal (-31)
end
end
end