Skip to content

Commit 61012df

Browse files
committed
test/openssl/test_x509store: break up test_verify
The test case is huge and too complex. Break it up into separate test cases for better documentation.
1 parent 02b6f82 commit 61012df

File tree

1 file changed

+227
-112
lines changed

1 file changed

+227
-112
lines changed

test/openssl/test_x509store.rb

Lines changed: 227 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -65,159 +65,274 @@ def test_add_file_path
6565
tmpfile and tmpfile.close!
6666
end
6767

68-
def test_verify
69-
# OpenSSL uses time(2) while Time.now uses clock_gettime(CLOCK_REALTIME),
70-
# and there may be difference.
71-
now = Time.now - 3
68+
def test_verify_simple
7269
ca_exts = [
73-
["basicConstraints","CA:TRUE",true],
74-
["keyUsage","cRLSign,keyCertSign",true],
70+
["basicConstraints", "CA:TRUE", true],
71+
["keyUsage", "cRLSign,keyCertSign", true],
7572
]
73+
ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA")
74+
ca1_key = Fixtures.pkey("rsa-1")
75+
ca1_cert = issue_cert(ca1, ca1_key, 1, ca_exts, nil, nil)
76+
ca2 = OpenSSL::X509::Name.parse_rfc2253("CN=Intermediate CA")
77+
ca2_key = Fixtures.pkey("rsa-2")
78+
ca2_cert = issue_cert(ca2, ca2_key, 2, ca_exts, ca1_cert, ca1_key)
79+
7680
ee_exts = [
77-
["keyUsage","keyEncipherment,digitalSignature",true],
81+
["keyUsage", "keyEncipherment,digitalSignature", true],
7882
]
79-
ca1_cert = issue_cert(@ca1, @rsa2048, 1, ca_exts, nil, nil)
80-
ca2_cert = issue_cert(@ca2, @rsa1024, 2, ca_exts, ca1_cert, @rsa2048,
81-
not_after: now+1800)
82-
ee1_cert = issue_cert(@ee1, @dsa256, 10, ee_exts, ca2_cert, @rsa1024)
83-
ee2_cert = issue_cert(@ee2, @dsa512, 20, ee_exts, ca2_cert, @rsa1024)
84-
ee3_cert = issue_cert(@ee2, @dsa512, 30, ee_exts, ca2_cert, @rsa1024,
85-
not_before: now-100, not_after: now-1)
86-
ee4_cert = issue_cert(@ee2, @dsa512, 40, ee_exts, ca2_cert, @rsa1024,
87-
not_before: now+1000, not_after: now+2000,)
88-
89-
revoke_info = []
90-
crl1 = issue_crl(revoke_info, 1, now, now+1800, [],
91-
ca1_cert, @rsa2048, OpenSSL::Digest.new('SHA1'))
92-
revoke_info = [ [2, now, 1], ]
93-
crl1_2 = issue_crl(revoke_info, 2, now, now+1800, [],
94-
ca1_cert, @rsa2048, OpenSSL::Digest.new('SHA1'))
95-
revoke_info = [ [20, now, 1], ]
96-
crl2 = issue_crl(revoke_info, 1, now, now+1800, [],
97-
ca2_cert, @rsa1024, OpenSSL::Digest.new('SHA1'))
98-
revoke_info = []
99-
crl2_2 = issue_crl(revoke_info, 2, now-100, now-1, [],
100-
ca2_cert, @rsa1024, OpenSSL::Digest.new('SHA1'))
101-
102-
assert_equal(true, ca1_cert.verify(ca1_cert.public_key)) # self signed
103-
assert_equal(true, ca2_cert.verify(ca1_cert.public_key)) # issued by ca1
104-
assert_equal(true, ee1_cert.verify(ca2_cert.public_key)) # issued by ca2
105-
assert_equal(true, ee2_cert.verify(ca2_cert.public_key)) # issued by ca2
106-
assert_equal(true, ee3_cert.verify(ca2_cert.public_key)) # issued by ca2
107-
assert_equal(true, crl1.verify(ca1_cert.public_key)) # issued by ca1
108-
assert_equal(true, crl1_2.verify(ca1_cert.public_key)) # issued by ca1
109-
assert_equal(true, crl2.verify(ca2_cert.public_key)) # issued by ca2
110-
assert_equal(true, crl2_2.verify(ca2_cert.public_key)) # issued by ca2
83+
ee1 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 1")
84+
ee1_key = Fixtures.pkey("rsa-3")
85+
ee1_cert = issue_cert(ee1, ee1_key, 10, ee_exts, ca2_cert, ca2_key)
11186

