diff --git a/docs/resources/key_rsa.md b/docs/resources/key_rsa.md new file mode 100644 index 000000000..691fe1f86 --- /dev/null +++ b/docs/resources/key_rsa.md @@ -0,0 +1,70 @@ +--- +title: The key_rsa Resource +--- + +# key_rsa + +Use the `key_rsa` 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 `key_rsa` resource block declares a `key file` to be tested. + + describe key_rsa('mycertificate.key') do + it { should be_private } + it { should be_public } + its('public_key') { should match "-----BEGIN PUBLIC KEY-----\n3597459df9f3982" } + its('key_length') { should eq 2048 } + end + +You can use an optional passphrase with `key_rsa` + + describe key_rsa('mycertificate.key', 'passphrase') do + it { should be_private } + end + +## Supported Properties + +### public? + +To verify if a key is public use the following: + + describe key_rsa('/etc/pki/www.mywebsite.com.key') do + it { should be_public } + end + +### public_key (String) + +The `public_key` property returns the public part of the RSA key pair + + describe key_rsa('/etc/pki/www.mywebsite.com.key') do + its('public_key') { should match "-----BEGIN PUBLIC KEY-----\n3597459df9f3982......" } + end + +### private? + +This property verifies that the key includes a private key: + + describe key_rsa('/etc/pki/www.mywebsite.com.key') do + it { should be_private } + end + + +### private_key (String) + +The `private_key` property returns the private key or the RSA key pair. + + describe key_rsa('/etc/pki/www.mywebsite.com.key') do + its('private_key') { should match "-----BEGIN RSA PRIVATE KEY-----\nMIIJJwIBAAK......" } + end + +### key_length + +The `key_length` property allows testing the number of bits in the key pair. + + describe key_rsa('/etc/pki/www.mywebsite.com.key') do + its('key_length') { should eq 2048 } + end diff --git a/docs/resources/x509_certificate.md b/docs/resources/x509_certificate.md new file mode 100644 index 000000000..36c25c334 --- /dev/null +++ b/docs/resources/x509_certificate.md @@ -0,0 +1,146 @@ +--- +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('validity_in_days') { should be > 30 } + end + +## Supported Properties + +### subject.XX + +`subject` property makes it easier to access individual subject elements. + + describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do + its('subject.CN') { should eq "www.mywebsite.com" } + end + +### subject_dn (String) + +The `subject_dn` string returns the distinguished name of the subject field. It contains several fields separated by forward slashes. The field identifiers are the same ones used by OpenSSL to generate CSR's and certs. Use `subject.XX` instead to access the parsed version. + +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_dn') { should match "CN=www.mywebsite.com" } + end + +### issuer.XX + +`issuer` makes it easier to access individual issuer elements. + + describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do + its('issuer.CN') { should eq "Acme Trust CA" } + end + +### issuer_dn (String) + +The `issuer_dn` is the distinguished name 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_cn') { should match "CN=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('/etc/pki/www.mywebsite.com.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 + + +### validity_in_days (Float) + +The `validity_in_days` 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('validity_in_days') { 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. + + describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do + its('serial') { should eq 9623283588743302433 } + end + +### version (Integer) + +The `version` property exposes the certificate version. + + describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do + its('version') { should eq 2 } + end + +### 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 + # 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') { should include 'Non Repudiation' } + its('extensions.keyUsage') { should 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 diff --git a/lib/inspec/resource.rb b/lib/inspec/resource.rb index 4163f5a09..29fdc7916 100644 --- a/lib/inspec/resource.rb +++ b/lib/inspec/resource.rb @@ -95,6 +95,7 @@ require 'resources/iptables' require 'resources/json' require 'resources/kernel_module' require 'resources/kernel_parameter' +require 'resources/key_rsa' require 'resources/limits_conf' require 'resources/login_def' require 'resources/mount' @@ -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' diff --git a/lib/resources/key_rsa.rb b/lib/resources/key_rsa.rb new file mode 100644 index 000000000..190125578 --- /dev/null +++ b/lib/resources/key_rsa.rb @@ -0,0 +1,67 @@ +# encoding: utf-8 +# author: Richard Nixon +# author: Christoph Hartmann + +require 'openssl' +require 'hashie/mash' + +module Inspec::Resources + class RsaKey < Inspec.resource(1) + name 'key_rsa' + desc 'public/private RSA key pair test' + example " + describe rsa_key('/etc/pki/www.mywebsite.com.key') do + its('public_key') { should match /BEGIN RSA PUBLIC KEY/ } + end + + describe rsa_key('/etc/pki/www.mywebsite.com.key', 'passphrase') do + it { should be_private } + it { should be_public } + end + " + + def initialize(keypath, passphrase = nil) + @key_path = keypath + @key_file = inspec.file(@key_path) + @key = nil + @passphrase = passphrase + + return skip_resource "Unable to find key file #{@key_path}" unless @key_file.exist? + + begin + @key = OpenSSL::PKey.read(@key_file.content, @passphrase) + rescue OpenSSL::PKey::RSAError => _ + return skip_resource "Unable to load key file #{@key_path}" + end + end + + def public? + return if @key.nil? + @key.public? + end + + def public_key + return if @key.nil? + @key.public_key.to_s + end + + def private? + return if @key.nil? + @key.private? + end + + def private_key + return if @key.nil? + @key.to_s + end + + def key_length + return if @key.nil? + @key.public_key.n.num_bytes * 8 + end + + def to_s + "rsa_key #{@key_path}" + end + end +end diff --git a/lib/resources/x509_certificate.rb b/lib/resources/x509_certificate.rb new file mode 100644 index 000000000..21c3e89fb --- /dev/null +++ b/lib/resources/x509_certificate.rb @@ -0,0 +1,143 @@ +# encoding: utf-8 +# author: Richard Nixon +# author: Christoph Hartmann + +require 'openssl' +require 'hashie/mash' + +module Inspec::Resources + class X509CertificateResource < Inspec.resource(1) # rubocop:disable Metrics/ClassLength + 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('validity_in_days') { should be > 30 } + end + + describe x509_certificate('trials/x509/cert.pem') do + it { should be_certificate } + it { should be_valid } + its('fingerprint') { should eq '62b137bdf427e7273dc2e487877b3033e4c8ce17' } + its('signature_algorithm') { should eq 'sha1WithRSAEncryption' } + its('validity_in_days') { should_not be < 100 } + its('validity_in_days') { should be >= 100 } + its('subject_dn') { should eq '/C=DE/ST=Berlin/L=Berlin/O=InSpec/OU=Chef Software, Inc/CN=inspec.io/emailAddress=support@chef.io' } + its('subject.C') { should eq 'DE' } + its('subject.emailAddress') { should_not be_empty } + its('subject.emailAddress') { should eq 'support@chef.io' } + its('issuer_dn') { should eq '/C=DE/ST=Berlin/L=Berlin/O=InSpec/OU=Chef Software, Inc/CN=inspec.io/emailAddress=support@chef.io' } + its('key_length') { should be >= 2048 } + its('extensions.subjectKeyIdentifier') { should cmp 'A5:16:0B:12:F4:48:0F:06:6C:32:29:67:98:12:DF:3D:0D:75:9D:5C' } + end + " + + # @see https://tools.ietf.org/html/rfc5280#page-23 + def initialize(filename) + @certpath = filename + @issuer = nil + @parsed_subject = nil + @parsed_issuer = nil + @extensions = nil + + file = inspec.file(@certpath) + return skip_resource "Unable to find certificate file #{@certpath}" unless file.exist? + + begin + @cert = OpenSSL::X509::Certificate.new file.content + rescue OpenSSL::X509::CertificateError + @cert = nil + return skip_resource "Unable to load certificate #{@certpath}" + end + end + + # Forward these methods directly to OpenSSL::X509::Certificate instance + %w{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 certificate? + !@cert.nil? + end + + def fingerprint + return if @cert.nil? + OpenSSL::Digest::SHA1.new(@cert.to_der).to_s + end + + def serial + return if @cert.nil? + @cert.serial.to_i + end + + def subject_dn + return if @cert.nil? + @cert.subject.to_s + end + + def subject + return if @cert.nil? + # 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_dn + return if @cert.nil? + @cert.issuer.to_s + end + + def issuer + return if @cert.nil? + # 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 + return if @cert.nil? + @cert.public_key.n.num_bytes * 8 + end + + def validity_in_days + (not_after - Time.now.utc) / 86400 + end + + def valid? + now = Time.now + certificate? && (now >= not_before && now <= not_after) + 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(&: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 diff --git a/test/cookbooks/os_prepare/metadata.rb b/test/cookbooks/os_prepare/metadata.rb index 4f1b8a3cb..c6bce4cc7 100644 --- a/test/cookbooks/os_prepare/metadata.rb +++ b/test/cookbooks/os_prepare/metadata.rb @@ -12,3 +12,4 @@ depends 'postgresql' depends 'httpd', '~> 0.2' depends 'windows' depends 'ssh-hardening' +depends 'openssl' diff --git a/test/cookbooks/os_prepare/recipes/default.rb b/test/cookbooks/os_prepare/recipes/default.rb index 1d85a33a4..b9f7ae435 100644 --- a/test/cookbooks/os_prepare/recipes/default.rb +++ b/test/cookbooks/os_prepare/recipes/default.rb @@ -18,6 +18,7 @@ include_recipe('os_prepare::package') include_recipe('os_prepare::registry_key') include_recipe('os_prepare::iis_site') include_recipe('os_prepare::iptables') unless node['osprepare']['docker'] +include_recipe('os_prepare::x509') # config file parsing include_recipe('os_prepare::json_yaml_csv_ini') diff --git a/test/cookbooks/os_prepare/recipes/x509.rb b/test/cookbooks/os_prepare/recipes/x509.rb new file mode 100644 index 000000000..30e4d9617 --- /dev/null +++ b/test/cookbooks/os_prepare/recipes/x509.rb @@ -0,0 +1,15 @@ +if node['platform_family'] != 'windows' + + openssl_x509 '/tmp/mycert.pem' do + common_name 'www.f00bar.com' + org 'Foo Bar' + org_unit 'Lab' + country 'US' + expire 360 + end + + openssl_rsa_key '/tmp/server.key' do + key_length 2048 + end + +end diff --git a/test/helper.rb b/test/helper.rb index a92577961..37179c8f4 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -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 diff --git a/test/integration/default/x509_spec.rb b/test/integration/default/x509_spec.rb new file mode 100644 index 000000000..f19645243 --- /dev/null +++ b/test/integration/default/x509_spec.rb @@ -0,0 +1,24 @@ +# encoding: utf-8 + +if os.windows? + STDERR.puts "\033[1;33mTODO: Not running #{__FILE__} because we are not on Linux.\033[0m" + return +end + +describe x509_certificate('/tmp/mycert.pem') do + it { should be_certificate } + it { should be_valid } + its('signature_algorithm') { should eq 'sha256WithRSAEncryption' } + its('validity_in_days') { should_not be < 100 } + its('validity_in_days') { should be >= 100 } + its('subject_dn') { should eq '/C=US/O=Foo Bar/OU=Lab/CN=www.f00bar.com' } + its('subject.C') { should eq 'US' } + its('issuer_dn') { should eq '/C=US/O=Foo Bar/OU=Lab/CN=www.f00bar.com' } + its('key_length') { should be >= 2048 } +end + +describe key_rsa('/tmp/server.key') do + it { should be_private } + it { should be_public } + its('key_length') { should eq 2048 } +end diff --git a/test/unit/mock/files/test_ca_public.key.pem b/test/unit/mock/files/test_ca_public.key.pem new file mode 100644 index 000000000..a7a48034f --- /dev/null +++ b/test/unit/mock/files/test_ca_public.key.pem @@ -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----- diff --git a/test/unit/mock/files/test_certificate.rsa.crt.pem b/test/unit/mock/files/test_certificate.rsa.crt.pem new file mode 100644 index 000000000..bc2bc68b7 --- /dev/null +++ b/test/unit/mock/files/test_certificate.rsa.crt.pem @@ -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----- diff --git a/test/unit/mock/files/test_certificate.rsa.key.pem b/test/unit/mock/files/test_certificate.rsa.key.pem new file mode 100644 index 000000000..b028d4655 --- /dev/null +++ b/test/unit/mock/files/test_certificate.rsa.key.pem @@ -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----- diff --git a/test/unit/resources/key_rsa_test.rb b/test/unit/resources/key_rsa_test.rb new file mode 100644 index 000000000..442e99a3f --- /dev/null +++ b/test/unit/resources/key_rsa_test.rb @@ -0,0 +1,17 @@ +# encoding: utf-8 +# author: Richard Nixon + +require 'helper' +require 'inspec/resource' + +describe 'Inspec::Resources::RsaKey' do + let (:resource_key) { load_resource('key_rsa', '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 diff --git a/test/unit/resources/x509_certificate_test.rb b/test/unit/resources/x509_certificate_test.rb new file mode 100644 index 000000000..11885afa1 --- /dev/null +++ b/test/unit/resources/x509_certificate_test.rb @@ -0,0 +1,86 @@ +# 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' + ) + } + + it 'verify subject distingushed name' do + _(resource_cert.send('subject_dn')).must_match 'Inspec Test Certificate' + end + + it 'parses the certificate subject' do + _(resource_cert.send('subject').CN).must_equal 'Inspec Test Certificate' + _(resource_cert.send('subject').emailAddress).must_equal 'support@chef.io' + end + + it 'verify issue distingushed name' do + _(resource_cert.send('issuer_dn')).must_match 'Inspec Test CA' + end + + it 'parses the issuer' do + _(resource_cert.send('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 determine fingerprint' do + _(resource_cert.send('fingerprint')).must_equal '62bb500b0190ae47fd593c29a0b92ddbeb6c1eb6' + 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('validity_in_days')).must_equal 28 + end + # Expired + Time.stub :now, Time.new(2018, 4, 1, 1, 28, 57, '+00:00') do + _(resource_cert.send('validity_in_days')).must_equal (-31) + end + end +end