1 # frozen_string_literal: true
2 require_relative "utils"
6 class OpenSSL::TestX509Store < OpenSSL::TestCase
8 # v2.3.0 emits explicit warning
9 assert_warning(/new does not take any arguments/) {
10 OpenSSL::X509::Store.new(123)
14 def test_add_file_path
16 ["basicConstraints", "CA:TRUE", true],
17 ["keyUsage", "cRLSign,keyCertSign", true],
19 cert1_subj = OpenSSL::X509::Name.parse_rfc2253("CN=Cert 1")
20 cert1_key = Fixtures.pkey("rsa-1")
21 cert1 = issue_cert(cert1_subj, cert1_key, 1, ca_exts, nil, nil)
22 cert2_subj = OpenSSL::X509::Name.parse_rfc2253("CN=Cert 2")
23 cert2_key = Fixtures.pkey("rsa-2")
24 cert2 = issue_cert(cert2_subj, cert2_key, 1, ca_exts, nil, nil)
26 # X509::Store#add_file reads concatenated PEM file
27 tmpfile = Tempfile.open { |f| f << cert1.to_pem << cert2.to_pem; f }
28 store = OpenSSL::X509::Store.new
29 assert_equal false, store.verify(cert1)
30 assert_equal false, store.verify(cert2)
31 store.add_file(tmpfile.path)
32 assert_equal true, store.verify(cert1)
33 assert_equal true, store.verify(cert2)
35 # X509::Store#add_path
37 hash1 = "%08x.%d" % [cert1_subj.hash, 0]
38 File.write(File.join(dir, hash1), cert1.to_pem)
39 store = OpenSSL::X509::Store.new
42 assert_equal true, store.verify(cert1)
43 assert_equal false, store.verify(cert2)
46 # OpenSSL < 1.1.1 leaks an error on a duplicate certificate
47 assert_nothing_raised { store.add_file(tmpfile.path) }
48 assert_equal [], OpenSSL.errors
51 assert_raise(TypeError) { store.add_file(nil) }
53 tmpfile and tmpfile.close!
56 def test_verify_simple
58 ["basicConstraints", "CA:TRUE", true],
59 ["keyUsage", "cRLSign,keyCertSign", true],
61 ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA")
62 ca1_key = Fixtures.pkey("rsa-1")
63 ca1_cert = issue_cert(ca1, ca1_key, 1, ca_exts, nil, nil)
64 ca2 = OpenSSL::X509::Name.parse_rfc2253("CN=Intermediate CA")
65 ca2_key = Fixtures.pkey("rsa-2")
66 ca2_cert = issue_cert(ca2, ca2_key, 2, ca_exts, ca1_cert, ca1_key)
69 ["keyUsage", "keyEncipherment,digitalSignature", true],
71 ee1 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 1")
72 ee1_key = Fixtures.pkey("rsa-3")
73 ee1_cert = issue_cert(ee1, ee1_key, 10, ee_exts, ca2_cert, ca2_key)
76 store = OpenSSL::X509::Store.new
77 assert_equal(false, store.verify(ee1_cert, [ca2_cert, ca1_cert]))
78 assert_equal(OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN, store.error)
79 assert_match(/self.signed/i, store.error_string)
81 # CA1 trusted, CA2 missing
82 store = OpenSSL::X509::Store.new
83 store.add_cert(ca1_cert)
84 assert_equal(false, store.verify(ee1_cert))
85 assert_equal(OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, store.error)
87 # CA1 trusted, CA2 supplied
88 store = OpenSSL::X509::Store.new
89 store.add_cert(ca1_cert)
90 assert_equal(true, store.verify(ee1_cert, [ca2_cert]))
91 assert_match(/ok/i, store.error_string)
92 assert_equal(OpenSSL::X509::V_OK, store.error)
93 assert_equal([ee1_cert, ca2_cert, ca1_cert], store.chain)
96 def test_verify_callback
98 ["basicConstraints", "CA:TRUE", true],
99 ["keyUsage", "cRLSign,keyCertSign", true],
101 ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA")
102 ca1_key = Fixtures.pkey("rsa-1")
103 ca1_cert = issue_cert(ca1, ca1_key, 1, ca_exts, nil, nil)
104 ca2 = OpenSSL::X509::Name.parse_rfc2253("CN=Intermediate CA")
105 ca2_key = Fixtures.pkey("rsa-2")
106 ca2_cert = issue_cert(ca2, ca2_key, 2, ca_exts, ca1_cert, ca1_key)
109 ["keyUsage", "keyEncipherment,digitalSignature", true],
111 ee1 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 1")
112 ee1_key = Fixtures.pkey("rsa-3")
113 ee1_cert = issue_cert(ee1, ee1_key, 10, ee_exts, ca2_cert, ca2_key)
115 # verify_callback on X509::Store is called with proper arguments
117 store = OpenSSL::X509::Store.new
118 store.verify_callback = -> (preverify_ok, sctx) {
119 cb_calls << [preverify_ok, sctx.current_cert]
122 store.add_cert(ca1_cert)
123 assert_equal(true, store.verify(ee1_cert, [ca2_cert]))
124 assert_include([2, 3, 4, 5], cb_calls.size)
125 cb_calls.each do |pre_ok, cert|
126 assert_equal(true, pre_ok)
127 assert_include([ca1_cert, ca2_cert, ee1_cert], cert)
130 # verify_callback can change verification result
131 store = OpenSSL::X509::Store.new
132 store.verify_callback = -> (preverify_ok, sctx) {
133 next preverify_ok if sctx.current_cert != ee1_cert
134 sctx.error = OpenSSL::X509::V_ERR_APPLICATION_VERIFICATION
137 store.add_cert(ca1_cert)
138 assert_equal(false, store.verify(ee1_cert, [ca2_cert]))
139 assert_equal(OpenSSL::X509::V_ERR_APPLICATION_VERIFICATION, store.error)
141 # Exception raised by verify_callback is currently suppressed, and is
142 # treated as a non-truthy return (with warning)
143 store = OpenSSL::X509::Store.new
144 store.verify_callback = -> (preverify_ok, sctx) {
147 store.add_cert(ca1_cert)
148 assert_warning(/exception in verify_callback/) {
149 assert_equal(false, store.verify(ee1_cert, [ca2_cert]))
152 # The block given to X509::Store#verify replaces it
154 store = OpenSSL::X509::Store.new
155 store.verify_callback = -> (preverify_ok, sctx) { called = :store; preverify_ok }
156 store.add_cert(ca1_cert)
157 blk = proc { |preverify_ok, sctx| called = :block; preverify_ok }
158 assert_equal(true, store.verify(ee1_cert, [ca2_cert], &blk))
159 assert_equal(:block, called)
162 def test_verify_purpose
164 ["basicConstraints", "CA:TRUE", true],
165 ["keyUsage", "cRLSign,keyCertSign", true],
167 ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA")
168 ca1_key = Fixtures.pkey("rsa-1")
169 ca1_cert = issue_cert(ca1, ca1_key, 1, ca_exts, nil, nil)
172 ["keyUsage", "keyEncipherment,digitalSignature", true],
174 ee1 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 1")
175 ee1_key = Fixtures.pkey("rsa-3")
176 ee1_cert = issue_cert(ee1, ee1_key, 10, ee_exts, ca1_cert, ca1_key)
179 store = OpenSSL::X509::Store.new
180 store.add_cert(ca1_cert)
181 assert_equal(true, store.verify(ca1_cert))
182 assert_equal(true, store.verify(ee1_cert))
184 # Purpose set to X509::PURPOSE_SSL_CLIENT; keyUsage is checked
185 store = OpenSSL::X509::Store.new
186 store.purpose = OpenSSL::X509::PURPOSE_CRL_SIGN
187 store.add_cert(ca1_cert)
188 assert_equal(true, store.verify(ca1_cert))
189 assert_equal(false, store.verify(ee1_cert))
192 def test_verify_validity_period
193 # Creating test certificates with validity periods:
195 # now-5000 now-1000 now+1000 now+5000
196 # CA1:|---------------------------------------------------------------|
197 # EE1:|---------------------------------------------------------------|
198 # EE2:|-------------------------|
199 # EE3: |-------------------------|
202 ["basicConstraints", "CA:TRUE", true],
203 ["keyUsage", "cRLSign,keyCertSign", true],
205 ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA")
206 ca1_key = Fixtures.pkey("rsa-1")
207 ca1_cert = issue_cert(ca1, ca1_key, 1, ca_exts, nil, nil,
208 not_before: now - 5000, not_after: now + 5000)
211 ["keyUsage", "keyEncipherment,digitalSignature", true],
213 ee1 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 1")
214 ee1_key = Fixtures.pkey("rsa-1")
215 ee1_cert = issue_cert(ee1, ee1_key, 11, ee_exts, ca1_cert, ca1_key,
216 not_before: now - 5000, not_after: now + 5000)
217 ee2 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 2")
218 ee2_key = Fixtures.pkey("rsa-2")
219 ee2_cert = issue_cert(ee2, ee2_key, 12, ee_exts, ca1_cert, ca1_key,
220 not_before: now - 5000, not_after: now - 1000)
221 ee3 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 3")
222 ee3_key = Fixtures.pkey("rsa-3")
223 ee3_cert = issue_cert(ee3, ee3_key, 13, ee_exts, ca1_cert, ca1_key,
224 not_before: now + 1000, not_after: now + 5000)
227 store = OpenSSL::X509::Store.new
228 store.add_cert(ca1_cert)
229 assert_equal(true, store.verify(ee1_cert))
230 assert_equal(false, store.verify(ee2_cert))
231 assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error)
232 assert_equal(false, store.verify(ee3_cert))
233 assert_equal(OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID, store.error)
235 # Time set to now-2000; EE2 is still valid
236 store = OpenSSL::X509::Store.new
237 store.time = now - 2000
238 store.add_cert(ca1_cert)
239 assert_equal(true, store.verify(ee1_cert))
240 assert_equal(true, store.verify(ee2_cert))
241 assert_equal(false, store.verify(ee3_cert))
242 assert_equal(OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID, store.error)
245 def test_verify_with_crl
247 ["basicConstraints", "CA:TRUE", true],
248 ["keyUsage", "cRLSign,keyCertSign", true],
250 ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA")
251 ca1_key = Fixtures.pkey("rsa-1")
252 ca1_cert = issue_cert(ca1, ca1_key, 1, ca_exts, nil, nil)
253 ca2 = OpenSSL::X509::Name.parse_rfc2253("CN=Intermediate CA")
254 ca2_key = Fixtures.pkey("rsa-2")
255 ca2_cert = issue_cert(ca2, ca2_key, 2, ca_exts, ca1_cert, ca1_key)
258 ["keyUsage", "keyEncipherment,digitalSignature", true],
260 ee1 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 1")
261 ee1_key = Fixtures.pkey("rsa-3")
262 ee1_cert = issue_cert(ee1, ee1_key, 10, ee_exts, ca2_cert, ca2_key)
263 ee2 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 2")
264 ee2_key = Fixtures.pkey("rsa-3")
265 ee2_cert = issue_cert(ee2, ee2_key, 20, ee_exts, ca2_cert, ca2_key)
267 # OpenSSL uses time(2) while Time.now uses clock_gettime(CLOCK_REALTIME),
268 # and there may be difference, so giving 50 seconds margin.
271 ca1_crl1 = issue_crl(revoke_info, 1, now, now+1800, [], ca1_cert, ca1_key, "sha256")
272 revoke_info = [ [2, now, 1], ]
273 ca1_crl2 = issue_crl(revoke_info, 2, now, now+1800, [], ca1_cert, ca1_key, "sha256")
275 revoke_info = [ [20, now, 1], ]
276 ca2_crl1 = issue_crl(revoke_info, 1, now, now+1800, [], ca2_cert, ca2_key, "sha256")
278 ca2_crl2 = issue_crl(revoke_info, 2, now-100, now-1, [], ca2_cert, ca2_key, "sha256")
280 # CRL check required, but no CRL supplied
281 store = OpenSSL::X509::Store.new
282 store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK
283 store.add_cert(ca1_cert)
284 assert_equal(false, store.verify(ca2_cert))
285 assert_equal(OpenSSL::X509::V_ERR_UNABLE_TO_GET_CRL, store.error)
287 # Intermediate CA revoked EE2
288 store = OpenSSL::X509::Store.new
289 store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK
290 store.add_cert(ca1_cert)
291 store.add_crl(ca1_crl1) # revoke no cert
292 store.add_crl(ca2_crl1) # revoke ee2_cert
293 assert_equal(true, store.verify(ca2_cert))
294 assert_equal(true, store.verify(ee1_cert, [ca2_cert]))
295 assert_equal(false, store.verify(ee2_cert, [ca2_cert]))
297 # Root CA revoked Intermediate CA; Intermediate CA revoked EE2
298 store = OpenSSL::X509::Store.new
299 store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK
300 store.add_cert(ca1_cert)
301 store.add_crl(ca1_crl2) # revoke ca2_cert
302 store.add_crl(ca2_crl1) # revoke ee2_cert
303 assert_equal(false, store.verify(ca2_cert))
304 # Validity of intermediate CAs is not checked by default
305 assert_equal(true, store.verify(ee1_cert, [ca2_cert]))
306 assert_equal(false, store.verify(ee2_cert, [ca2_cert]))
308 # Same as above, but with OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
309 store = OpenSSL::X509::Store.new
310 store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK|OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
311 store.add_cert(ca1_cert)
312 store.add_crl(ca1_crl2) # revoke ca2_cert
313 store.add_crl(ca2_crl1) # revoke ee2_cert
314 assert_equal(false, store.verify(ca2_cert))
315 assert_equal(false, store.verify(ee1_cert, [ca2_cert]))
316 assert_equal(false, store.verify(ee2_cert, [ca2_cert]))
318 # Expired CRL supplied
319 store = OpenSSL::X509::Store.new
320 store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK|OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
321 store.add_cert(ca1_cert)
322 store.add_cert(ca2_cert)
323 store.add_crl(ca1_crl1)
324 store.add_crl(ca2_crl2) # issued by ca2 but expired
325 assert_equal(true, store.verify(ca2_cert))
326 assert_equal(false, store.verify(ee1_cert))
327 assert_equal(OpenSSL::X509::V_ERR_CRL_HAS_EXPIRED, store.error)
328 assert_equal(false, store.verify(ee2_cert))
331 def test_add_cert_duplicate
332 # Up until OpenSSL 1.1.0, X509_STORE_add_{cert,crl}() returned an error
333 # if the given certificate is already in the X509_STORE
334 return if openssl?(1, 1, 0) || libressl?
335 ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA")
336 ca1_key = Fixtures.pkey("rsa-1")
337 ca1_cert = issue_cert(ca1, ca1_key, 1, [], nil, nil)
338 store = OpenSSL::X509::Store.new
339 store.add_cert(ca1_cert)
340 assert_raise(OpenSSL::X509::StoreError){
341 store.add_cert(ca1_cert) # add same certificate twice
346 crl1 = issue_crl(revoke_info, 1, now, now+1800, [],
347 ca1_cert, ca1_key, "sha256")
348 revoke_info = [ [2, now, 1], ]
349 crl2 = issue_crl(revoke_info, 2, now+1800, now+3600, [],
350 ca1_cert, ca1_key, "sha256")
352 assert_raise(OpenSSL::X509::StoreError){
353 store.add_crl(crl2) # add CRL issued by same CA twice.
358 store = OpenSSL::X509::Store.new
359 assert_raise(NoMethodError) { store.dup }
360 ctx = OpenSSL::X509::StoreContext.new(store)
361 assert_raise(NoMethodError) { ctx.dup }
365 # Deprecated in Ruby 1.9.3
366 cert = OpenSSL::X509::Certificate.new
367 store = OpenSSL::X509::Store.new
368 ctx = OpenSSL::X509::StoreContext.new(store, cert, [])
369 assert_warning(/cleanup/) { ctx.cleanup }