ssl-test 1.5.0 → 1.6.0

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.
@@ -2,11 +2,23 @@ require "ssl-test"
2
2
  require "benchmark"
3
3
  require 'webrick'
4
4
  require 'webrick/httpproxy'
5
+ require 'rspec/retry'
5
6
 
6
7
  # Uncomment for debug logging:
7
8
  # require "logger"
8
9
  # SSLTest.logger = Logger.new(STDOUT)
9
10
 
11
+ RSpec.configure do |config|
12
+ # The error/revocation examples below hit several public TLS test endpoints
13
+ # (badssl.com, testserver.host, ssl.com) which intermittently reset connections
14
+ # under load. They're spread across a few providers to avoid hammering a single
15
+ # one, and examples tagged `:retry` are re-run a few times (via rspec-retry) so
16
+ # transient network blips don't fail the suite.
17
+ config.verbose_retry = true
18
+ config.display_try_failure_messages = true
19
+ config.default_sleep_interval = 1
20
+ end
21
+
10
22
  describe SSLTest do
11
23
  before { SSLTest.flush_cache }
12
24
 
@@ -48,36 +60,36 @@ describe SSLTest do
48
60
  expect(cert).to be_a OpenSSL::X509::Certificate
49
61
  end
50
62
 
51
- it "returns error on self signed certificate" do
52
- valid, error, cert = SSLTest.test("https://self-signed.badssl.com/")
63
+ it "returns error on self signed certificate", :retry => 5 do
64
+ valid, error, cert = SSLTest.test("https://self-signed.testserver.host/")
53
65
  expect(error).to eq ("error code 18: self-signed certificate")
54
66
  expect(valid).to eq(false)
55
67
  expect(cert).to be_a OpenSSL::X509::Certificate
56
68
  end
57
69
 
58
- it "returns error on incomplete chain" do
70
+ it "returns error on incomplete chain", :retry => 5 do
59
71
  valid, error, cert = SSLTest.test("https://incomplete-chain.badssl.com/")
60
72
  expect(error).to eq ("error code 20: unable to get local issuer certificate")
61
73
  expect(valid).to eq(false)
62
74
  expect(cert).to be_a OpenSSL::X509::Certificate
63
75
  end
64
76
 
65
- it "returns error on untrusted root" do
66
- valid, error, cert = SSLTest.test("https://untrusted-root.badssl.com/")
77
+ it "returns error on untrusted root", :retry => 5 do
78
+ valid, error, cert = SSLTest.test("https://untrusted-root.testserver.host/")
67
79
  expect(error).to eq ("error code 19: self-signed certificate in certificate chain")
68
80
  expect(valid).to eq(false)
69
81
  expect(cert).to be_a OpenSSL::X509::Certificate
70
82
  end
71
83
 
72
- it "returns error on invalid host" do
84
+ it "returns error on invalid host", :retry => 5 do
73
85
  valid, error, cert = SSLTest.test("https://wrong.host.badssl.com/")
74
86
  expect(error).to include('error code 62: hostname mismatch')
75
87
  expect(valid).to eq(false)
76
88
  expect(cert).to be_a OpenSSL::X509::Certificate
77
89
  end
78
90
 
79
- it "returns error on expired cert" do
80
- valid, error, cert = SSLTest.test("https://expired.badssl.com/")
91
+ it "returns error on expired cert", :retry => 5 do
92
+ valid, error, cert = SSLTest.test("https://expired-rsa-dv.ssl.com/")
81
93
  expect(error).to eq ("error code 10: certificate has expired")
82
94
  expect(valid).to eq(false)
83
95
  expect(cert).to be_a OpenSSL::X509::Certificate
@@ -86,7 +98,7 @@ describe SSLTest do
86
98
  it "returns undetermined state on unhandled error" do
87
99
  valid, error, cert = SSLTest.test("https://pijoinlrfgind.com")
88
100
  expect(error).to include("SSL certificate test failed: Failed to open TCP connection to pijoinlrfgind.com:443")
89
- expect(error).to include(/name.*not known/i)
101
+ expect(error).to match(/name.*not known/i)
90
102
  expect(valid).to be_nil
91
103
  expect(cert).to be_nil
92
104
  end
@@ -94,13 +106,13 @@ describe SSLTest do
94
106
  it "stops on timeouts" do