87+
# Nothing trusted
11288
store = OpenSSL::X509::Store.new
113-
assert_equal(false, store.verify(ca1_cert))
114-
assert_not_equal(OpenSSL::X509::V_OK, store.error)
89+
assert_equal(false, store.verify(ee1_cert, [ca2_cert, ca1_cert]))
90+
assert_equal(OpenSSL::X509::V_ERR_SELF_SIGNED_CERT_IN_CHAIN, store.error)
91+
assert_match(/self.signed/i, store.error_string)
11592

116-
assert_equal(false, store.verify(ca2_cert))
117-
assert_not_equal(OpenSSL::X509::V_OK, store.error)
93+
# CA1 trusted, CA2 missing
94+
store = OpenSSL::X509::Store.new
95+
store.add_cert(ca1_cert)
96+
assert_equal(false, store.verify(ee1_cert))
97+
assert_equal(OpenSSL::X509::V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, store.error)
11898

99+
# CA1 trusted, CA2 supplied
100+
store = OpenSSL::X509::Store.new
119101
store.add_cert(ca1_cert)
120-
assert_equal(true, store.verify(ca2_cert))
102+
assert_equal(true, store.verify(ee1_cert, [ca2_cert]))
103+
assert_match(/ok/i, store.error_string)
121104
assert_equal(OpenSSL::X509::V_OK, store.error)
122-
assert_equal("ok", store.error_string)
123-
chain = store.chain
124-
assert_equal(2, chain.size)
125-
assert_equal(@ca2.to_der, chain[0].subject.to_der)
126-
assert_equal(@ca1.to_der, chain[1].subject.to_der)
105+
assert_equal([ee1_cert, ca2_cert, ca1_cert], store.chain)
106+
end
127107

128-
store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
129-
assert_equal(false, store.verify(ca2_cert))
130-
assert_not_equal(OpenSSL::X509::V_OK, store.error)
108+
def test_verify_callback
109+
ca_exts = [
110+
["basicConstraints", "CA:TRUE", true],
111+
["keyUsage", "cRLSign,keyCertSign", true],
112+
]
113+
ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA")
114+
ca1_key = Fixtures.pkey("rsa-1")
115+
ca1_cert = issue_cert(ca1, ca1_key, 1, ca_exts, nil, nil)
116+
ca2 = OpenSSL::X509::Name.parse_rfc2253("CN=Intermediate CA")
117+
ca2_key = Fixtures.pkey("rsa-2")
118+
ca2_cert = issue_cert(ca2, ca2_key, 2, ca_exts, ca1_cert, ca1_key)
131119

132-
store.purpose = OpenSSL::X509::PURPOSE_CRL_SIGN
133-
assert_equal(true, store.verify(ca2_cert))
134-
assert_equal(OpenSSL::X509::V_OK, store.error)
120+
ee_exts = [
121+
["keyUsage", "keyEncipherment,digitalSignature", true],
122+
]
123+
ee1 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 1")
124+
ee1_key = Fixtures.pkey("rsa-3")
125+
ee1_cert = issue_cert(ee1, ee1_key, 10, ee_exts, ca2_cert, ca2_key)
135126

136-
store.add_cert(ca2_cert)
137-
store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
138-
assert_equal(true, store.verify(ee1_cert))
139-
assert_equal(true, store.verify(ee2_cert))
140-
assert_equal(OpenSSL::X509::V_OK, store.error)
141-
assert_equal("ok", store.error_string)
142-
chain = store.chain
143-
assert_equal(3, chain.size)
144-
assert_equal(@ee2.to_der, chain[0].subject.to_der)
145-
assert_equal(@ca2.to_der, chain[1].subject.to_der)
146-
assert_equal(@ca1.to_der, chain[2].subject.to_der)
147-
assert_equal(false, store.verify(ee3_cert))
148-
assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error)
149-
assert_match(/expire/i, store.error_string)
150-
assert_equal(false, store.verify(ee4_cert))
151-
assert_equal(OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID, store.error)
152-
assert_match(/not yet valid/i, store.error_string)
127+
# verify_callback on X509::Store is called with proper arguments
128+
cb_calls = []
129+
store = OpenSSL::X509::Store.new
130+
store.verify_callback = -> (preverify_ok, sctx) {
131+
cb_calls << [preverify_ok, sctx.current_cert]
132+
preverify_ok
133+
}
134+
store.add_cert(ca1_cert)
135+
assert_equal(true, store.verify(ee1_cert, [ca2_cert]))
136+
assert_equal(3, cb_calls.size)
137+
assert_equal([true, ca1_cert], cb_calls[0])
138+
assert_equal([true, ca2_cert], cb_calls[1])
139+
assert_equal([true, ee1_cert], cb_calls[2])
153140

