1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
|
require 'spec_helper'
require 'puppet/ssl/certificate_revocation_list'
describe Puppet::SSL::CertificateRevocationList do
before do
ca = Puppet::SSL::CertificateAuthority.new
ca.generate_ca_certificate
@cert = ca.host.certificate.content
@key = ca.host.key.content
@class = Puppet::SSL::CertificateRevocationList
end
def expects_time_close_to_now(time)
expect(time.to_i).to be_within(5*60).of(Time.now.to_i)
end
def expects_time_close_to_five_years(time)
future = Time.now + Puppet::SSL::CertificateRevocationList::FIVE_YEARS
expect(time.to_i).to be_within(5*60).of(future.to_i)
end
def expects_crlnumber_extension(crl, value)
crlNumber = crl.content.extensions.find { |ext| ext.oid == "crlNumber" }
expect(crlNumber.value).to eq(value.to_s)
expect(crlNumber).to_not be_critical
end
def expects_authkeyid_extension(crl, cert)
subjectKeyId = cert.extensions.find { |ext| ext.oid == 'subjectKeyIdentifier' }.value
authKeyId = crl.content.extensions.find { |ext| ext.oid == "authorityKeyIdentifier" }
expect(authKeyId.value.chomp).to eq("keyid:#{subjectKeyId}")
expect(authKeyId).to_not be_critical
end
def expects_crlreason_extension(crl, reason)
revoke = crl.content.revoked.first
crlNumber = crl.content.extensions.find { |ext| ext.oid == "crlNumber" }
expect(revoke.serial.to_s).to eq(crlNumber.value)
crlReason = revoke.extensions.find { |ext| ext.oid = 'CRLReason' }
expect(crlReason.value).to eq(reason)
expect(crlReason).to_not be_critical
end
it "should only support the text format" do
expect(@class.supported_formats).to eq([:s])
end
describe "when converting from a string" do
it "deserializes a CRL" do
crl = @class.new('foo')
crl.generate(@cert, @key)
new_crl = @class.from_s(crl.to_s)
expect(new_crl.content.to_text).to eq(crl.content.to_text)
end
end
describe "when an instance" do
before do
@crl = @class.new("whatever")
end
it "should always use 'crl' for its name" do
expect(@crl.name).to eq("crl")
end
it "should have a content attribute" do
expect(@crl).to respond_to(:content)
end
end
describe "when generating the crl" do
before do
@crl = @class.new("crl")
end
it "should set its issuer to the subject of the passed certificate" do
expect(@crl.generate(@cert, @key).issuer.to_s).to eq(@cert.subject.to_s)
end
it "should set its version to 1" do
expect(@crl.generate(@cert, @key).version).to eq(1)
end
it "should create an instance of OpenSSL::X509::CRL" do
expect(@crl.generate(@cert, @key)).to be_an_instance_of(OpenSSL::X509::CRL)
end
it "should add an extension for the CRL number" do
@crl.generate(@cert, @key)
expects_crlnumber_extension(@crl, 0)
end
it "should add an extension for the authority key identifier" do
@crl.generate(@cert, @key)
expects_authkeyid_extension(@crl, @cert)
end
it "returns the last update time in UTC" do
# https://tools.ietf.org/html/rfc5280#section-5.1.2.4
thisUpdate = @crl.generate(@cert, @key).last_update
expect(thisUpdate).to be_utc
expects_time_close_to_now(thisUpdate)
end
it "returns the next update time in UTC 5 years from now" do
# https://tools.ietf.org/html/rfc5280#section-5.1.2.5
nextUpdate = @crl.generate(@cert, @key).next_update
expect(nextUpdate).to be_utc
expects_time_close_to_five_years(nextUpdate)
end
it "should verify using the CA public_key" do
expect(@crl.generate(@cert, @key).verify(@key.public_key)).to be_truthy
end
it "should set the content to the generated crl" do
# this test shouldn't be needed since we test the return of generate() which should be the content field
@crl.generate(@cert, @key)
expect(@crl.content).to be_an_instance_of(OpenSSL::X509::CRL)
end
end
# This test suite isn't exactly complete, because the
# SSL stuff is very complicated. It just hits the high points.
describe "when revoking a certificate" do
before do
@crl = @class.new("crl")
@crl.generate(@cert, @key)
allow(Puppet::SSL::CertificateRevocationList.indirection).to receive(:save)
end
it "should require a serial number and the CA's private key" do
expect { @crl.revoke }.to raise_error(ArgumentError)
end
it "should mark the CRL as updated at a time that makes it valid now" do
@crl.revoke(1, @key)
expects_time_close_to_now(@crl.content.last_update)
end
it "should mark the CRL valid for five years" do
@crl.revoke(1, @key)
expects_time_close_to_five_years(@crl.content.next_update)
end
it "should sign the CRL with the CA's private key and a digest instance" do
digest = Puppet::SSL::CertificateSigner.new.digest
expect(@crl.content).to receive(:sign).with(@key, be_a(digest))
@crl.revoke(1, @key)
end
it "should save the CRL" do
expect(Puppet::SSL::CertificateRevocationList.indirection).to receive(:save).with(@crl, any_args)
@crl.revoke(1, @key)
end
it "adds the crlNumber extension containing the serial number" do
serial = 1
@crl.revoke(serial, @key)
expects_crlnumber_extension(@crl, serial)
end
it "adds the CA cert's subjectKeyId as the authorityKeyIdentifier to the CRL" do
@crl.revoke(1, @key)
expects_authkeyid_extension(@crl, @cert)
end
it "adds a non-critical CRL reason specifying key compromise by default" do
# https://tools.ietf.org/html/rfc5280#section-5.3.1
serial = 1
@crl.revoke(serial, @key)
expects_crlreason_extension(@crl, 'Key Compromise')
end
it "allows alternate reasons to be specified" do
serial = 1
@crl.revoke(serial, @key, OpenSSL::OCSP::REVOKED_STATUS_CACOMPROMISE)
expects_crlreason_extension(@crl, 'CA Compromise')
end
end
end
|