diff options
author | Luke Kanies <luke@madstop.com> | 2008-04-02 10:52:49 -0500 |
---|---|---|
committer | Luke Kanies <luke@madstop.com> | 2008-04-15 21:34:06 -0500 |
commit | 546ac97398caa1e9defb34df9567d798e4959020 (patch) | |
tree | 1317d56e127348e20270acac377cb8afbd13aa70 | |
parent | c98ad25403dbb27289048513af48d0b3e1723b18 (diff) | |
download | puppet-546ac97398caa1e9defb34df9567d798e4959020.tar.gz puppet-546ac97398caa1e9defb34df9567d798e4959020.tar.xz puppet-546ac97398caa1e9defb34df9567d798e4959020.zip |
Adding the first attempt at managing the certificate
revocation list.
-rw-r--r-- | lib/puppet/ssl/certificate_revocation_list.rb | 72 | ||||
-rwxr-xr-x | spec/unit/ssl/certificate_revocation_list.rb | 167 |
2 files changed, 239 insertions, 0 deletions
diff --git a/lib/puppet/ssl/certificate_revocation_list.rb b/lib/puppet/ssl/certificate_revocation_list.rb new file mode 100644 index 000000000..e892e276a --- /dev/null +++ b/lib/puppet/ssl/certificate_revocation_list.rb @@ -0,0 +1,72 @@ +require 'puppet/ssl/base' + +# Manage the CRL. +class Puppet::SSL::CertificateRevocationList < Puppet::SSL::Base + wraps OpenSSL::X509::CRL + + # Knows how to create a CRL with our system defaults. + def generate(cert, key) + Puppet.info "Creating a new SSL key for %s" % name + @content = wrapped_class.new + @content.issuer = cert.subject + @content.version = 1 + + @content + end + + def initialize(name, cert, key) + raise Puppet::Error, "Cannot manage the CRL when :cacrl is set to false" if [false, "false"].include?(Puppet[:cacrl]) + + @name = name + + read_or_generate(cert, key) + end + + # A stupid indirection method to make this easier to test. Yay. + def read_or_generate(cert, key) + unless read(Puppet[:cacrl]) + generate(cert, key) + save(key) + end + end + + # Revoke the certificate with serial number SERIAL issued by this + # CA. The REASON must be one of the OpenSSL::OCSP::REVOKED_* reasons + def revoke(serial, reason = OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE) + if @config[:cacrl] == 'false' + raise Puppet::Error, "Revocation requires a CRL, but ca_crl is set to 'false'" + end + time = Time.now + revoked = OpenSSL::X509::Revoked.new + revoked.serial = serial + revoked.time = time + enum = OpenSSL::ASN1::Enumerated(reason) + ext = OpenSSL::X509::Extension.new("CRLReason", enum) + revoked.add_extension(ext) + @content.add_revoked(revoked) + store_crl + end + + # Save the CRL to disk. Note that none of the other Base subclasses + # have this method, because they all use the indirector to find and save + # the CRL. + def save(key) + # Increment the crlNumber + e = @content.extensions.find { |e| e.oid == 'crlNumber' } + ext = @content.extensions.reject { |e| e.oid == 'crlNumber' } + crlNum = OpenSSL::ASN1::Integer(e ? e.value.to_i + 1 : 0) + ext << OpenSSL::X509::Extension.new("crlNumber", crlNum) + @content.extensions = ext + + # Set last/next update + now = Time.now + @content.last_update = now + # Keep CRL valid for 5 years + @content.next_update = now + 5 * 365*24*60*60 + + sign_with_key(@content) + Puppet.settings.write(:cacrl) do |f| + f.puts @content.to_pem + end + end +end diff --git a/spec/unit/ssl/certificate_revocation_list.rb b/spec/unit/ssl/certificate_revocation_list.rb new file mode 100755 index 000000000..75044ff56 --- /dev/null +++ b/spec/unit/ssl/certificate_revocation_list.rb @@ -0,0 +1,167 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/ssl/certificate_revocation_list' + +describe Puppet::SSL::CertificateRevocationList do + before do + @cert = stub 'cert', :subject => "mysubject" + + @key = stub 'key' + + @class = Puppet::SSL::CertificateRevocationList + end + + describe "when an instance" do + before do + @class.any_instance.stubs(:read_or_generate) + + @crl = @class.new("myname", @cert, @key) + end + + it "should have a name attribute" do + @crl.name.should == "myname" + end + + it "should have a content attribute" do + @crl.should respond_to(:content) + end + + it "should be able to read the crl from disk" do + path = "/my/path" + File.expects(:read).with(path).returns("my crl") + crl = mock 'crl' + OpenSSL::X509::CRL.expects(:new).with("my crl").returns(crl) + @crl.read(path).should equal(crl) + @crl.content.should equal(crl) + end + + it "should return an empty string when converted to a string with no crl" do + @crl.to_s.should == "" + end + + it "should convert the crl to pem format when converted to a string" do + crl = mock 'crl', :to_pem => "pem" + @crl.content = crl + @crl.to_s.should == "pem" + end + + it "should have a :to_text method that it delegates to the actual crl" do + real_crl = mock 'crl' + real_crl.expects(:to_text).returns "crltext" + @crl.content = real_crl + @crl.to_text.should == "crltext" + end + end + + describe "when initializing" do + it "should require the CA cert and key" do + lambda { @class.new("myname") }.should raise_error(ArgumentError) + end + + it "should fail if :cacrl is set to false" do + Puppet.settings.expects(:value).with(:cacrl).returns false + lambda { @class.new("myname", @cert, @key) }.should raise_error(Puppet::Error) + end + + it "should fail if :cacrl is set to the string 'false'" do + Puppet.settings.expects(:value).with(:cacrl).returns "false" + lambda { @class.new("myname", @cert, @key) }.should raise_error(Puppet::Error) + end + + it "should read the CRL from disk" do + Puppet.settings.stubs(:value).with(:cacrl).returns "/path/to/crl" + @class.any_instance.expects(:read).with("/path/to/crl").returns("my key") + + @class.new("myname", @cert, @key) + end + + describe "and no CRL exists on disk" do + before do + @class.any_instance.stubs(:read).returns(false) + @class.any_instance.stubs(:generate) + @class.any_instance.stubs(:save) + end + + it "should generate a new CRL" do + @class.any_instance.expects(:generate).with(@cert, @key) + + @class.new("myname", @cert, @key) + end + + it "should save the CRL" do + @class.any_instance.expects(:save).with(@key) + + @class.new("myname", @cert, @key) + end + end + end + + describe "when generating the crl" do + before do + @real_crl = mock 'crl' + @real_crl.stub_everything + + OpenSSL::X509::CRL.stubs(:new).returns(@real_crl) + + @class.any_instance.stubs(:read_or_generate) + + @crl = @class.new("myname", @cert, @key) + end + + it "should set its issuer to the subject of the passed certificate" do + @real_crl.expects(:issuer=).with(@cert.subject) + + @crl.generate(@cert, @key) + end + + it "should set its version to 1" do + @real_crl.expects(:version=).with(1) + + @crl.generate(@cert, @key) + end + + it "should create an instance of OpenSSL::X509::CRL" do + OpenSSL::X509::CRL.expects(:new).returns(@real_crl) + + @crl.generate(@cert, @key) + end + + it "should set the content to the generated crl" do + @crl.generate(@cert, @key) + @crl.content.should equal(@real_crl) + end + + it "should return the generated crl" do + @crl.generate(@cert, @key).should equal(@real_crl) + end + + it "should return the crl in pem format" do + @crl.generate(@cert, @key) + @crl.content.expects(:to_pem).returns "my normal crl" + @crl.to_s.should == "my normal crl" + end + end + + describe "when saving the CRL" do + before do + @real_crl = mock 'crl' + @real_crl.stub_everything + + @class.any_instance.stubs(:read_or_generate) + + @crl = @class.new("myname", @cert, @key) + @crl.generate(@cert, @key) + end + + it "should use the Settings#write method to write the file" do + fh = mock 'filehandle' + Puppet.settings.expects(:write).with(:cacrl).yields fh + + fh.expects :print + + @crl.save(@key) + end + end +end |