r509 0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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