141+
# verify_callback can change verification result
142+
store = OpenSSL::X509::Store.new
143+
store.verify_callback = -> (preverify_ok, sctx) {
144+
next preverify_ok if sctx.current_cert != ee1_cert
145+
sctx.error = OpenSSL::X509::V_ERR_APPLICATION_VERIFICATION
146+
false
147+
}
148+
store.add_cert(ca1_cert)
149+
assert_equal(false, store.verify(ee1_cert, [ca2_cert]))
150+
assert_equal(OpenSSL::X509::V_ERR_APPLICATION_VERIFICATION, store.error)
151+
152+
# Exception raised by verify_callback is currently suppressed, and is
153+
# treated as a non-truthy return (with warning)
154+
store = OpenSSL::X509::Store.new
155+
store.verify_callback = -> (preverify_ok, sctx) {
156+
raise "suppressed"
157+
}
158+
store.add_cert(ca1_cert)
159+
assert_warning(/exception in verify_callback/) {
160+
assert_equal(false, store.verify(ee1_cert, [ca2_cert]))
161+
}
162+
163+
# The block given to X509::Store#verify replaces it
164+
called = nil
165+
store = OpenSSL::X509::Store.new
166+
store.verify_callback = -> (preverify_ok, sctx) { called = :store; preverify_ok }
167+
store.add_cert(ca1_cert)
168+
blk = proc { |preverify_ok, sctx| called = :block; preverify_ok }
169+
assert_equal(true, store.verify(ee1_cert, [ca2_cert], &blk))
170+
assert_equal(:block, called)
171+
end
172+
173+
def test_verify_purpose
174+
ca_exts = [
175+
["basicConstraints", "CA:TRUE", true],
176+
["keyUsage", "cRLSign,keyCertSign", true],
177+
]
178+
ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA")
179+
ca1_key = Fixtures.pkey("rsa-1")
180+
ca1_cert = issue_cert(ca1, ca1_key, 1, ca_exts, nil, nil)
181+
182+
ee_exts = [
183+
["keyUsage", "keyEncipherment,digitalSignature", true],
184+
]
185+
ee1 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 1")
186+
ee1_key = Fixtures.pkey("rsa-3")
187+
ee1_cert = issue_cert(ee1, ee1_key, 10, ee_exts, ca1_cert, ca1_key)
188+
189+
# Purpose not set
154190
store = OpenSSL::X509::Store.new
155191
store.add_cert(ca1_cert)
156-
store.add_cert(ca2_cert)
157-
store.time = now + 1500
158192
assert_equal(true, store.verify(ca1_cert))
159-
assert_equal(true, store.verify(ca2_cert))
160-
assert_equal(true, store.verify(ee4_cert))
161-
store.time = now + 1900
193+
assert_equal(true, store.verify(ee1_cert))
194+
195+
# Purpose set to X509::PURPOSE_SSL_CLIENT; keyUsage is checked
196+
store = OpenSSL::X509::Store.new
197+
store.purpose = OpenSSL::X509::PURPOSE_CRL_SIGN
198+
store.add_cert(ca1_cert)
162199
assert_equal(true, store.verify(ca1_cert))
163-
assert_equal(false, store.verify(ca2_cert))
164-
assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error)
165-
assert_equal(false, store.verify(ee4_cert))
166-
assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error)
167-
store.time = now + 4000
168200
assert_equal(false, store.verify(ee1_cert))
169-
assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error)
170-
assert_equal(false, store.verify(ee4_cert))
171-
assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error)
201+
end
202+
203+
def test_verify_validity_period
204+
# Creating test certificates with validity periods:
205+
#
206+
# now-5000 now-1000 now+1000 now+5000
207+
# CA1:|---------------------------------------------------------------|
208+
# EE1:|---------------------------------------------------------------|
209+
# EE2:|-------------------------|
210+
# EE3: |-------------------------|
211+
now = Time.now
212+
ca_exts = [
213+
["basicConstraints", "CA:TRUE", true],
214+
["keyUsage", "cRLSign,keyCertSign", true],
215+
]
216+
ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA")
217+
ca1_key = Fixtures.pkey("rsa-1")
218+
ca1_cert = issue_cert(ca1, ca1_key, 1, ca_exts, nil, nil,
219+
not_before: now - 5000, not_after: now + 5000)
220+
221+
ee_exts = [
222+
["keyUsage", "keyEncipherment,digitalSignature", true],
223+
]
224+
ee1 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 1")
225+
ee1_key = Fixtures.pkey("rsa-1")
226+
ee1_cert = issue_cert(ee1, ee1_key, 11, ee_exts, ca1_cert, ca1_key,
227+
not_before: now - 5000, not_after: now + 5000)
228+
ee2 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 2")
229+
ee2_key = Fixtures.pkey("rsa-2")
230+
ee2_cert = issue_cert(ee2, ee2_key, 12, ee_exts, ca1_cert, ca1_key,
231+
not_before: now - 5000, not_after: now - 1000)
232+
ee3 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 3")
233+
ee3_key = Fixtures.pkey("rsa-3")
234+
ee3_cert = issue_cert(ee3, ee3_key, 13, ee_exts, ca1_cert, ca1_key,
235+
not_before: now + 1000, not_after: now + 5000)
172236

