r509 0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (162) hide show
  1. data/README.md +447 -0
  2. data/Rakefile +38 -0
  3. data/bin/r509 +96 -0
  4. data/bin/r509-parse +35 -0
  5. data/doc/R509.html +154 -0
  6. data/doc/R509/Cert.html +3954 -0
  7. data/doc/R509/Cert/Extensions.html +360 -0
  8. data/doc/R509/Cert/Extensions/AuthorityInfoAccess.html +391 -0
  9. data/doc/R509/Cert/Extensions/AuthorityKeyIdentifier.html +148 -0
  10. data/doc/R509/Cert/Extensions/BasicConstraints.html +482 -0
  11. data/doc/R509/Cert/Extensions/CrlDistributionPoints.html +316 -0
  12. data/doc/R509/Cert/Extensions/ExtendedKeyUsage.html +780 -0
  13. data/doc/R509/Cert/Extensions/KeyUsage.html +1230 -0
  14. data/doc/R509/Cert/Extensions/SubjectAlternativeName.html +467 -0
  15. data/doc/R509/Cert/Extensions/SubjectKeyIdentifier.html +216 -0
  16. data/doc/R509/CertificateAuthority.html +126 -0
  17. data/doc/R509/CertificateAuthority/Signer.html +855 -0
  18. data/doc/R509/Config.html +127 -0
  19. data/doc/R509/Config/CaConfig.html +2144 -0
  20. data/doc/R509/Config/CaConfigPool.html +599 -0
  21. data/doc/R509/Config/CaProfile.html +656 -0
  22. data/doc/R509/Config/SubjectItemPolicy.html +578 -0
  23. data/doc/R509/Crl.html +126 -0
  24. data/doc/R509/Crl/Administrator.html +2077 -0
  25. data/doc/R509/Crl/Parser.html +1224 -0
  26. data/doc/R509/Csr.html +2248 -0
  27. data/doc/R509/IOHelpers.html +564 -0
  28. data/doc/R509/MessageDigest.html +396 -0
  29. data/doc/R509/NameSanitizer.html +319 -0
  30. data/doc/R509/Ocsp.html +128 -0
  31. data/doc/R509/Ocsp/Request.html +126 -0
  32. data/doc/R509/Ocsp/Request/Nonce.html +160 -0
  33. data/doc/R509/Ocsp/Response.html +837 -0
  34. data/doc/R509/OidMapper.html +393 -0
  35. data/doc/R509/PrivateKey.html +1647 -0
  36. data/doc/R509/R509Error.html +134 -0
  37. data/doc/R509/Spki.html +1424 -0
  38. data/doc/R509/Subject.html +836 -0
  39. data/doc/R509/Validity.html +160 -0
  40. data/doc/R509/Validity/Checker.html +320 -0
  41. data/doc/R509/Validity/DefaultChecker.html +283 -0
  42. data/doc/R509/Validity/DefaultWriter.html +330 -0
  43. data/doc/R509/Validity/Status.html +561 -0
  44. data/doc/R509/Validity/Writer.html +394 -0
  45. data/doc/_index.html +501 -0
  46. data/doc/class_list.html +53 -0
  47. data/doc/css/common.css +1 -0
  48. data/doc/css/full_list.css +57 -0
  49. data/doc/css/style.css +328 -0
  50. data/doc/file.README.html +534 -0
  51. data/doc/file.r509.html +149 -0
  52. data/doc/file_list.html +58 -0
  53. data/doc/frames.html +28 -0
  54. data/doc/index.html +534 -0
  55. data/doc/js/app.js +208 -0
  56. data/doc/js/full_list.js +173 -0
  57. data/doc/js/jquery.js +4 -0
  58. data/doc/methods_list.html +1932 -0
  59. data/doc/top-level-namespace.html +112 -0
  60. data/lib/r509.rb +22 -0
  61. data/lib/r509/cert.rb +414 -0
  62. data/lib/r509/cert/extensions.rb +309 -0
  63. data/lib/r509/certificateauthority.rb +290 -0
  64. data/lib/r509/config.rb +407 -0
  65. data/lib/r509/crl.rb +379 -0
  66. data/lib/r509/csr.rb +324 -0
  67. data/lib/r509/exceptions.rb +5 -0
  68. data/lib/r509/io_helpers.rb +52 -0
  69. data/lib/r509/messagedigest.rb +49 -0
  70. data/lib/r509/ocsp.rb +85 -0
  71. data/lib/r509/oidmapper.rb +32 -0
  72. data/lib/r509/privatekey.rb +185 -0
  73. data/lib/r509/spki.rb +112 -0
  74. data/lib/r509/subject.rb +133 -0
  75. data/lib/r509/validity.rb +92 -0
  76. data/lib/r509/version.rb +4 -0
  77. data/r509.yaml +73 -0
  78. data/spec/cert/extensions_spec.rb +632 -0
  79. data/spec/cert_spec.rb +321 -0
  80. data/spec/certificate_authority_spec.rb +260 -0
  81. data/spec/config_spec.rb +349 -0
  82. data/spec/crl_spec.rb +215 -0
  83. data/spec/csr_spec.rb +302 -0
  84. data/spec/fixtures.rb +233 -0
  85. data/spec/fixtures/cert1.der +0 -0
  86. data/spec/fixtures/cert1.pem +24 -0
  87. data/spec/fixtures/cert1_public_key_modulus.txt +1 -0
  88. data/spec/fixtures/cert3.p12 +0 -0
  89. data/spec/fixtures/cert3.pem +28 -0
  90. data/spec/fixtures/cert3_key.pem +27 -0
  91. data/spec/fixtures/cert3_key_des3.pem +30 -0
  92. data/spec/fixtures/cert4.pem +14 -0
  93. data/spec/fixtures/cert5.pem +30 -0
  94. data/spec/fixtures/cert6.pem +26 -0
  95. data/spec/fixtures/cert_expired.pem +26 -0
  96. data/spec/fixtures/cert_not_yet_valid.pem +26 -0
  97. data/spec/fixtures/cert_san.pem +27 -0
  98. data/spec/fixtures/cert_san2.pem +22 -0
  99. data/spec/fixtures/config_pool_test_minimal.yaml +15 -0
  100. data/spec/fixtures/config_test.yaml +41 -0
  101. data/spec/fixtures/config_test_engine_key.yaml +7 -0
  102. data/spec/fixtures/config_test_engine_no_key_name.yaml +6 -0
  103. data/spec/fixtures/config_test_minimal.yaml +7 -0
  104. data/spec/fixtures/config_test_password.yaml +7 -0
  105. data/spec/fixtures/config_test_various.yaml +100 -0
  106. data/spec/fixtures/crl_list_file.txt +1 -0
  107. data/spec/fixtures/crl_with_reason.pem +17 -0
  108. data/spec/fixtures/csr1.der +0 -0
  109. data/spec/fixtures/csr1.pem +17 -0
  110. data/spec/fixtures/csr1_key.der +0 -0
  111. data/spec/fixtures/csr1_key.pem +27 -0
  112. data/spec/fixtures/csr1_key_encrypted_des3.pem +30 -0
  113. data/spec/fixtures/csr1_newlines.pem +32 -0
  114. data/spec/fixtures/csr1_no_begin_end.pem +15 -0
  115. data/spec/fixtures/csr1_public_key_modulus.txt +1 -0
  116. data/spec/fixtures/csr2.pem +15 -0
  117. data/spec/fixtures/csr2_key.pem +27 -0
  118. data/spec/fixtures/csr3.pem +16 -0
  119. data/spec/fixtures/csr4.pem +25 -0
  120. data/spec/fixtures/csr_dsa.pem +15 -0
  121. data/spec/fixtures/csr_invalid_signature.pem +13 -0
  122. data/spec/fixtures/dsa_key.pem +20 -0
  123. data/spec/fixtures/key4.pem +27 -0
  124. data/spec/fixtures/key4_encrypted_des3.pem +30 -0
  125. data/spec/fixtures/missing_key_identifier_ca.cer +21 -0
  126. data/spec/fixtures/missing_key_identifier_ca.key +27 -0
  127. data/spec/fixtures/ocsptest.r509.local.pem +27 -0
  128. data/spec/fixtures/ocsptest.r509.local_ocsp_request.der +0 -0
  129. data/spec/fixtures/ocsptest2.r509.local.pem +27 -0
  130. data/spec/fixtures/second_ca.cer +26 -0
  131. data/spec/fixtures/second_ca.key +27 -0
  132. data/spec/fixtures/spkac.der +0 -0
  133. data/spec/fixtures/spkac.txt +1 -0
  134. data/spec/fixtures/spkac_dsa.txt +1 -0
  135. data/spec/fixtures/stca.pem +22 -0
  136. data/spec/fixtures/stca_ocsp_request.der +0 -0
  137. data/spec/fixtures/stca_ocsp_response.der +0 -0
  138. data/spec/fixtures/test1.csr +17 -0
  139. data/spec/fixtures/test_ca.cer +22 -0
  140. data/spec/fixtures/test_ca.key +28 -0
  141. data/spec/fixtures/test_ca.p12 +0 -0
  142. data/spec/fixtures/test_ca_des3.key +30 -0
  143. data/spec/fixtures/test_ca_ocsp.cer +26 -0
  144. data/spec/fixtures/test_ca_ocsp.key +27 -0
  145. data/spec/fixtures/test_ca_ocsp.p12 +0 -0
  146. data/spec/fixtures/test_ca_ocsp_chain.txt +48 -0
  147. data/spec/fixtures/test_ca_ocsp_response.der +0 -0
  148. data/spec/fixtures/test_ca_subroot.cer +26 -0
  149. data/spec/fixtures/test_ca_subroot.key +27 -0
  150. data/spec/fixtures/test_ca_subroot_ocsp.cer +25 -0
  151. data/spec/fixtures/test_ca_subroot_ocsp.key +27 -0
  152. data/spec/fixtures/test_ca_subroot_ocsp_response.der +0 -0
  153. data/spec/fixtures/unknown_oid.csr +17 -0
  154. data/spec/message_digest_spec.rb +89 -0
  155. data/spec/ocsp_spec.rb +111 -0
  156. data/spec/oid_mapper_spec.rb +31 -0
  157. data/spec/privatekey_spec.rb +198 -0
  158. data/spec/spec_helper.rb +14 -0
  159. data/spec/spki_spec.rb +157 -0
  160. data/spec/subject_spec.rb +203 -0
  161. data/spec/validity_spec.rb +98 -0
  162. metadata +257 -0
@@ -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