require 'spec_helper' require 'r509/config' describe R509::CertificateAuthority::OptionsBuilder do it "errors when the object passed is not a CAConfig" do expect { R509::CertificateAuthority::OptionsBuilder.new("string") }.to raise_error(ArgumentError, "You must supply a R509::Config::CAConfig object to this class at instantiation") end context "enforces subject item policies" do before :all do config = R509::Config::CAConfig.new(:ca_cert => R509::Cert.new(:cert => TestFixtures::TEST_CA_CERT)) subject_item_policy = R509::Config::SubjectItemPolicy.new("CN" => { :policy => "required" }, "O" => { :policy => "required" }, "OU" => { :policy => "optional" }, "L" => { :policy => "required" }) profile = R509::Config::CertProfile.new( :default_md => "SHA512", :subject_item_policy => subject_item_policy ) config.set_profile("profile", profile) @builder = R509::CertificateAuthority::OptionsBuilder.new(config) end it "removes disallowed and keeps required/optional items" do csr = R509::CSR.new(:subject => [['C', 'US'], ['ST', 'Illinois'], ['L', 'Chicago'], ['O', 'Paul Kehrer'], ['OU', 'Enginerding'], ['CN', 'langui.sh']], :bit_strength => 1024) data = @builder.build_and_enforce(:csr => csr, :profile_name => 'profile') expect(data[:subject].to_s).to eq('/L=Chicago/O=Paul Kehrer/OU=Enginerding/CN=langui.sh') end it "raises error when required item is missing" do csr = R509::CSR.new(:subject => [['ST', 'Illinois'], ['L', 'Chicago'], ['O', 'Paul Kehrer']], :bit_strength => 1024) expect { @builder.build_and_enforce(:csr => csr, :profile_name => 'profile') }.to raise_error(R509::R509Error, /This profile requires you supply/) end end it "raises error on invalid signature" do config = R509::Config::CAConfig.new(:ca_cert => R509::Cert.new(:cert => TestFixtures::TEST_CA_CERT)) profile = R509::Config::CertProfile.new( :default_md => "SHA512" ) config.set_profile("profile", profile) builder = R509::CertificateAuthority::OptionsBuilder.new(config) csr = R509::CSR.new(:csr => TestFixtures::CSR_INVALID_SIGNATURE) expect { builder.build_and_enforce(:csr => csr, :profile_name => 'profile') }.to raise_error(R509::R509Error, 'Request signature is invalid.') spki = R509::SPKI.new(:spki => TestFixtures::SPKI_INVALID_SIGNATURE) expect { builder.build_and_enforce(:spki => spki, :profile_name => 'profile') }.to raise_error(R509::R509Error, 'Request signature is invalid.') end context "extension builder" do before :all do @config = R509::Config::CAConfig.new(:ca_cert => R509::Cert.new(:cert => TestFixtures::TEST_CA_CERT)) @csr = R509::CSR.new(:csr => TestFixtures::CSR) end it "adds basic constraints" do profile = R509::Config::CertProfile.new( :basic_constraints => { :ca => false } ) @config.set_profile("profile", profile) builder = R509::CertificateAuthority::OptionsBuilder.new(@config) data = builder.build_and_enforce(:csr => @csr, :profile_name => 'profile') ext = data[:extensions].select { |el| el.is_a?(R509::Cert::Extensions::BasicConstraints) } expect(ext.size).to eq(1) expect(ext[0].is_ca?).to be false end it "creates subject key identifier" do profile = R509::Config::CertProfile.new @config.set_profile("profile", profile) builder = R509::CertificateAuthority::OptionsBuilder.new(@config) data = builder.build_and_enforce(:csr => @csr, :profile_name => 'profile') expect(data[:extensions].select { |el| el.is_a?(R509::Cert::Extensions::SubjectKeyIdentifier) }.size).to eq(1) end it "creates authority key identifier" do profile = R509::Config::CertProfile.new @config.set_profile("profile", profile) builder = R509::CertificateAuthority::OptionsBuilder.new(@config) data = builder.build_and_enforce(:csr => @csr, :profile_name => 'profile') expect(data[:extensions].select { |el| el.is_a?(R509::Cert::Extensions::AuthorityKeyIdentifier) }.size).to eq(1) end it "adds key usage" do profile = R509::Config::CertProfile.new( :key_usage => { :value => ['keyEncipherment'] } ) @config.set_profile("profile", profile) builder = R509::CertificateAuthority::OptionsBuilder.new(@config) data = builder.build_and_enforce(:csr => @csr, :profile_name => 'profile') ext = data[:extensions].select { |el| el.is_a?(R509::Cert::Extensions::KeyUsage) } expect(ext.size).to eq(1) expect(ext[0].allowed_uses).to eq(['keyEncipherment']) end it "adds extended key usage" do profile = R509::Config::CertProfile.new( :extended_key_usage => { :value => ['serverAuth'] } ) @config.set_profile("profile", profile) builder = R509::CertificateAuthority::OptionsBuilder.new(@config) data = builder.build_and_enforce(:csr => @csr, :profile_name => 'profile') ext = data[:extensions].select { |el| el.is_a?(R509::Cert::Extensions::ExtendedKeyUsage) } expect(ext.size).to eq(1) expect(ext[0].allowed_uses).to eq(['serverAuth']) end it "adds certificate policies" do profile = R509::Config::CertProfile.new( :certificate_policies => { :value => [{ :policy_identifier => "2.16.840.1.99999.21.234" }] } ) @config.set_profile("profile", profile) builder = R509::CertificateAuthority::OptionsBuilder.new(@config) data = builder.build_and_enforce(:csr => @csr, :profile_name => 'profile') expect(data[:extensions].select { |el| el.is_a?(R509::Cert::Extensions::CertificatePolicies) }.size).to eq(1) end it "adds CRL distribution points" do cdp = R509::Cert::Extensions::CRLDistributionPoints.new(:value => [{ :type => 'URI', :value => 'http://crl.domain.com/crl.crl' }]) profile = R509::Config::CertProfile.new( :crl_distribution_points => cdp ) @config.set_profile("profile", profile) builder = R509::CertificateAuthority::OptionsBuilder.new(@config) data = builder.build_and_enforce(:csr => @csr, :profile_name => 'profile') expect(data[:extensions].select { |el| el.is_a?(R509::Cert::Extensions::CRLDistributionPoints) }.size).to eq(1) end it "adds authority info access" do args = { :ca_issuers_location => [{ :type => 'URI', :value => 'http://www.domain.com' }], :ocsp_location => [{ :type => 'URI', :value => 'http://ocsp.domain.com' }], :critical => false } aia = R509::Cert::Extensions::AuthorityInfoAccess.new(args) profile = R509::Config::CertProfile.new( :authority_info_access => aia ) @config.set_profile("profile", profile) builder = R509::CertificateAuthority::OptionsBuilder.new(@config) data = builder.build_and_enforce(:csr => @csr, :profile_name => 'profile') expect(data[:extensions].select { |el| el.is_a?(R509::Cert::Extensions::AuthorityInfoAccess) }.size).to eq(1) end it "adds inhibit any policy" do profile = R509::Config::CertProfile.new( :inhibit_any_policy => { :value => 1 } ) @config.set_profile("profile", profile) builder = R509::CertificateAuthority::OptionsBuilder.new(@config) data = builder.build_and_enforce(:csr => @csr, :profile_name => 'profile') expect(data[:extensions].select { |el| el.is_a?(R509::Cert::Extensions::InhibitAnyPolicy) }.size).to eq(1) end it "adds policy constraints" do profile = R509::Config::CertProfile.new( :policy_constraints => { :inhibit_policy_mapping => 1 } ) @config.set_profile("profile", profile) builder = R509::CertificateAuthority::OptionsBuilder.new(@config) data = builder.build_and_enforce(:csr => @csr, :profile_name => 'profile') expect(data[:extensions].select { |el| el.is_a?(R509::Cert::Extensions::PolicyConstraints) }.size).to eq(1) end it "adds name constraints" do profile = R509::Config::CertProfile.new( :name_constraints => { :permitted => [{ :type => "URI", :value => "domain.com" }] } ) @config.set_profile("profile", profile) builder = R509::CertificateAuthority::OptionsBuilder.new(@config) data = builder.build_and_enforce(:csr => @csr, :profile_name => 'profile') expect(data[:extensions].select { |el| el.is_a?(R509::Cert::Extensions::NameConstraints) }.size).to eq(1) end it "adds OCSP no check" do profile = R509::Config::CertProfile.new( :ocsp_no_check => { :value => true } ) @config.set_profile("profile", profile) builder = R509::CertificateAuthority::OptionsBuilder.new(@config) data = builder.build_and_enforce(:csr => @csr, :profile_name => 'profile') expect(data[:extensions].select { |el| el.is_a?(R509::Cert::Extensions::OCSPNoCheck) }.size).to eq(1) end end context "extension merging" do before :all do @config = R509::Config::CAConfig.new(:ca_cert => R509::Cert.new(:cert => TestFixtures::TEST_CA_CERT)) @csr = R509::CSR.new(:csr => TestFixtures::CSR) profile = R509::Config::CertProfile.new( :ocsp_no_check => { :value => true }, :key_usage => { :value => ['digitalSignature'] } ) @config.set_profile("profile", profile) @builder = R509::CertificateAuthority::OptionsBuilder.new(@config) end it "adds extensions that don't exist in the profile" do exts = [R509::Cert::Extensions::ExtendedKeyUsage.new(:value => ['timeStamping']), R509::Cert::Extensions::InhibitAnyPolicy.new(:value => 1)] data = @builder.build_and_enforce(:csr => @csr, :extensions => exts, :profile_name => 'profile') expect(data[:extensions].size).to eq(6) expect(data[:extensions].select { |el| el.is_a?(R509::Cert::Extensions::InhibitAnyPolicy) }.size).to eq(1) expect(data[:extensions].select { |el| el.is_a?(R509::Cert::Extensions::ExtendedKeyUsage) }.size).to eq(1) expect(data[:extensions].select { |el| el.is_a?(R509::Cert::Extensions::KeyUsage) }.size).to eq(1) expect(data[:extensions].select { |el| el.is_a?(R509::Cert::Extensions::OCSPNoCheck) }.size).to eq(1) end it "replaces extensions that already exist in the profile" do exts = [R509::Cert::Extensions::KeyUsage.new(:value => ['digitalSignature'])] data = @builder.build_and_enforce(:csr => @csr, :extensions => exts, :profile_name => 'profile') expect(data[:extensions].size).to eq(4) expect(data[:extensions].select { |el| el.is_a?(R509::Cert::Extensions::OCSPNoCheck) }.size).to eq(1) ku = data[:extensions].select { |el| el.is_a?(R509::Cert::Extensions::KeyUsage) } expect(ku[0].allowed_uses).to eq(['digitalSignature']) end end context "enforces message_digest without an allowed_message_digests array in the profile" do before :all do config = R509::Config::CAConfig.new(:ca_cert => R509::Cert.new(:cert => TestFixtures::TEST_CA_CERT)) profile = R509::Config::CertProfile.new( :default_md => "SHA512" ) config.set_profile("profile", profile) @builder = R509::CertificateAuthority::OptionsBuilder.new(config) @csr = R509::CSR.new(:csr => TestFixtures::CSR) end it "issues with all digest types" do R509::MessageDigest::KNOWN_MDS.each do |md| options = { :csr => R509::CSR.new(:csr => @csr), :message_digest => md, :profile_name => 'profile' } enforced = @builder.build_and_enforce(options) expect(enforced[:message_digest].upcase).to eq(md) end end end context "enforces message_digest with an allowed_message_digests array in the profile" do before :all do config = R509::Config::CAConfig.new(:ca_cert => R509::Cert.new(:cert => TestFixtures::TEST_CA_CERT)) profile = R509::Config::CertProfile.new( :basic_constraints => { :ca => false }, :key_usage => { :value => ["digitalSignature"] }, :allowed_mds => ['sha256', 'sha1', 'sha384'], :default_md => 'sha256' ) config.set_profile("profile", profile) @builder = R509::CertificateAuthority::OptionsBuilder.new(config) @csr = R509::CSR.new(:csr => TestFixtures::CSR) end it "passes a disallowed hash" do expect { @builder.build_and_enforce(:csr => @csr, :message_digest => 'md5', :profile_name => "profile") }.to raise_error(R509::R509Error, 'The message digest passed is not allowed by this configuration. Allowed digests: SHA256, SHA1, SHA384') end it "permits an allowed hash (not default)" do data = @builder.build_and_enforce(:csr => @csr, :message_digest => "sha384", :profile_name => "profile") expect(data[:message_digest]).to eq('sha384') end it "returns the default hash if no hash is passed" do data = @builder.build_and_enforce(:csr => @csr, :profile_name => "profile") expect(data[:message_digest]).to eq('sha256') end end context "enforces not_after" do before :all do config = R509::Config::CAConfig.new(:ca_cert => R509::Cert.new(:cert => TestFixtures::TEST_CA_CERT)) profile = R509::Config::CertProfile.new( :basic_constraints => { :ca => false }, :key_usage => { :value => ["digitalSignature"] }, :allowed_mds => ['sha256', 'sha1', 'sha384'], :default_md => 'sha256' ) config.set_profile("profile", profile) @builder = R509::CertificateAuthority::OptionsBuilder.new(config) @csr = R509::CSR.new(:csr => TestFixtures::CSR) end it "allows not_before and not_after through" do not_before = Time.now - 3600 not_after = Time.now + 3600 hash = @builder.build_and_enforce( :csr => @csr, :message_digest => 'sha256', :profile_name => 'profile', :not_before => not_before, :not_after => not_after ) expect(hash[:not_before]).to eq(not_before) expect(hash[:not_after]).to eq(not_after) end it "does not add a not_before or not_after key if not passed" do hash = @builder.build_and_enforce( :csr => @csr, :message_digest => 'sha256', :profile_name => 'profile' ) expect(hash.key?(:not_before)).to be false expect(hash.key?(:not_after)).to be false end it "raises error when not_after is after the issuing CA's expiry" do expect do @builder.build_and_enforce( :csr => @csr, :message_digest => 'sha256', :profile_name => 'profile', :not_after => Time.now + 86400 * 7300 * 25 ) end.to raise_error(R509::R509Error, 'The requested certificate lifetime would exceed the issuing CA.') end end end