mirror of
https://github.com/inspec/inspec
synced 2024-11-26 22:50:36 +00:00
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:
parent
2d9d7aa106
commit
f66f0b3a18
11 changed files with 569 additions and 0 deletions
41
docs/resources/rsa_key.md
Normal file
41
docs/resources/rsa_key.md
Normal 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
|
161
docs/resources/x509_certificate.md
Normal file
161
docs/resources/x509_certificate.md
Normal 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
|
|
@ -119,6 +119,7 @@ require 'resources/postgres_session'
|
||||||
require 'resources/powershell'
|
require 'resources/powershell'
|
||||||
require 'resources/processes'
|
require 'resources/processes'
|
||||||
require 'resources/registry_key'
|
require 'resources/registry_key'
|
||||||
|
require 'resources/rsa_key'
|
||||||
require 'resources/security_policy'
|
require 'resources/security_policy'
|
||||||
require 'resources/service'
|
require 'resources/service'
|
||||||
require 'resources/shadow'
|
require 'resources/shadow'
|
||||||
|
@ -131,6 +132,7 @@ require 'resources/windows_feature'
|
||||||
require 'resources/windows_task'
|
require 'resources/windows_task'
|
||||||
require 'resources/xinetd'
|
require 'resources/xinetd'
|
||||||
require 'resources/wmi'
|
require 'resources/wmi'
|
||||||
|
require 'resources/x509_certificate'
|
||||||
require 'resources/yum'
|
require 'resources/yum'
|
||||||
require 'resources/zfs_dataset'
|
require 'resources/zfs_dataset'
|
||||||
require 'resources/zfs_pool'
|
require 'resources/zfs_pool'
|
||||||
|
|
57
lib/resources/rsa_key.rb
Normal file
57
lib/resources/rsa_key.rb
Normal 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
|
133
lib/resources/x509_certificate.rb
Normal file
133
lib/resources/x509_certificate.rb
Normal 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
|
|
@ -134,6 +134,10 @@ class MockLoader
|
||||||
'/etc/xinetd.d/echo' => mockfile.call('xinetd.d_echo'),
|
'/etc/xinetd.d/echo' => mockfile.call('xinetd.d_echo'),
|
||||||
'/etc/sysctl.conf' => mockfile.call('sysctl.conf'),
|
'/etc/sysctl.conf' => mockfile.call('sysctl.conf'),
|
||||||
'/etc/postgresql/9.4/main/postgresql.conf' => mockfile.call('postgresql.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
|
# create all mock commands
|
||||||
|
|
9
test/unit/mock/files/test_ca_public.key.pem
Normal file
9
test/unit/mock/files/test_ca_public.key.pem
Normal 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-----
|
24
test/unit/mock/files/test_certificate.rsa.crt.pem
Normal file
24
test/unit/mock/files/test_certificate.rsa.crt.pem
Normal 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-----
|
27
test/unit/mock/files/test_certificate.rsa.key.pem
Normal file
27
test/unit/mock/files/test_certificate.rsa.key.pem
Normal 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-----
|
19
test/unit/resources/rsa_key_test.rb
Normal file
19
test/unit/resources/rsa_key_test.rb
Normal 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
|
92
test/unit/resources/x509_certificate_test.rb
Normal file
92
test/unit/resources/x509_certificate_test.rb
Normal 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
|
Loading…
Reference in a new issue