r509 0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +447 -0
- data/Rakefile +38 -0
- data/bin/r509 +96 -0
- data/bin/r509-parse +35 -0
- data/doc/R509.html +154 -0
- data/doc/R509/Cert.html +3954 -0
- data/doc/R509/Cert/Extensions.html +360 -0
- data/doc/R509/Cert/Extensions/AuthorityInfoAccess.html +391 -0
- data/doc/R509/Cert/Extensions/AuthorityKeyIdentifier.html +148 -0
- data/doc/R509/Cert/Extensions/BasicConstraints.html +482 -0
- data/doc/R509/Cert/Extensions/CrlDistributionPoints.html +316 -0
- data/doc/R509/Cert/Extensions/ExtendedKeyUsage.html +780 -0
- data/doc/R509/Cert/Extensions/KeyUsage.html +1230 -0
- data/doc/R509/Cert/Extensions/SubjectAlternativeName.html +467 -0
- data/doc/R509/Cert/Extensions/SubjectKeyIdentifier.html +216 -0
- data/doc/R509/CertificateAuthority.html +126 -0
- data/doc/R509/CertificateAuthority/Signer.html +855 -0
- data/doc/R509/Config.html +127 -0
- data/doc/R509/Config/CaConfig.html +2144 -0
- data/doc/R509/Config/CaConfigPool.html +599 -0
- data/doc/R509/Config/CaProfile.html +656 -0
- data/doc/R509/Config/SubjectItemPolicy.html +578 -0
- data/doc/R509/Crl.html +126 -0
- data/doc/R509/Crl/Administrator.html +2077 -0
- data/doc/R509/Crl/Parser.html +1224 -0
- data/doc/R509/Csr.html +2248 -0
- data/doc/R509/IOHelpers.html +564 -0
- data/doc/R509/MessageDigest.html +396 -0
- data/doc/R509/NameSanitizer.html +319 -0
- data/doc/R509/Ocsp.html +128 -0
- data/doc/R509/Ocsp/Request.html +126 -0
- data/doc/R509/Ocsp/Request/Nonce.html +160 -0
- data/doc/R509/Ocsp/Response.html +837 -0
- data/doc/R509/OidMapper.html +393 -0
- data/doc/R509/PrivateKey.html +1647 -0
- data/doc/R509/R509Error.html +134 -0
- data/doc/R509/Spki.html +1424 -0
- data/doc/R509/Subject.html +836 -0
- data/doc/R509/Validity.html +160 -0
- data/doc/R509/Validity/Checker.html +320 -0
- data/doc/R509/Validity/DefaultChecker.html +283 -0
- data/doc/R509/Validity/DefaultWriter.html +330 -0
- data/doc/R509/Validity/Status.html +561 -0
- data/doc/R509/Validity/Writer.html +394 -0
- data/doc/_index.html +501 -0
- data/doc/class_list.html +53 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +57 -0
- data/doc/css/style.css +328 -0
- data/doc/file.README.html +534 -0
- data/doc/file.r509.html +149 -0
- data/doc/file_list.html +58 -0
- data/doc/frames.html +28 -0
- data/doc/index.html +534 -0
- data/doc/js/app.js +208 -0
- data/doc/js/full_list.js +173 -0
- data/doc/js/jquery.js +4 -0
- data/doc/methods_list.html +1932 -0
- data/doc/top-level-namespace.html +112 -0
- data/lib/r509.rb +22 -0
- data/lib/r509/cert.rb +414 -0
- data/lib/r509/cert/extensions.rb +309 -0
- data/lib/r509/certificateauthority.rb +290 -0
- data/lib/r509/config.rb +407 -0
- data/lib/r509/crl.rb +379 -0
- data/lib/r509/csr.rb +324 -0
- data/lib/r509/exceptions.rb +5 -0
- data/lib/r509/io_helpers.rb +52 -0
- data/lib/r509/messagedigest.rb +49 -0
- data/lib/r509/ocsp.rb +85 -0
- data/lib/r509/oidmapper.rb +32 -0
- data/lib/r509/privatekey.rb +185 -0
- data/lib/r509/spki.rb +112 -0
- data/lib/r509/subject.rb +133 -0
- data/lib/r509/validity.rb +92 -0
- data/lib/r509/version.rb +4 -0
- data/r509.yaml +73 -0
- data/spec/cert/extensions_spec.rb +632 -0
- data/spec/cert_spec.rb +321 -0
- data/spec/certificate_authority_spec.rb +260 -0
- data/spec/config_spec.rb +349 -0
- data/spec/crl_spec.rb +215 -0
- data/spec/csr_spec.rb +302 -0
- data/spec/fixtures.rb +233 -0
- data/spec/fixtures/cert1.der +0 -0
- data/spec/fixtures/cert1.pem +24 -0
- data/spec/fixtures/cert1_public_key_modulus.txt +1 -0
- data/spec/fixtures/cert3.p12 +0 -0
- data/spec/fixtures/cert3.pem +28 -0
- data/spec/fixtures/cert3_key.pem +27 -0
- data/spec/fixtures/cert3_key_des3.pem +30 -0
- data/spec/fixtures/cert4.pem +14 -0
- data/spec/fixtures/cert5.pem +30 -0
- data/spec/fixtures/cert6.pem +26 -0
- data/spec/fixtures/cert_expired.pem +26 -0
- data/spec/fixtures/cert_not_yet_valid.pem +26 -0
- data/spec/fixtures/cert_san.pem +27 -0
- data/spec/fixtures/cert_san2.pem +22 -0
- data/spec/fixtures/config_pool_test_minimal.yaml +15 -0
- data/spec/fixtures/config_test.yaml +41 -0
- data/spec/fixtures/config_test_engine_key.yaml +7 -0
- data/spec/fixtures/config_test_engine_no_key_name.yaml +6 -0
- data/spec/fixtures/config_test_minimal.yaml +7 -0
- data/spec/fixtures/config_test_password.yaml +7 -0
- data/spec/fixtures/config_test_various.yaml +100 -0
- data/spec/fixtures/crl_list_file.txt +1 -0
- data/spec/fixtures/crl_with_reason.pem +17 -0
- data/spec/fixtures/csr1.der +0 -0
- data/spec/fixtures/csr1.pem +17 -0
- data/spec/fixtures/csr1_key.der +0 -0
- data/spec/fixtures/csr1_key.pem +27 -0
- data/spec/fixtures/csr1_key_encrypted_des3.pem +30 -0
- data/spec/fixtures/csr1_newlines.pem +32 -0
- data/spec/fixtures/csr1_no_begin_end.pem +15 -0
- data/spec/fixtures/csr1_public_key_modulus.txt +1 -0
- data/spec/fixtures/csr2.pem +15 -0
- data/spec/fixtures/csr2_key.pem +27 -0
- data/spec/fixtures/csr3.pem +16 -0
- data/spec/fixtures/csr4.pem +25 -0
- data/spec/fixtures/csr_dsa.pem +15 -0
- data/spec/fixtures/csr_invalid_signature.pem +13 -0
- data/spec/fixtures/dsa_key.pem +20 -0
- data/spec/fixtures/key4.pem +27 -0
- data/spec/fixtures/key4_encrypted_des3.pem +30 -0
- data/spec/fixtures/missing_key_identifier_ca.cer +21 -0
- data/spec/fixtures/missing_key_identifier_ca.key +27 -0
- data/spec/fixtures/ocsptest.r509.local.pem +27 -0
- data/spec/fixtures/ocsptest.r509.local_ocsp_request.der +0 -0
- data/spec/fixtures/ocsptest2.r509.local.pem +27 -0
- data/spec/fixtures/second_ca.cer +26 -0
- data/spec/fixtures/second_ca.key +27 -0
- data/spec/fixtures/spkac.der +0 -0
- data/spec/fixtures/spkac.txt +1 -0
- data/spec/fixtures/spkac_dsa.txt +1 -0
- data/spec/fixtures/stca.pem +22 -0
- data/spec/fixtures/stca_ocsp_request.der +0 -0
- data/spec/fixtures/stca_ocsp_response.der +0 -0
- data/spec/fixtures/test1.csr +17 -0
- data/spec/fixtures/test_ca.cer +22 -0
- data/spec/fixtures/test_ca.key +28 -0
- data/spec/fixtures/test_ca.p12 +0 -0
- data/spec/fixtures/test_ca_des3.key +30 -0
- data/spec/fixtures/test_ca_ocsp.cer +26 -0
- data/spec/fixtures/test_ca_ocsp.key +27 -0
- data/spec/fixtures/test_ca_ocsp.p12 +0 -0
- data/spec/fixtures/test_ca_ocsp_chain.txt +48 -0
- data/spec/fixtures/test_ca_ocsp_response.der +0 -0
- data/spec/fixtures/test_ca_subroot.cer +26 -0
- data/spec/fixtures/test_ca_subroot.key +27 -0
- data/spec/fixtures/test_ca_subroot_ocsp.cer +25 -0
- data/spec/fixtures/test_ca_subroot_ocsp.key +27 -0
- data/spec/fixtures/test_ca_subroot_ocsp_response.der +0 -0
- data/spec/fixtures/unknown_oid.csr +17 -0
- data/spec/message_digest_spec.rb +89 -0
- data/spec/ocsp_spec.rb +111 -0
- data/spec/oid_mapper_spec.rb +31 -0
- data/spec/privatekey_spec.rb +198 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/spki_spec.rb +157 -0
- data/spec/subject_spec.rb +203 -0
- data/spec/validity_spec.rb +98 -0
- metadata +257 -0
data/spec/config_spec.rb
ADDED
@@ -0,0 +1,349 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'r509/config'
|
3
|
+
require 'r509/exceptions'
|
4
|
+
|
5
|
+
describe R509::Config::CaConfigPool do
|
6
|
+
context "defined manually" do
|
7
|
+
it "has no configs" do
|
8
|
+
pool = R509::Config::CaConfigPool.new({})
|
9
|
+
|
10
|
+
pool["first"].should == nil
|
11
|
+
end
|
12
|
+
|
13
|
+
it "has one config" do
|
14
|
+
config = R509::Config::CaConfig.new(
|
15
|
+
:ca_cert => TestFixtures.test_ca_cert,
|
16
|
+
:profiles => { "first_profile" => R509::Config::CaProfile.new }
|
17
|
+
)
|
18
|
+
|
19
|
+
pool = R509::Config::CaConfigPool.new({
|
20
|
+
"first" => config
|
21
|
+
})
|
22
|
+
|
23
|
+
pool["first"].should == config
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "all configs" do
|
28
|
+
it "no configs" do
|
29
|
+
pool = R509::Config::CaConfigPool.new({})
|
30
|
+
pool.all.should == []
|
31
|
+
end
|
32
|
+
|
33
|
+
it "one config" do
|
34
|
+
config = R509::Config::CaConfig.new(
|
35
|
+
:ca_cert => TestFixtures.test_ca_cert,
|
36
|
+
:profiles => { "first_profile" => R509::Config::CaProfile.new }
|
37
|
+
)
|
38
|
+
|
39
|
+
pool = R509::Config::CaConfigPool.new({
|
40
|
+
"first" => config
|
41
|
+
})
|
42
|
+
|
43
|
+
pool.all.should == [config]
|
44
|
+
end
|
45
|
+
|
46
|
+
it "two configs" do
|
47
|
+
config1 = R509::Config::CaConfig.new(
|
48
|
+
:ca_cert => TestFixtures.test_ca_cert,
|
49
|
+
:profiles => { "first_profile" => R509::Config::CaProfile.new }
|
50
|
+
)
|
51
|
+
config2 = R509::Config::CaConfig.new(
|
52
|
+
:ca_cert => TestFixtures.test_ca_cert,
|
53
|
+
:profiles => { "first_profile" => R509::Config::CaProfile.new }
|
54
|
+
)
|
55
|
+
|
56
|
+
pool = R509::Config::CaConfigPool.new({
|
57
|
+
"first" => config1,
|
58
|
+
"second" => config2
|
59
|
+
})
|
60
|
+
|
61
|
+
pool.all.size.should == 2
|
62
|
+
pool.all.include?(config1).should == true
|
63
|
+
pool.all.include?(config2).should == true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context "loaded from YAML" do
|
68
|
+
it "should load two configs" do
|
69
|
+
pool = R509::Config::CaConfigPool.from_yaml("certificate_authorities", File.read("#{File.dirname(__FILE__)}/fixtures/config_pool_test_minimal.yaml"), {:ca_root_path => "#{File.dirname(__FILE__)}/fixtures"})
|
70
|
+
|
71
|
+
pool.names.should include("test_ca", "second_ca")
|
72
|
+
|
73
|
+
pool["test_ca"].should_not == nil
|
74
|
+
pool["test_ca"].num_profiles.should == 0
|
75
|
+
pool["second_ca"].should_not == nil
|
76
|
+
pool["second_ca"].num_profiles.should == 0
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe R509::Config::CaConfig do
|
82
|
+
before :each do
|
83
|
+
@config = R509::Config::CaConfig.new(
|
84
|
+
:ca_cert => TestFixtures.test_ca_cert
|
85
|
+
)
|
86
|
+
end
|
87
|
+
|
88
|
+
subject {@config}
|
89
|
+
|
90
|
+
its(:message_digest) {should == "SHA1"}
|
91
|
+
its(:crl_validity_hours) {should == 168}
|
92
|
+
its(:ocsp_validity_hours) {should == 168}
|
93
|
+
its(:ocsp_start_skew_seconds) {should == 3600}
|
94
|
+
its(:cdp_location) {should be_nil}
|
95
|
+
its(:ocsp_location) {should be_nil}
|
96
|
+
its(:num_profiles) {should == 0}
|
97
|
+
|
98
|
+
it "should have the proper CA cert" do
|
99
|
+
@config.ca_cert.to_pem.should == TestFixtures.test_ca_cert.to_pem
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should have the proper CA key" do
|
103
|
+
@config.ca_cert.key.to_pem.should == TestFixtures.test_ca_cert.key.to_pem
|
104
|
+
end
|
105
|
+
|
106
|
+
it "raises an error if you don't pass :ca_cert" do
|
107
|
+
expect { R509::Config::CaConfig.new(:crl_validity_hours => 2) }.to raise_error ArgumentError, 'Config object requires that you pass :ca_cert'
|
108
|
+
end
|
109
|
+
it "raises an error if :ca_cert is not of type R509::Cert" do
|
110
|
+
expect { R509::Config::CaConfig.new(:ca_cert => 'not a cert, and not right type') }.to raise_error ArgumentError, ':ca_cert must be of type R509::Cert'
|
111
|
+
end
|
112
|
+
it "loads the config even if :ca_cert does not contain a private key" do
|
113
|
+
config = R509::Config::CaConfig.new( :ca_cert => R509::Cert.new( :cert => TestFixtures::TEST_CA_CERT) )
|
114
|
+
config.ca_cert.subject.to_s.should_not be_nil
|
115
|
+
end
|
116
|
+
it "raises an error if :ocsp_cert that is not R509::Cert" do
|
117
|
+
expect { R509::Config::CaConfig.new(:ca_cert => TestFixtures.test_ca_cert, :ocsp_cert => "not a cert") }.to raise_error ArgumentError, ':ocsp_cert, if provided, must be of type R509::Cert'
|
118
|
+
end
|
119
|
+
it "raises an error if :ocsp_cert does not contain a private key" do
|
120
|
+
expect { R509::Config::CaConfig.new( :ca_cert => TestFixtures.test_ca_cert, :ocsp_cert => R509::Cert.new( :cert => TestFixtures::TEST_CA_CERT) ) }.to raise_error ArgumentError, ':ocsp_cert must contain a private key, not just a certificate'
|
121
|
+
end
|
122
|
+
it "returns the correct cert object on #ocsp_cert if none is specified" do
|
123
|
+
@config.ocsp_cert.should == @config.ca_cert
|
124
|
+
end
|
125
|
+
it "returns the correct cert object on #ocsp_cert if an ocsp_cert was specified" do
|
126
|
+
ocsp_cert = R509::Cert.new(
|
127
|
+
:cert => TestFixtures::TEST_CA_OCSP_CERT,
|
128
|
+
:key => TestFixtures::TEST_CA_OCSP_KEY
|
129
|
+
)
|
130
|
+
config = R509::Config::CaConfig.new(
|
131
|
+
:ca_cert => TestFixtures.test_ca_cert,
|
132
|
+
:ocsp_cert => ocsp_cert
|
133
|
+
)
|
134
|
+
|
135
|
+
config.ocsp_cert.should == ocsp_cert
|
136
|
+
end
|
137
|
+
it "fails to specify a non-Config::CaProfile as the profile" do
|
138
|
+
config = R509::Config::CaConfig.new(
|
139
|
+
:ca_cert => TestFixtures.test_ca_cert
|
140
|
+
)
|
141
|
+
|
142
|
+
expect{ config.set_profile("bogus", "not a Config::CaProfile")}.to raise_error TypeError
|
143
|
+
end
|
144
|
+
|
145
|
+
it "shouldn't let you specify a profile that's not a Config::CaProfile, on instantiation" do
|
146
|
+
expect{ R509::Config::CaConfig.new(
|
147
|
+
:ca_cert => TestFixtures.test_ca_cert,
|
148
|
+
:profiles => { "first_profile" => "not a Config::CaProfile" }
|
149
|
+
) }.to raise_error TypeError
|
150
|
+
end
|
151
|
+
|
152
|
+
it "can specify a single profile" do
|
153
|
+
first_profile = R509::Config::CaProfile.new
|
154
|
+
|
155
|
+
config = R509::Config::CaConfig.new(
|
156
|
+
:ca_cert => TestFixtures.test_ca_cert,
|
157
|
+
:profiles => { "first_profile" => first_profile }
|
158
|
+
)
|
159
|
+
|
160
|
+
config.profile("first_profile").should == first_profile
|
161
|
+
end
|
162
|
+
|
163
|
+
it "raises an error if you specify an invalid profile" do
|
164
|
+
first_profile = R509::Config::CaProfile.new
|
165
|
+
|
166
|
+
config = R509::Config::CaConfig.new(
|
167
|
+
:ca_cert => TestFixtures.test_ca_cert,
|
168
|
+
:profiles => { "first_profile" => first_profile }
|
169
|
+
)
|
170
|
+
|
171
|
+
expect { config.profile("non-existent-profile") }.to raise_error(R509::R509Error, "unknown profile 'non-existent-profile'")
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should load YAML" do
|
175
|
+
config = R509::Config::CaConfig.from_yaml("test_ca", File.read("#{File.dirname(__FILE__)}/fixtures/config_test.yaml"), {:ca_root_path => "#{File.dirname(__FILE__)}/fixtures"})
|
176
|
+
config.crl_validity_hours.should == 72
|
177
|
+
config.ocsp_validity_hours.should == 96
|
178
|
+
config.message_digest.should == "SHA1"
|
179
|
+
config.num_profiles.should == 3
|
180
|
+
end
|
181
|
+
it "loads OCSP cert/key from yaml" do
|
182
|
+
config = R509::Config::CaConfig.from_yaml("ocsp_delegate_ca", File.read("#{File.dirname(__FILE__)}/fixtures/config_test_various.yaml"), {:ca_root_path => "#{File.dirname(__FILE__)}/fixtures"})
|
183
|
+
config.ocsp_cert.has_private_key?.should == true
|
184
|
+
config.ocsp_cert.subject.to_s.should == "/C=US/ST=Illinois/L=Chicago/O=r509 LLC/CN=r509 OCSP Signer"
|
185
|
+
end
|
186
|
+
it "loads OCSP pkcs12 from yaml" do
|
187
|
+
config = R509::Config::CaConfig.from_yaml("ocsp_pkcs12_ca", File.read("#{File.dirname(__FILE__)}/fixtures/config_test_various.yaml"), {:ca_root_path => "#{File.dirname(__FILE__)}/fixtures"})
|
188
|
+
config.ocsp_cert.has_private_key?.should == true
|
189
|
+
config.ocsp_cert.subject.to_s.should == "/C=US/ST=Illinois/L=Chicago/O=r509 LLC/CN=r509 OCSP Signer"
|
190
|
+
end
|
191
|
+
it "loads OCSP cert/key in engine from yaml" do
|
192
|
+
#most of this code path is tested by loading ca_cert engine.
|
193
|
+
#look there for the extensive doubling
|
194
|
+
expect { R509::Config::CaConfig.from_yaml("ocsp_engine_ca", File.read("#{File.dirname(__FILE__)}/fixtures/config_test_various.yaml"), {:ca_root_path => "#{File.dirname(__FILE__)}/fixtures"}) }.to raise_error(R509::R509Error,"You must supply a key_name with an engine")
|
195
|
+
end
|
196
|
+
it "loads OCSP chain from yaml" do
|
197
|
+
config = R509::Config::CaConfig.from_yaml("ocsp_chain_ca", File.read("#{File.dirname(__FILE__)}/fixtures/config_test_various.yaml"), {:ca_root_path => "#{File.dirname(__FILE__)}/fixtures"})
|
198
|
+
config.ocsp_chain.size.should == 2
|
199
|
+
config.ocsp_chain[0].kind_of?(OpenSSL::X509::Certificate).should == true
|
200
|
+
config.ocsp_chain[1].kind_of?(OpenSSL::X509::Certificate).should == true
|
201
|
+
end
|
202
|
+
it "should load subject_item_policy from yaml (if present)" do
|
203
|
+
config = R509::Config::CaConfig.from_yaml("test_ca", File.read("#{File.dirname(__FILE__)}/fixtures/config_test.yaml"), {:ca_root_path => "#{File.dirname(__FILE__)}/fixtures"})
|
204
|
+
config.profile("server").subject_item_policy.should be_nil
|
205
|
+
config.profile("server_with_subject_item_policy").subject_item_policy.optional.should include("O","OU")
|
206
|
+
config.profile("server_with_subject_item_policy").subject_item_policy.required.should include("CN","ST","C")
|
207
|
+
end
|
208
|
+
|
209
|
+
it "should load YAML which only has a CA Cert and Key defined" do
|
210
|
+
config = R509::Config::CaConfig.from_yaml("test_ca", File.read("#{File.dirname(__FILE__)}/fixtures/config_test_minimal.yaml"), {:ca_root_path => "#{File.dirname(__FILE__)}/fixtures"})
|
211
|
+
config.num_profiles.should == 0
|
212
|
+
end
|
213
|
+
|
214
|
+
it "should load YAML which has CA cert and key with password" do
|
215
|
+
expect { R509::Config::CaConfig.from_yaml("password_ca", File.read("#{File.dirname(__FILE__)}/fixtures/config_test_password.yaml"), {:ca_root_path => "#{File.dirname(__FILE__)}/fixtures"}) }.to_not raise_error
|
216
|
+
end
|
217
|
+
|
218
|
+
it "should load YAML which has a PKCS12 with password" do
|
219
|
+
expect { R509::Config::CaConfig.from_yaml("pkcs12_ca", File.read("#{File.dirname(__FILE__)}/fixtures/config_test_various.yaml"), {:ca_root_path => "#{File.dirname(__FILE__)}/fixtures"}) }.to_not raise_error
|
220
|
+
end
|
221
|
+
|
222
|
+
it "raises error on YAML with pkcs12 and key" do
|
223
|
+
expect { R509::Config::CaConfig.from_yaml("pkcs12_key_ca", File.read("#{File.dirname(__FILE__)}/fixtures/config_test_various.yaml"), {:ca_root_path => "#{File.dirname(__FILE__)}/fixtures"}) }.to raise_error(R509::R509Error, "You can't specify both pkcs12 and key")
|
224
|
+
end
|
225
|
+
|
226
|
+
it "raises error on YAML with pkcs12 and cert" do
|
227
|
+
expect { R509::Config::CaConfig.from_yaml("pkcs12_cert_ca", File.read("#{File.dirname(__FILE__)}/fixtures/config_test_various.yaml"), {:ca_root_path => "#{File.dirname(__FILE__)}/fixtures"}) }.to raise_error(R509::R509Error, "You can't specify both pkcs12 and cert")
|
228
|
+
end
|
229
|
+
|
230
|
+
it "raises error on YAML with pkcs12 and engine" do
|
231
|
+
expect { R509::Config::CaConfig.from_yaml("pkcs12_engine_ca", File.read("#{File.dirname(__FILE__)}/fixtures/config_test_various.yaml"), {:ca_root_path => "#{File.dirname(__FILE__)}/fixtures"}) }.to raise_error(R509::R509Error, "You can't specify both engine and pkcs12")
|
232
|
+
end
|
233
|
+
|
234
|
+
it "loads config with cert and no key (useful in certain cases)" do
|
235
|
+
config = R509::Config::CaConfig.from_yaml("cert_no_key_ca", File.read("#{File.dirname(__FILE__)}/fixtures/config_test_various.yaml"), {:ca_root_path => "#{File.dirname(__FILE__)}/fixtures"})
|
236
|
+
config.ca_cert.subject.to_s.should_not be_nil
|
237
|
+
end
|
238
|
+
|
239
|
+
it "should load YAML which has an engine" do
|
240
|
+
#i can test this, it's just gonna take a whole lot of floorin' it!
|
241
|
+
conf = double("conf")
|
242
|
+
engine = double("engine")
|
243
|
+
faux_key = double("faux_key")
|
244
|
+
|
245
|
+
public_key = OpenSSL::PKey::RSA.new("-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqLfP8948QEZMMkZNHLDP\nOHZVrPdVvecD6bp8dz96LalMQiWjgMkJf7mPHXoNMQ5rIpntiUOmhupu+sty30+C\ndbZZIbZohioTSYq9ZIC/LC9ME12F78GRMKhBGA+ZiouzWvXqOEdMnanfSgKrlSIS\nssF71dfmOEQ08fn9Vl5jAgWmGe+v615iHqBNGr64kYooTrZYLaPlTScO1UZ76vnB\nHfNQU+tsEZNXxtZXKQqkxHxLShCOj6qmYRNn/upTZoWWd04+zXjYGEC3eKvi9ctN\n9FY+KJ6QCCa8H0Kt3cU5qyw6pzdljhbG6NKhod7OMqlGjmHdsCAYAqe3xH+V/8oe\ndwIDAQAB\n-----END PUBLIC KEY-----\n")
|
246
|
+
|
247
|
+
conf.should_receive(:kind_of?).with(Hash).and_return(true)
|
248
|
+
conf.should_receive(:[]).with("ca_cert").and_return(
|
249
|
+
"cert" => "#{File.dirname(__FILE__)}/fixtures/test_ca.cer",
|
250
|
+
"engine" => engine,
|
251
|
+
"key_name" => "r509_key"
|
252
|
+
)
|
253
|
+
conf.should_receive(:[]).at_least(1).times.and_return(nil)
|
254
|
+
conf.should_receive(:has_key?).at_least(1).times.and_return(false)
|
255
|
+
|
256
|
+
engine.should_receive(:respond_to?).with(:load_private_key).and_return(true)
|
257
|
+
engine.should_receive(:kind_of?).with(OpenSSL::Engine).and_return(true)
|
258
|
+
faux_key.should_receive(:public_key).and_return(public_key)
|
259
|
+
engine.should_receive(:load_private_key).with("r509_key").and_return(faux_key)
|
260
|
+
|
261
|
+
config = R509::Config::CaConfig.load_from_hash(conf)
|
262
|
+
end
|
263
|
+
|
264
|
+
it "should fail if YAML for ca_cert contains engine and key" do
|
265
|
+
expect { R509::Config::CaConfig.from_yaml("engine_and_key", File.read("#{File.dirname(__FILE__)}/fixtures/config_test_engine_key.yaml"), {:ca_root_path => "#{File.dirname(__FILE__)}/fixtures"}) }.to raise_error(R509::R509Error, "You can't specify both key and engine")
|
266
|
+
end
|
267
|
+
|
268
|
+
it "should fail if YAML for ca_cert contains engine but no key_name" do
|
269
|
+
expect { R509::Config::CaConfig.from_yaml("engine_no_key_name", File.read("#{File.dirname(__FILE__)}/fixtures/config_test_engine_no_key_name.yaml"), {:ca_root_path => "#{File.dirname(__FILE__)}/fixtures"}) }.to raise_error(R509::R509Error, 'You must supply a key_name with an engine')
|
270
|
+
end
|
271
|
+
|
272
|
+
it "should fail if YAML config is null" do
|
273
|
+
expect{ R509::Config::CaConfig.from_yaml("no_config_here", File.read("#{File.dirname(__FILE__)}/fixtures/config_test.yaml"), {:ca_root_path => "#{File.dirname(__FILE__)}/fixtures"}) }.to raise_error(ArgumentError)
|
274
|
+
end
|
275
|
+
|
276
|
+
it "should fail if YAML config isn't a hash" do
|
277
|
+
expect{ R509::Config::CaConfig.from_yaml("config_is_string", File.read("#{File.dirname(__FILE__)}/fixtures/config_test.yaml"), {:ca_root_path => "#{File.dirname(__FILE__)}/fixtures"}) }.to raise_error(ArgumentError)
|
278
|
+
end
|
279
|
+
|
280
|
+
it "should fail if YAML config doesn't give a root CA directory that's a directory" do
|
281
|
+
expect{ R509::Config::CaConfig.from_yaml("test_ca", File.read("#{File.dirname(__FILE__)}/fixtures/config_test.yaml"), {:ca_root_path => "#{File.dirname(__FILE__)}/fixtures/no_directory_here"}) }.to raise_error(R509::R509Error)
|
282
|
+
end
|
283
|
+
|
284
|
+
it "should load YAML from filename" do
|
285
|
+
config = R509::Config::CaConfig.load_yaml("test_ca", "#{File.dirname(__FILE__)}/fixtures/config_test.yaml", {:ca_root_path => "#{File.dirname(__FILE__)}/fixtures"})
|
286
|
+
config.crl_validity_hours.should == 72
|
287
|
+
config.ocsp_validity_hours.should == 96
|
288
|
+
config.message_digest.should == "SHA1"
|
289
|
+
end
|
290
|
+
|
291
|
+
it "can specify crl_number_file" do
|
292
|
+
config = R509::Config::CaConfig.new(
|
293
|
+
:ca_cert => TestFixtures.test_ca_cert,
|
294
|
+
:crl_number_file => "crl_number_file.txt"
|
295
|
+
)
|
296
|
+
config.crl_number_file.should == 'crl_number_file.txt'
|
297
|
+
end
|
298
|
+
|
299
|
+
it "can specify crl_list_file" do
|
300
|
+
config = R509::Config::CaConfig.new(
|
301
|
+
:ca_cert => TestFixtures.test_ca_cert,
|
302
|
+
:crl_list_file => "crl_list_file.txt"
|
303
|
+
)
|
304
|
+
config.crl_list_file.should == 'crl_list_file.txt'
|
305
|
+
end
|
306
|
+
|
307
|
+
end
|
308
|
+
|
309
|
+
describe R509::Config::SubjectItemPolicy do
|
310
|
+
it "raises an error if you supply a non-hash" do
|
311
|
+
expect { R509::Config::SubjectItemPolicy.new('string') }.to raise_error(ArgumentError, "Must supply a hash in form 'shortname'=>'required/optional'")
|
312
|
+
end
|
313
|
+
it "raises an error if a required element is missing" do
|
314
|
+
subject_item_policy = R509::Config::SubjectItemPolicy.new("CN" => "required", "O" => "required", "OU" => "optional", "L" => "required")
|
315
|
+
subject = R509::Subject.new [["CN","langui.sh"],["OU","Org Unit"],["O","Org"]]
|
316
|
+
expect { subject_item_policy.validate_subject(subject) }.to raise_error(R509::R509Error, /This profile requires you supply/)
|
317
|
+
end
|
318
|
+
it "raises an error if your hash values are anything other than required or optional" do
|
319
|
+
expect { R509::Config::SubjectItemPolicy.new("CN" => "somethirdoption") }.to raise_error(ArgumentError, "Unknown subject item policy value. Allowed values are required and optional")
|
320
|
+
end
|
321
|
+
it "validates a subject with the same fields as the policy" do
|
322
|
+
subject_item_policy = R509::Config::SubjectItemPolicy.new("CN" => "required", "O" => "required", "OU" => "optional")
|
323
|
+
subject = R509::Subject.new [["CN","langui.sh"],["OU","Org Unit"],["O","Org"]]
|
324
|
+
validated_subject = subject_item_policy.validate_subject(subject)
|
325
|
+
validated_subject.to_s.should == subject.to_s
|
326
|
+
end
|
327
|
+
it "does not match if you get case of subject_item_policy element wrong" do
|
328
|
+
subject_item_policy = R509::Config::SubjectItemPolicy.new("cn" => "required")
|
329
|
+
subject = R509::Subject.new [["CN","langui.sh"]]
|
330
|
+
expect { subject_item_policy.validate_subject(subject) }.to raise_error(R509::R509Error, 'This profile requires you supply cn')
|
331
|
+
end
|
332
|
+
it "removes subject items that are not in the policy" do
|
333
|
+
subject_item_policy = R509::Config::SubjectItemPolicy.new("CN" => "required")
|
334
|
+
subject = R509::Subject.new [["CN","langui.sh"],["OU","Org Unit"],["O","Org"]]
|
335
|
+
validated_subject = subject_item_policy.validate_subject(subject)
|
336
|
+
validated_subject.to_s.should == "/CN=langui.sh"
|
337
|
+
end
|
338
|
+
it "does not reorder subject items as it validates" do
|
339
|
+
subject_item_policy = R509::Config::SubjectItemPolicy.new("CN" => "required", "O" => "required", "OU" => "optional", "L" => "required")
|
340
|
+
subject = R509::Subject.new [["L","Chicago"],["CN","langui.sh"],["OU","Org Unit"],["O","Org"]]
|
341
|
+
validated_subject = subject_item_policy.validate_subject(subject)
|
342
|
+
validated_subject.to_s.should == subject.to_s
|
343
|
+
end
|
344
|
+
it "loads all the required and optional elements" do
|
345
|
+
subject_item_policy = R509::Config::SubjectItemPolicy.new("CN" => "required", "O" => "required", "OU" => "optional", "L" => "required", "emailAddress" => "optional")
|
346
|
+
subject_item_policy.optional.should include("OU","emailAddress")
|
347
|
+
subject_item_policy.required.should include("CN","O","L")
|
348
|
+
end
|
349
|
+
end
|
data/spec/crl_spec.rb
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
describe R509::Crl::Parser do
|
5
|
+
before :each do
|
6
|
+
@crl_reason = TestFixtures::CRL_REASON
|
7
|
+
@crl = R509::Crl::Parser.new(@crl_reason)
|
8
|
+
@test_ca_cert = TestFixtures::TEST_CA_CERT
|
9
|
+
end
|
10
|
+
|
11
|
+
it "loads a crl with load_from_file" do
|
12
|
+
path = File.dirname(__FILE__) + '/fixtures/crl_with_reason.pem'
|
13
|
+
crl = R509::Crl::Parser.load_from_file path
|
14
|
+
crl.revoked[12345].should_not be_nil
|
15
|
+
end
|
16
|
+
|
17
|
+
it "returns issuer" do
|
18
|
+
@crl.issuer.to_s.should == "/C=US/ST=Illinois/L=Chicago/O=Ruby CA Project/CN=Test CA"
|
19
|
+
end
|
20
|
+
|
21
|
+
it "returns last_update" do
|
22
|
+
@crl.last_update.should == Time.at(1327446093)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "returns next_update" do
|
26
|
+
@crl.next_update.should == Time.at(1328054493)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "returns signature_algorithm" do
|
30
|
+
@crl.signature_algorithm.should == "sha1WithRSAEncryption"
|
31
|
+
end
|
32
|
+
|
33
|
+
it "verifies the CRL signature" do
|
34
|
+
cert = R509::Cert.new(:cert => @test_ca_cert)
|
35
|
+
@crl.verify(cert.public_key).should == true
|
36
|
+
end
|
37
|
+
|
38
|
+
it "checks if a serial is revoked?" do
|
39
|
+
@crl.revoked?(111111).should == false
|
40
|
+
@crl.revoked?(12345).should == true
|
41
|
+
end
|
42
|
+
|
43
|
+
it "returns a hash of all revoked certs" do
|
44
|
+
@crl.revoked[12345][:time].should == Time.at(1327449693)
|
45
|
+
@crl.revoked[12345][:reason].should == "Key Compromise"
|
46
|
+
@crl.revoked[123456][:time].should == Time.at(1327449693)
|
47
|
+
@crl.revoked[123456][:reason].should == "Unspecified"
|
48
|
+
@crl.revoked[1234567][:time].should == Time.at(1327449693)
|
49
|
+
@crl.revoked[1234567][:reason].should == "Unspecified"
|
50
|
+
@crl.revoked[12345678].should == nil
|
51
|
+
end
|
52
|
+
|
53
|
+
it "returns revocation information for a serial" do
|
54
|
+
@crl.revoked_cert(11111).should == nil
|
55
|
+
revoked_info = @crl.revoked_cert(12345)
|
56
|
+
revoked_info[:time].should == Time.at(1327449693)
|
57
|
+
revoked_info[:reason].should == "Key Compromise"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe R509::Crl::Administrator do
|
62
|
+
before :each do
|
63
|
+
@cert = TestFixtures::CERT
|
64
|
+
@csr = TestFixtures::CSR
|
65
|
+
@csr3 = TestFixtures::CSR3
|
66
|
+
@test_ca_config = TestFixtures.test_ca_no_profile_config
|
67
|
+
end
|
68
|
+
it "generates CRL with no entries in revocation list" do
|
69
|
+
crl = R509::Crl::Administrator.new(@test_ca_config)
|
70
|
+
crl.generate_crl
|
71
|
+
crl.to_pem.should match(/BEGIN X509 CRL/)
|
72
|
+
end
|
73
|
+
it "raises exception when no R509::Config::CaConfig object is passed to the constructor" do
|
74
|
+
expect { R509::Crl::Administrator.new(['random']) }.to raise_error(R509::R509Error)
|
75
|
+
end
|
76
|
+
it "can write the crl_number_file" do
|
77
|
+
crl = R509::Crl::Administrator.new(@test_ca_config)
|
78
|
+
crl.crl_number_file.string.should == "1"
|
79
|
+
crl.crl_number_file.reopen("")
|
80
|
+
crl.save_crl_number
|
81
|
+
crl.crl_number_file.string.should == "1"
|
82
|
+
end
|
83
|
+
it "adds a cert to the revocation list" do
|
84
|
+
crl = R509::Crl::Administrator.new(@test_ca_config)
|
85
|
+
crl.revoked?(383834832).should == false
|
86
|
+
crl.revoke_cert(383834832)
|
87
|
+
crl.revoked?(383834832).should == true
|
88
|
+
parsed_crl = OpenSSL::X509::CRL.new(crl.to_der)
|
89
|
+
parsed_crl.revoked[0].serial.should == 383834832
|
90
|
+
end
|
91
|
+
it "can revoke (with reason)" do
|
92
|
+
crl = R509::Crl::Administrator.new(@test_ca_config)
|
93
|
+
crl.revoked?(12345).should == false
|
94
|
+
crl.revoke_cert(12345, 1)
|
95
|
+
crl.revoked?(12345).should == true
|
96
|
+
crl.revoked_cert(12345)[:reason].should == 1
|
97
|
+
|
98
|
+
parsed_crl = OpenSSL::X509::CRL.new(crl.to_pem)
|
99
|
+
parsed_crl.revoked[0].serial.should == 12345
|
100
|
+
parsed_crl.revoked[0].extensions[0].oid.should == "CRLReason"
|
101
|
+
parsed_crl.revoked[0].extensions[0].value.should == "Key Compromise"
|
102
|
+
end
|
103
|
+
it "cannot revoke the same serial twice" do
|
104
|
+
crl = R509::Crl::Administrator.new(@test_ca_config)
|
105
|
+
crl.revoked?(12345).should == false
|
106
|
+
crl.revoke_cert(12345, 1)
|
107
|
+
crl.revoked?(12345).should == true
|
108
|
+
crl.revoked_cert(12345)[:reason].should == 1
|
109
|
+
expect { crl.revoke_cert(12345, 1) }.to raise_error(R509::R509Error, "Cannot revoke a previously revoked certificate")
|
110
|
+
crl.revoked?(12345).should == true
|
111
|
+
end
|
112
|
+
it "adds a cert to the revocation list with an invalid reason code" do
|
113
|
+
crl = R509::Crl::Administrator.new(@test_ca_config)
|
114
|
+
crl.revoke_cert(383834832,15)
|
115
|
+
crl.generate_crl.should match(/BEGIN X509 CRL/)
|
116
|
+
crl.revoked?(383834832).should == true
|
117
|
+
crl.revoked_cert(383834832)[:reason].should == 0
|
118
|
+
end
|
119
|
+
it "removes a cert from the revocation list" do
|
120
|
+
crl = R509::Crl::Administrator.new(@test_ca_config)
|
121
|
+
crl.revoke_cert(383834832)
|
122
|
+
crl.revoked?(383834832).should == true
|
123
|
+
parsed_crl = OpenSSL::X509::CRL.new(crl.to_pem)
|
124
|
+
parsed_crl.revoked[0].serial.should == 383834832
|
125
|
+
crl.unrevoke_cert(383834832)
|
126
|
+
crl.revoked?(383834832).should == false
|
127
|
+
parsed_crl = OpenSSL::X509::CRL.new(crl.to_pem)
|
128
|
+
parsed_crl.revoked.empty?.should == true
|
129
|
+
end
|
130
|
+
it "loads an existing revocation list file" do
|
131
|
+
config = R509::Config::CaConfig.new(
|
132
|
+
:ca_cert => TestFixtures.test_ca_cert,
|
133
|
+
:crl_list_file => TestFixtures::CRL_LIST_FILE
|
134
|
+
)
|
135
|
+
crl = R509::Crl::Administrator.new(config)
|
136
|
+
crl.revoked?(12345).should == true
|
137
|
+
crl.revoked_cert(12345)[:revoke_time].should == 1323983885
|
138
|
+
crl.revoked_cert(12345)[:reason].should == 0
|
139
|
+
|
140
|
+
end
|
141
|
+
it "when nil crl_list_file still call generate_crl" do
|
142
|
+
config = R509::Config::CaConfig.new(
|
143
|
+
:ca_cert => TestFixtures.test_ca_cert,
|
144
|
+
:crl_list_file => nil
|
145
|
+
)
|
146
|
+
crl = R509::Crl::Administrator.new(config)
|
147
|
+
crl.to_pem.should match(/BEGIN X509 CRL/)
|
148
|
+
end
|
149
|
+
it "sets validity via yaml" do
|
150
|
+
crl = R509::Crl::Administrator.new(@test_ca_config)
|
151
|
+
now = Time.at Time.now.to_i
|
152
|
+
crl.generate_crl
|
153
|
+
crl.next_update.should == (now+168*3600) #default 168 hours (7 days)
|
154
|
+
end
|
155
|
+
it "has a last_update time" do
|
156
|
+
crl = R509::Crl::Administrator.new(@test_ca_config)
|
157
|
+
now = Time.at Time.now.to_i
|
158
|
+
crl.generate_crl
|
159
|
+
crl.last_update.should == (now - @test_ca_config.crl_start_skew_seconds)
|
160
|
+
end
|
161
|
+
it "returns der" do
|
162
|
+
crl = R509::Crl::Administrator.new(@test_ca_config)
|
163
|
+
crl.generate_crl
|
164
|
+
parsed_crl = crl.to_crl
|
165
|
+
parsed_crl.issuer.to_s.should == '/C=US/ST=Illinois/L=Chicago/O=Ruby CA Project/CN=Test CA'
|
166
|
+
parsed_crl.issuer_cn.should == 'Test CA'
|
167
|
+
end
|
168
|
+
it "returns pem" do
|
169
|
+
crl = R509::Crl::Administrator.new(@test_ca_config)
|
170
|
+
crl.generate_crl
|
171
|
+
parsed_crl = crl.to_crl
|
172
|
+
parsed_crl.issuer.to_s.should == '/C=US/ST=Illinois/L=Chicago/O=Ruby CA Project/CN=Test CA'
|
173
|
+
parsed_crl.issuer_cn.should == 'Test CA'
|
174
|
+
end
|
175
|
+
it "writes to pem" do
|
176
|
+
crl = R509::Crl::Administrator.new(@test_ca_config)
|
177
|
+
crl.generate_crl
|
178
|
+
sio = StringIO.new
|
179
|
+
sio.set_encoding("BINARY") if sio.respond_to?(:set_encoding)
|
180
|
+
crl.write_pem(sio)
|
181
|
+
parsed_crl = R509::Crl::Parser.new(sio.string)
|
182
|
+
parsed_crl.issuer.to_s.should == '/C=US/ST=Illinois/L=Chicago/O=Ruby CA Project/CN=Test CA'
|
183
|
+
parsed_crl.issuer_cn.should == 'Test CA'
|
184
|
+
end
|
185
|
+
it "writes to der" do
|
186
|
+
crl = R509::Crl::Administrator.new(@test_ca_config)
|
187
|
+
crl.generate_crl
|
188
|
+
sio = StringIO.new
|
189
|
+
sio.set_encoding("BINARY") if sio.respond_to?(:set_encoding)
|
190
|
+
crl.write_der(sio)
|
191
|
+
parsed_crl = R509::Crl::Parser.new(sio.string)
|
192
|
+
parsed_crl.issuer.to_s.should == '/C=US/ST=Illinois/L=Chicago/O=Ruby CA Project/CN=Test CA'
|
193
|
+
parsed_crl.issuer_cn.should == 'Test CA'
|
194
|
+
end
|
195
|
+
it "writes crl list" do
|
196
|
+
crl = R509::Crl::Administrator.new(@test_ca_config)
|
197
|
+
crl.revoke_cert(12345)
|
198
|
+
crl.save_crl_list
|
199
|
+
crl.crl_list_file.string.should match(/[0-9]+,[0-9]+,[0-9]+,[0-9]+,[0-9]+/)
|
200
|
+
end
|
201
|
+
it "doesn't write the crl_number_file when it is nil" do
|
202
|
+
config = R509::Config::CaConfig.new(
|
203
|
+
:ca_cert => TestFixtures.test_ca_cert
|
204
|
+
)
|
205
|
+
crl = R509::Crl::Administrator.new(config)
|
206
|
+
expect { crl.save_crl_number }.to_not raise_error(StandardError)
|
207
|
+
end
|
208
|
+
it "doesn't write the crl_list_file when it is nil" do
|
209
|
+
config = R509::Config::CaConfig.new(
|
210
|
+
:ca_cert => TestFixtures.test_ca_cert
|
211
|
+
)
|
212
|
+
crl = R509::Crl::Administrator.new(config)
|
213
|
+
expect { crl.save_crl_list }.to_not raise_error(StandardError)
|
214
|
+
end
|
215
|
+
end
|