95
107
  valid, error, cert = SSLTest.test("https://updown.io", open_timeout: 0)
96
108
  expect(error).to include("SSL certificate test failed")
97
- expect(error).to include(/timeout/i)
109
+ expect(error).to match(/timeout/i)
98
110
  expect(valid).to be_nil
99
111
  expect(cert).to be_nil
100
112
  end
101
113
 
102
114
  it "reports revocation exceptions" do
103
- expect(SSLTest).to receive(:follow_ocsp_redirects).and_raise(ArgumentError.new("test"))
115
+ expect(SSLTest).to receive(:follow_crl_redirects).and_raise(ArgumentError.new("test"))
104
116
  valid, error, cert = SSLTest.test("https://digicert.com")
105
117
  expect(error).to eq ("SSL certificate test failed: test")
106
118
  expect(valid).to be_nil
@@ -108,45 +120,48 @@ describe SSLTest do
108
120
  end
109
121
 
110
122
  it "returns error on revoked cert (OCSP)" do
123
+ # CRL is tried first; disable it so OCSP performs the revocation check
124
+ expect(SSLTest).to receive(:test_crl_revocation).once.and_return([false, "skip CRL", nil])
111
125
  expect(SSLTest).to receive(:follow_ocsp_redirects).once.and_call_original
112
- expect(SSLTest).not_to receive(:follow_crl_redirects)
113
126
  valid, error, cert = SSLTest.test("https://revoked-rsa-dv.ssl.com/")
114
- expect(error).to eq ("SSL certificate revoked: The certificate was revoked for an unknown reason (revocation date: 2025-06-09 15:07:39 UTC)")
127
+ expect(error).to eq ("SSL certificate revoked: The certificate was revoked for an unspecified reason (revocation date: 2026-06-09 14:37:38 UTC)")
115
128
  expect(valid).to eq(false)
116
129
  expect(cert).to be_a OpenSSL::X509::Certificate
117
130
  end
118
131
 
119
- it "returns error on revoked cert (CRL)" do
120
- expect(SSLTest).to receive(:test_ocsp_revocation).once.and_return([false, "skip OCSP", nil])
132
+ it "returns error on revoked cert (CRL)", :retry => 5 do
133
+ # CRL is tried first and detects the revocation, so OCSP is never used
121
134
  expect(SSLTest).to receive(:follow_crl_redirects).once.and_call_original
135
+ expect(SSLTest).not_to receive(:test_ocsp_revocation)
122
136
  valid, error, cert = SSLTest.test("https://revoked.badssl.com/")
123
- expect(error).to eq ("SSL certificate revoked: Key Compromise (revocation date: 2025-11-04 21:01:29 UTC)")
137
+ expect(error).to eq ("SSL certificate revoked: Key Compromise (revocation date: 2026-05-12 21:01:31 UTC)")
124
138
  expect(valid).to eq(false)
125
139
  expect(cert).to be_a OpenSSL::X509::Certificate
126
140
  end
127
141
 
128
142
  it "stops following redirection after the limit for the revoked certs check" do
129
143
  valid, error, cert = SSLTest.test("https://github.com/", redirection_limit: 0)
130
- expect(error).to include("Revocation test couldn't be performed: OCSP: Request failed")
144
+ expect(error).to include("Revocation test couldn't be performed: CRL: Missing crlDistributionPoints extension")
145
+ expect(error).to include("OCSP: Request failed")
131
146
  expect(error).to include("Too many redirections (> 0)")
132
147
  expect(valid).to eq(true)
133
148
  expect(cert).to be_a OpenSSL::X509::Certificate
134
149
  end
135
150
 
136
151
  it "warns when the OCSP URI is missing" do
137
- # Disable CRL fallback to see error message
138
- expect(SSLTest).to receive(:test_crl_revocation).once.and_return([false, "skip CRL", nil])
152
+ # Disable CRL (tried first) to see the OCSP error message
153
+ expect(SSLTest).to receive(:test_crl_revocation).twice.and_return([false, "skip CRL", nil])
139
154
  expect(SSLTest).to receive(:follow_ocsp_redirects).once.and_call_original
140
155
  valid, error, cert = SSLTest.test("https://google.com")