173-
# the underlying X509 struct caches the result of the last
174-
# verification for signature and not-before. so the following code
175-
# rebuilds new objects to avoid site effect.
176-
store.time = Time.now - 4000
177-
assert_equal(false, store.verify(OpenSSL::X509::Certificate.new(ca2_cert)))
237+
# Using system time
238+
store = OpenSSL::X509::Store.new
239+
store.add_cert(ca1_cert)
240+
assert_equal(true, store.verify(ee1_cert))
241+
assert_equal(false, store.verify(ee2_cert))
242+
assert_equal(OpenSSL::X509::V_ERR_CERT_HAS_EXPIRED, store.error)
243+
assert_equal(false, store.verify(ee3_cert))
178244
assert_equal(OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID, store.error)
179-
assert_equal(false, store.verify(OpenSSL::X509::Certificate.new(ee1_cert)))
245+
246+
# Time set to now-2000; EE2 is still valid
247+
store = OpenSSL::X509::Store.new
248+
store.time = now - 2000
249+
store.add_cert(ca1_cert)
250+
assert_equal(true, store.verify(ee1_cert))
251+
assert_equal(true, store.verify(ee2_cert))
252+
assert_equal(false, store.verify(ee3_cert))
180253
assert_equal(OpenSSL::X509::V_ERR_CERT_NOT_YET_VALID, store.error)
254+
end
181255

256+
def test_verify_with_crl
257+
ca_exts = [
258+
["basicConstraints", "CA:TRUE", true],
259+
["keyUsage", "cRLSign,keyCertSign", true],
260+
]
261+
ca1 = OpenSSL::X509::Name.parse_rfc2253("CN=Root CA")
262+
ca1_key = Fixtures.pkey("rsa-1")
263+
ca1_cert = issue_cert(ca1, ca1_key, 1, ca_exts, nil, nil)
264+
ca2 = OpenSSL::X509::Name.parse_rfc2253("CN=Intermediate CA")
265+
ca2_key = Fixtures.pkey("rsa-2")
266+
ca2_cert = issue_cert(ca2, ca2_key, 2, ca_exts, ca1_cert, ca1_key)
267+
268+
ee_exts = [
269+
["keyUsage", "keyEncipherment,digitalSignature", true],
270+
]
271+
ee1 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 1")
272+
ee1_key = Fixtures.pkey("rsa-3")
273+
ee1_cert = issue_cert(ee1, ee1_key, 10, ee_exts, ca2_cert, ca2_key)
274+
ee2 = OpenSSL::X509::Name.parse_rfc2253("CN=EE 2")
275+
ee2_key = Fixtures.pkey("rsa-3")
276+
ee2_cert = issue_cert(ee2, ee2_key, 20, ee_exts, ca2_cert, ca2_key)
277+
278+
# OpenSSL uses time(2) while Time.now uses clock_gettime(CLOCK_REALTIME),
279+
# and there may be difference, so giving 50 seconds margin.
280+
now = Time.now - 50
281+
revoke_info = []
282+
ca1_crl1 = issue_crl(revoke_info, 1, now, now+1800, [], ca1_cert, ca1_key, "sha256")
283+
revoke_info = [ [2, now, 1], ]
284+
ca1_crl2 = issue_crl(revoke_info, 2, now, now+1800, [], ca1_cert, ca1_key, "sha256")
285+
286+
revoke_info = [ [20, now, 1], ]
287+
ca2_crl1 = issue_crl(revoke_info, 1, now, now+1800, [], ca2_cert, ca2_key, "sha256")
288+
revoke_info = []
289+
ca2_crl2 = issue_crl(revoke_info, 2, now-100, now-1, [], ca2_cert, ca2_key, "sha256")
290+
291+
# CRL check required, but no CRL supplied
292+
store = OpenSSL::X509::Store.new
293+
store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK
294+
store.add_cert(ca1_cert)
295+
assert_equal(false, store.verify(ca2_cert))
296+
assert_equal(OpenSSL::X509::V_ERR_UNABLE_TO_GET_CRL, store.error)
297+
298+
# Intermediate CA revoked EE2
182299
store = OpenSSL::X509::Store.new
183-
store.purpose = OpenSSL::X509::PURPOSE_ANY
184300
store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK
185301
store.add_cert(ca1_cert)
186-
store.add_crl(crl1) # revoke no cert
187-
store.add_crl(crl2) # revoke ee2_cert
188-
assert_equal(true, store.verify(ca1_cert))
189-
assert_equal(true, store.verify(ca2_cert))
190-
assert_equal(true, store.verify(ee1_cert, [ca2_cert]))
302+
store.add_crl(ca1_crl1) # revoke no cert
303+
store.add_crl(ca2_crl1) # revoke ee2_cert
304+
assert_equal(true, store.verify(ca2_cert))
305+
assert_equal(true, store.verify(ee1_cert, [ca2_cert]))
191306
assert_equal(false, store.verify(ee2_cert, [ca2_cert]))
192307

