* 2022-01-18 [ci skip]
[ruby-80x24.org.git] / test / openssl / test_x509store.rb
blobd6c0e707a27f19ad4b985a4844bd03f9c2e3ba93
1 # frozen_string_literal: true
2 require_relative "utils"
4 if defined?(OpenSSL)
6 class OpenSSL::TestX509Store < OpenSSL::TestCase
7   def test_store_new
8     # v2.3.0 emits explicit warning
9     assert_warning(/new does not take any arguments/) {
10       OpenSSL::X509::Store.new(123)
11     }
12   end
14   def test_add_file_path
15     ca_exts = [
16       ["basicConstraints", "CA:TRUE", true],
17       ["keyUsage", "cRLSign,keyCertSign", true],
18     ]
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
36     Dir.mktmpdir do |dir|
37       hash1 = "%08x.%d" % [cert1_subj.hash, 0]
38       File.write(File.join(dir, hash1), cert1.to_pem)
39       store = OpenSSL::X509::Store.new
40       store.add_path(dir)
42       assert_equal true, store.verify(cert1)
43       assert_equal false, store.verify(cert2)
44     end
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
50     # Non-String is given
51     assert_raise(TypeError) { store.add_file(nil) }
52   ensure
53     tmpfile and tmpfile.close!
54   end
56   def test_verify_simple
57     ca_exts = [
58       ["basicConstraints", "CA:TRUE", true],
59       ["keyUsage", "cRLSign,keyCertSign", true],
60     ]
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)
68     ee_exts = [
69       ["keyUsage", "keyEncipherment,digitalSignature", true],
70     ]
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)
75     # Nothing trusted
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)
94   end
96   def test_verify_callback
97     ca_exts = [
98       ["basicConstraints", "CA:TRUE", true],
99       ["keyUsage", "cRLSign,keyCertSign", true],
100     ]
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)
108     ee_exts = [
109       ["keyUsage", "keyEncipherment,digitalSignature", true],
110     ]
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
116     cb_calls = []
117     store = OpenSSL::X509::Store.new
118     store.verify_callback = -> (preverify_ok, sctx) {
119       cb_calls << [preverify_ok, sctx.current_cert]
120       preverify_ok
121     }
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)
128     end
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
135       false
136     }
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) {
145       raise "suppressed"
146     }
147     store.add_cert(ca1_cert)
148     assert_warning(/exception in verify_callback/) {
149       assert_equal(false, store.verify(ee1_cert, [ca2_cert]))
150     }
152     # The block given to X509::Store#verify replaces it
153     called = nil
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)
160   end
162   def test_verify_purpose
163     ca_exts = [
164       ["basicConstraints", "CA:TRUE", true],
165       ["keyUsage", "cRLSign,keyCertSign", true],
166     ]
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)
171     ee_exts = [
172       ["keyUsage", "keyEncipherment,digitalSignature", true],
173     ]
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)
178     # Purpose not set
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))
190   end
192   def test_verify_validity_period
193     # Creating test certificates with validity periods:
194     #
195     #  now-5000                 now-1000    now+1000                  now+5000
196     # CA1:|---------------------------------------------------------------|
197     # EE1:|---------------------------------------------------------------|
198     # EE2:|-------------------------|
199     # EE3:                                      |-------------------------|
200     now = Time.now
201     ca_exts = [
202       ["basicConstraints", "CA:TRUE", true],
203       ["keyUsage", "cRLSign,keyCertSign", true],
204     ]
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)
210     ee_exts = [
211       ["keyUsage", "keyEncipherment,digitalSignature", true],
212     ]
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)
226     # Using system time
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)
243   end
245   def test_verify_with_crl
246     ca_exts = [
247       ["basicConstraints", "CA:TRUE", true],
248       ["keyUsage", "cRLSign,keyCertSign", true],
249     ]
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)
257     ee_exts = [
258       ["keyUsage", "keyEncipherment,digitalSignature", true],
259     ]
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.
269     now = Time.now - 50
270     revoke_info = []
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")
277     revoke_info = []
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))
329   end
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
342     }
344     now = Time.now
345     revoke_info = []
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")
351     store.add_crl(crl1)
352     assert_raise(OpenSSL::X509::StoreError){
353       store.add_crl(crl2) # add CRL issued by same CA twice.
354     }
355   end
357   def test_dup
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 }
362   end
364   def test_ctx_cleanup
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 }
370   end