141
- expect(error).to eq ("Revocation test couldn't be performed: OCSP: Missing OCSP URI in authorityInfoAccess extension, CRL: skip CRL")
156
+ expect(error).to eq ("Revocation test couldn't be performed: CRL: skip CRL, OCSP: Missing OCSP URI in authorityInfoAccess extension")
142
157
  expect(valid).to eq(true)
143
158
  expect(cert).to be_a OpenSSL::X509::Certificate
144
159
  end
145
160
 
146
161
  it "works with CRL only" do
147
- # Disable OCSP
148
- expect(SSLTest).to receive(:test_ocsp_revocation).twice.and_return([false, "skip OCSP", nil])
162
+ # CRL is tried first and succeeds for both certs, so OCSP is never used
149
163
  expect(SSLTest).to receive(:follow_crl_redirects).twice.and_call_original
164
+ expect(SSLTest).not_to receive(:test_ocsp_revocation)
150
165
  valid, error, cert = SSLTest.test("https://www.demarches-simplifiees.fr")
151
166
  expect(error).to be_nil
152
167
  expect(valid).to eq(true)
@@ -158,15 +173,15 @@ describe SSLTest do
158
173
  expect(SSLTest).to receive(:test_ocsp_revocation).once.and_return([false, "skip OCSP", nil])
159
174
  expect(SSLTest).not_to receive(:follow_crl_redirects)
160
175
  valid, error, cert = SSLTest.test("https://github.com")
161
- expect(error).to eq ("Revocation test couldn't be performed: OCSP: skip OCSP, CRL: Missing crlDistributionPoints extension")
176
+ expect(error).to eq ("Revocation test couldn't be performed: CRL: Missing crlDistributionPoints extension, OCSP: skip OCSP")
162
177
  expect(valid).to eq(true)
163
178
  expect(cert).to be_a OpenSSL::X509::Certificate
164
179
  end
165
180
 
166
- it "works with OCSP for first cert and CRL for intermediate (Google)" do
181
+ it "works with OCSP for first cert and CRL for intermediate (GitHub)" do
167
182
  expect(SSLTest).to receive(:follow_ocsp_redirects).once.and_call_original
168
183
  expect(SSLTest).to receive(:follow_crl_redirects).once.and_call_original
169
- valid, error, cert = SSLTest.test("https://google.com")
184
+ valid, error, cert = SSLTest.test("https://github.com")
170
185
  expect(error).to be_nil
171
186
  expect(valid).to eq(true)
172
187
  expect(cert).to be_a OpenSSL::X509::Certificate
@@ -236,11 +251,11 @@ describe SSLTest do
236
251
  SSLTest.send(:follow_crl_redirects, URI("http://crl.certigna.fr/certigna.crl")) # 1.1k
237
252
  SSLTest.send(:follow_crl_redirects, URI("http://crl3.digicert.com/DigiCertTLSHybridECCSHA3842020CA1-1.crl")) # 26k
238
253
  expect(SSLTest.cache_size[:crl][:lists]).to eq(2)
239
- expect(SSLTest.cache_size[:crl][:bytes]).to be > 6000
254
+ expect(SSLTest.cache_size[:crl][:bytes]).to be > 2000
240
255
  end
241
256
 
242
257
  it "returns OCSP cache size properly" do
243
- SSLTest.test("https://google.com")
258
+ SSLTest.test("https://github.com")
244
259
  expect(SSLTest.cache_size[:ocsp][:responses]).to eq(1)
245
260
  expect(SSLTest.cache_size[:ocsp][:errors]).to eq(0)
246
261
  expect(SSLTest.cache_size[:ocsp][:bytes]).to be > 150
@@ -323,7 +338,7 @@ describe SSLTest do
323
338
  it "reports revocation exceptions" do
324
339
  cert = OpenSSL::X509::Certificate.new(File.read(File.join(__dir__, 'fixtures/digicert_com_client.pem')))
325
340
  ca_bundle = OpenSSL::X509::Certificate.load(File.read(File.join(__dir__, 'fixtures/digicert_com_ca_bundle.pem')))
326
- expect(SSLTest).to receive(:follow_ocsp_redirects).and_raise(ArgumentError.new("test"))
341
+ expect(SSLTest).to receive(:follow_crl_redirects).and_raise(ArgumentError.new("test"))
327
342
  valid, error, cert = SSLTest.test_cert(cert, ca_bundle)