308+
# Root CA revoked Intermediate CA; Intermediate CA revoked EE2
193309
store = OpenSSL::X509::Store.new
194-
store.purpose = OpenSSL::X509::PURPOSE_ANY
195310
store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK
196311
store.add_cert(ca1_cert)
197-
store.add_crl(crl1_2) # revoke ca2_cert
198-
store.add_crl(crl2) # revoke ee2_cert
199-
assert_equal(true, store.verify(ca1_cert))
312+
store.add_crl(ca1_crl2) # revoke ca2_cert
313+
store.add_crl(ca2_crl1) # revoke ee2_cert
200314
assert_equal(false, store.verify(ca2_cert))
201-
assert_equal(true, store.verify(ee1_cert, [ca2_cert]),
202-
"This test is expected to be success with OpenSSL 0.9.7c or later.")
315+
# Validity of intermediate CAs is not checked by default
316+
assert_equal(true, store.verify(ee1_cert, [ca2_cert]))
203317
assert_equal(false, store.verify(ee2_cert, [ca2_cert]))
204318

205-
store.flags =
206-
OpenSSL::X509::V_FLAG_CRL_CHECK|OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
207-
assert_equal(true, store.verify(ca1_cert))
319+
# Same as above, but with OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
320+
store = OpenSSL::X509::Store.new
321+
store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK|OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
322+
store.add_cert(ca1_cert)
323+
store.add_crl(ca1_crl2) # revoke ca2_cert
324+
store.add_crl(ca2_crl1) # revoke ee2_cert
208325
assert_equal(false, store.verify(ca2_cert))
209326
assert_equal(false, store.verify(ee1_cert, [ca2_cert]))
210327
assert_equal(false, store.verify(ee2_cert, [ca2_cert]))
211328

329+
# Expired CRL supplied
212330
store = OpenSSL::X509::Store.new
213-
store.purpose = OpenSSL::X509::PURPOSE_ANY
214-
store.flags =
215-
OpenSSL::X509::V_FLAG_CRL_CHECK|OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
331+
store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK|OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
216332
store.add_cert(ca1_cert)
217333
store.add_cert(ca2_cert)
218-
store.add_crl(crl1)
219-
store.add_crl(crl2_2) # issued by ca2 but expired.
220-
assert_equal(true, store.verify(ca1_cert))
334+
store.add_crl(ca1_crl1)
335+
store.add_crl(ca2_crl2) # issued by ca2 but expired
221336
assert_equal(true, store.verify(ca2_cert))
222337
assert_equal(false, store.verify(ee1_cert))
223338
assert_equal(OpenSSL::X509::V_ERR_CRL_HAS_EXPIRED, store.error)

0 commit comments

Comments
 (0)