summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLuke Kanies <luke@madstop.com>2008-04-02 10:52:49 -0500
committerLuke Kanies <luke@madstop.com>2008-04-15 21:34:06 -0500
commit546ac97398caa1e9defb34df9567d798e4959020 (patch)
tree1317d56e127348e20270acac377cb8afbd13aa70
parentc98ad25403dbb27289048513af48d0b3e1723b18 (diff)
downloadpuppet-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.rb72
-rwxr-xr-xspec/unit/ssl/certificate_revocation_list.rb167
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