328
343
  expect(error).to eq("SSL certificate test failed: test")
329
344
  expect(valid).to be_nil
@@ -334,8 +349,9 @@ describe SSLTest do
334
349
  cert = OpenSSL::X509::Certificate.new(File.read(File.join(__dir__, 'fixtures/revoked_rsa_dv_client.pem')))
335
350
  ca_bundle = OpenSSL::X509::Certificate.load(File.read(File.join(__dir__, 'fixtures/revoked_rsa_dv_ca_bundle.pem')))
336
351
 
352
+ # CRL is tried first; disable it so OCSP performs the revocation check
353
+ expect(SSLTest).to receive(:test_crl_revocation).once.and_return([false, "skip CRL", nil])
337
354
  expect(SSLTest).to receive(:follow_ocsp_redirects).once.and_call_original
338
- expect(SSLTest).not_to receive(:follow_crl_redirects)
339
355
 
340
356
  valid, error, cert = SSLTest.test_cert(cert, ca_bundle)
341
357
  expect(error).to eq ("SSL certificate revoked: The certificate was revoked for an unknown reason (revocation date: 2025-06-09 15:07:39 UTC)")
@@ -347,10 +363,11 @@ describe SSLTest do
347
363
  cert = OpenSSL::X509::Certificate.new(File.read(File.join(__dir__, 'fixtures/revoked_badssl_client.pem')))
348
364
  ca_bundle = OpenSSL::X509::Certificate.load(File.read(File.join(__dir__, 'fixtures/revoked_badssl_ca_bundle.pem')))
349
365
 
350
- expect(SSLTest).to receive(:test_ocsp_revocation).once.and_return([false, "skip OCSP", nil])
366
+ # CRL is tried first and detects the revocation, so OCSP is never used
351
367
  expect(SSLTest).to receive(:follow_crl_redirects).once.and_call_original
368
+ expect(SSLTest).not_to receive(:test_ocsp_revocation)
352
369
  valid, error, cert = SSLTest.test_cert(cert, ca_bundle)
353
- expect(error).to eq ("SSL certificate revoked: Key Compromise (revocation date: 2025-11-04 21:01:29 UTC)")
370
+ expect(error).to eq ("SSL certificate revoked: Key Compromise (revocation date: 2026-05-12 21:01:31 UTC)")
354
371
  expect(valid).to eq(false)
355
372
  expect(cert).to eq(cert)
356
373
  end
@@ -360,7 +377,8 @@ describe SSLTest do
360
377
  ca_bundle = OpenSSL::X509::Certificate.load(File.read(File.join(__dir__, 'fixtures/www_github_com_ca_bundle.pem')))
361
378
 
362
379
  valid, error, cert = SSLTest.test_cert(cert, ca_bundle, redirection_limit: 0)
363
- expect(error).to include("Revocation test couldn't be performed: OCSP: Request failed")
380
+ expect(error).to include("Revocation test couldn't be performed: CRL: Missing crlDistributionPoints extension")
381
+ expect(error).to include("OCSP: Request failed")
364
382
  expect(error).to include("Too many redirections (> 0)")
365
383
  expect(valid).to eq(true)
366
384
  expect(cert).to eq(cert)
@@ -370,12 +388,12 @@ describe SSLTest do
370
388
  cert = OpenSSL::X509::Certificate.new(File.read(File.join(__dir__, 'fixtures/google_com_client.pem')))
371
389
  ca_bundle = OpenSSL::X509::Certificate.load(File.read(File.join(__dir__, 'fixtures/google_com_ca_bundle.pem')))
372
390
 
373
- # Disable CRL fallback to see error message
374
- expect(SSLTest).to receive(:test_crl_revocation).once.and_return([false, "skip CRL", nil])
391
+ # Disable CRL (tried first) to see the OCSP error message
392
+ expect(SSLTest).to receive(:test_crl_revocation).twice.and_return([false, "skip CRL", nil])
375
393
  expect(SSLTest).to receive(:follow_ocsp_redirects).once.and_call_original
376
394
 
377
395
  valid, error, cert = SSLTest.test_cert(cert, ca_bundle)
378
- expect(error).to eq ("Revocation test couldn't be performed: OCSP: Missing OCSP URI in authorityInfoAccess extension, CRL: skip CRL")
396
+ expect(error).to eq ("Revocation test couldn't be performed: CRL: skip CRL, OCSP: Missing OCSP URI in authorityInfoAccess extension")
379
397
  expect(valid).to eq(true)
380
398
  expect(cert).to eq(cert)
381
399
  end
@@ -384,9 +402,9 @@ describe SSLTest do
384
402
  cert = OpenSSL::X509::Certificate.new(File.read(File.join(__dir__, 'fixtures/www_demarches-simplifiees_fr_client.pem')))
385
403
  ca_bundle = OpenSSL::X509::Certificate.load(File.read(File.join(__dir__, 'fixtures/www_demarches-simplifiees_fr_ca_bundle.pem')))
386
404
 
387
- # Disable OCSP
388
- expect(SSLTest).to receive(:test_ocsp_revocation).twice.and_return([false, "skip OCSP", nil])
405
+ # CRL is tried first and succeeds for both certs, so OCSP is never used
389
406
  expect(SSLTest).to receive(:follow_crl_redirects).twice.and_call_original
407
+ expect(SSLTest).not_to receive(:test_ocsp_revocation)
390
408
 
391
409
  valid, error, cert = SSLTest.test_cert(cert, ca_bundle)
392
410
  expect(error).to be_nil
@@ -403,18 +421,18 @@ describe SSLTest do
403
421
  expect(SSLTest).not_to receive(:follow_crl_redirects)
404
422
 
405
423
  valid, error, cert = SSLTest.test_cert(cert, ca_bundle)
406
- expect(error).to eq ("Revocation test couldn't be performed: OCSP: skip OCSP, CRL: Missing crlDistributionPoints extension")
424
+ expect(error).to eq ("Revocation test couldn't be performed: CRL: Missing crlDistributionPoints extension, OCSP: skip OCSP")
407
425
  expect(valid).to eq(true)
408
426
  expect(cert).to eq(cert)
409
427
 
410
428
  end
411
429
 
412
- it "works with OCSP for first cert and CRL for intermediate (Google)" do
430
+ it "works with OCSP for first cert and CRL for intermediate (GitHub)" do
413
431
  expect(SSLTest).to receive(:follow_ocsp_redirects).once.and_call_original
414
432
  expect(SSLTest).to receive(:follow_crl_redirects).once.and_call_original
415
433
 
416
- cert = OpenSSL::X509::Certificate.new(File.read(File.join(__dir__, 'fixtures/google_com_client.pem')))
417
- ca_bundle = OpenSSL::X509::Certificate.load(File.read(File.join(__dir__, 'fixtures/google_com_ca_bundle.pem')))
434
+ cert = OpenSSL::X509::Certificate.new(File.read(File.join(__dir__, 'fixtures/www_github_com_client.pem')))
435
+ ca_bundle = OpenSSL::X509::Certificate.load(File.read(File.join(__dir__, 'fixtures/www_github_com_ca_bundle.pem')))
418
436
 
419
437
  valid, error, cert = SSLTest.test_cert(cert, ca_bundle)
420
438
  expect(error).to be_nil
@@ -453,7 +471,8 @@ describe SSLTest do
453
471
  expect(valid).to eq(true)
454
472
  expect(cert).to eq(cert)
455
473
 
456
- expect($proxy).to have_received(:do_GET).once
474
+ # CRL is tried first, so both certs are checked via CRL (GET) through the proxy
475
+ expect($proxy).to have_received(:do_GET).twice
457
476
  end
458
477
  end
459
478
 
data/ssl-test.gemspec CHANGED
@@ -20,5 +20,6 @@ Gem::Specification.new do |spec|
20
20
  spec.add_development_dependency "bundler", ">= 1.7"
21
21
  spec.add_development_dependency "rake"
22
22
  spec.add_development_dependency "rspec"
23
+ spec.add_development_dependency "rspec-retry"
23
24
  spec.add_development_dependency "webrick"
24
25
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ssl-test
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adrien Rey-Jarthon
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-11-28 00:00:00.000000000 Z
10
+ date: 2026-06-16 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: bundler
@@ -51,6 +51,20 @@ dependencies:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
53
  version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rspec-retry
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
54
68
  - !ruby/object:Gem::Dependency
55
69
  name: webrick
56
70
  requirement: !ruby/object:Gem::Requirement