ssl-test 1.4.0 → 1.5.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.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +8 -0
- data/.github/workflows/ruby.yml +16 -0
- data/README.md +43 -10
- data/lib/ssl-test/crl.rb +3 -3
- data/lib/ssl-test/ocsp.rb +3 -3
- data/lib/ssl-test.rb +68 -22
- data/spec/fixtures/digicert_com_ca_bundle.pem +94 -0
- data/spec/fixtures/digicert_com_client.pem +40 -0
- data/spec/fixtures/expired_cert_ca_bundle.pem +100 -0
- data/spec/fixtures/expired_cert_client.pem +31 -0
- data/spec/fixtures/google_com_ca_bundle.pem +139 -0
- data/spec/fixtures/google_com_client.pem +79 -0
- data/spec/fixtures/incomplete_chain_ca_bundle.pem +29 -0
- data/spec/fixtures/incomplete_chain_client.pem +29 -0
- data/spec/fixtures/revoked_badssl_ca_bundle.pem +81 -0
- data/spec/fixtures/revoked_badssl_client.pem +22 -0
- data/spec/fixtures/revoked_rsa_dv_ca_bundle.pem +114 -0
- data/spec/fixtures/revoked_rsa_dv_client.pem +41 -0
- data/spec/fixtures/self_signed_ca_bundle.pem +21 -0
- data/spec/fixtures/self_signed_client.pem +21 -0
- data/spec/fixtures/www_demarches-simplifiees_fr_ca_bundle.pem +108 -0
- data/spec/fixtures/www_demarches-simplifiees_fr_client.pem +52 -0
- data/spec/fixtures/www_github_com_ca_bundle.pem +67 -0
- data/spec/fixtures/www_github_com_client.pem +27 -0
- data/spec/fixtures/www_mycs_com_ca_bundle.pem +79 -0
- data/spec/fixtures/www_mycs_com_client.pem +33 -0
- data/spec/ssl-test_spec.rb +283 -36
- data/ssl-test.gemspec +1 -0
- metadata +59 -8
- data/.travis.yml +0 -5
data/spec/ssl-test_spec.rb
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
require "ssl-test"
|
|
2
2
|
require "benchmark"
|
|
3
|
+
require 'webrick'
|
|
4
|
+
require 'webrick/httpproxy'
|
|
3
5
|
|
|
4
6
|
# Uncomment for debug logging:
|
|
5
7
|
# require "logger"
|
|
6
8
|
# SSLTest.logger = Logger.new(STDOUT)
|
|
7
9
|
|
|
8
10
|
describe SSLTest do
|
|
9
|
-
|
|
11
|
+
before { SSLTest.flush_cache }
|
|
12
|
+
|
|
13
|
+
let(:proxy_thread) { nil }
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
after(:each) { proxy_thread&.kill }
|
|
17
|
+
|
|
18
|
+
describe '.test_url' do
|
|
10
19
|
it "returns no error on valid SNI website" do
|
|
11
20
|
valid, error, cert = SSLTest.test("https://www.mycs.com")
|
|
12
21
|
expect(error).to be_nil
|
|
@@ -15,19 +24,22 @@ describe SSLTest do
|
|
|
15
24
|
end
|
|
16
25
|
|
|
17
26
|
it "returns no error on valid SAN" do
|
|
18
|
-
|
|
27
|
+
# CN is updown.io, www.updown.io is an Alternative Name
|
|
28
|
+
valid, error, cert = SSLTest.test("https://www.updown.io/")
|
|
19
29
|
expect(error).to be_nil
|
|
20
30
|
expect(valid).to eq(true)
|
|
21
31
|
expect(cert).to be_a OpenSSL::X509::Certificate
|
|
22
32
|
end
|
|
23
33
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
# Disabled: unlikely to be repaired anytime soon: https://github.com/chromium/badssl.com/issues/447
|
|
35
|
+
# Couldn't find a good alternative
|
|
36
|
+
# it "returns no error when no CN" do
|
|
37
|
+
# pending "Expired for the moment https://github.com/chromium/badssl.com/issues/447"
|
|
38
|
+
# valid, error, cert = SSLTest.test("https://no-common-name.badssl.com/")
|
|
39
|
+
# expect(error).to be_nil
|
|
40
|
+
# expect(valid).to eq(true)
|
|
41
|
+
# expect(cert).to be_a OpenSSL::X509::Certificate
|
|
42
|
+
# end
|
|
31
43
|
|
|
32
44
|
it "works with websites blocking http requests" do
|
|
33
45
|
valid, error, cert = SSLTest.test("https://obyava.ua")
|
|
@@ -38,7 +50,7 @@ describe SSLTest do
|
|
|
38
50
|
|
|
39
51
|
it "returns error on self signed certificate" do
|
|
40
52
|
valid, error, cert = SSLTest.test("https://self-signed.badssl.com/")
|
|
41
|
-
expect(error).to eq ("error code 18: self
|
|
53
|
+
expect(error).to eq ("error code 18: self-signed certificate")
|
|
42
54
|
expect(valid).to eq(false)
|
|
43
55
|
expect(cert).to be_a OpenSSL::X509::Certificate
|
|
44
56
|
end
|
|
@@ -52,14 +64,14 @@ describe SSLTest do
|
|
|
52
64
|
|
|
53
65
|
it "returns error on untrusted root" do
|
|
54
66
|
valid, error, cert = SSLTest.test("https://untrusted-root.badssl.com/")
|
|
55
|
-
expect(error).to eq ("error code 19: self
|
|
67
|
+
expect(error).to eq ("error code 19: self-signed certificate in certificate chain")
|
|
56
68
|
expect(valid).to eq(false)
|
|
57
69
|
expect(cert).to be_a OpenSSL::X509::Certificate
|
|
58
70
|
end
|
|
59
71
|
|
|
60
72
|
it "returns error on invalid host" do
|
|
61
73
|
valid, error, cert = SSLTest.test("https://wrong.host.badssl.com/")
|
|
62
|
-
expect(error).to include('
|
|
74
|
+
expect(error).to include('error code 62: hostname mismatch')
|
|
63
75
|
expect(valid).to eq(false)
|
|
64
76
|
expect(cert).to be_a OpenSSL::X509::Certificate
|
|
65
77
|
end
|
|
@@ -73,21 +85,23 @@ describe SSLTest do
|
|
|
73
85
|
|
|
74
86
|
it "returns undetermined state on unhandled error" do
|
|
75
87
|
valid, error, cert = SSLTest.test("https://pijoinlrfgind.com")
|
|
76
|
-
expect(error).to
|
|
88
|
+
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)
|
|
77
90
|
expect(valid).to be_nil
|
|
78
91
|
expect(cert).to be_nil
|
|
79
92
|
end
|
|
80
93
|
|
|
81
94
|
it "stops on timeouts" do
|
|
82
95
|
valid, error, cert = SSLTest.test("https://updown.io", open_timeout: 0)
|
|
83
|
-
expect(error).to
|
|
96
|
+
expect(error).to include("SSL certificate test failed")
|
|
97
|
+
expect(error).to include(/timeout/i)
|
|
84
98
|
expect(valid).to be_nil
|
|
85
99
|
expect(cert).to be_nil
|
|
86
100
|
end
|
|
87
101
|
|
|
88
102
|
it "reports revocation exceptions" do
|
|
89
103
|
expect(SSLTest).to receive(:follow_ocsp_redirects).and_raise(ArgumentError.new("test"))
|
|
90
|
-
valid, error, cert = SSLTest.test("https://
|
|
104
|
+
valid, error, cert = SSLTest.test("https://digicert.com")
|
|
91
105
|
expect(error).to eq ("SSL certificate test failed: test")
|
|
92
106
|
expect(valid).to be_nil
|
|
93
107
|
expect(cert).to be_a OpenSSL::X509::Certificate
|
|
@@ -96,8 +110,8 @@ describe SSLTest do
|
|
|
96
110
|
it "returns error on revoked cert (OCSP)" do
|
|
97
111
|
expect(SSLTest).to receive(:follow_ocsp_redirects).once.and_call_original
|
|
98
112
|
expect(SSLTest).not_to receive(:follow_crl_redirects)
|
|
99
|
-
valid, error, cert = SSLTest.test("https://revoked.
|
|
100
|
-
expect(error).to eq ("SSL certificate revoked: The certificate was revoked for an unknown reason (revocation date:
|
|
113
|
+
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)")
|
|
101
115
|
expect(valid).to eq(false)
|
|
102
116
|
expect(cert).to be_a OpenSSL::X509::Certificate
|
|
103
117
|
end
|
|
@@ -106,14 +120,15 @@ describe SSLTest do
|
|
|
106
120
|
expect(SSLTest).to receive(:test_ocsp_revocation).once.and_return([false, "skip OCSP", nil])
|
|
107
121
|
expect(SSLTest).to receive(:follow_crl_redirects).once.and_call_original
|
|
108
122
|
valid, error, cert = SSLTest.test("https://revoked.badssl.com/")
|
|
109
|
-
expect(error).to eq ("SSL certificate revoked:
|
|
123
|
+
expect(error).to eq ("SSL certificate revoked: Key Compromise (revocation date: 2025-11-04 21:01:29 UTC)")
|
|
110
124
|
expect(valid).to eq(false)
|
|
111
125
|
expect(cert).to be_a OpenSSL::X509::Certificate
|
|
112
126
|
end
|
|
113
127
|
|
|
114
128
|
it "stops following redirection after the limit for the revoked certs check" do
|
|
115
129
|
valid, error, cert = SSLTest.test("https://github.com/", redirection_limit: 0)
|
|
116
|
-
expect(error).to
|
|
130
|
+
expect(error).to include("Revocation test couldn't be performed: OCSP: Request failed")
|
|
131
|
+
expect(error).to include("Too many redirections (> 0)")
|
|
117
132
|
expect(valid).to eq(true)
|
|
118
133
|
expect(cert).to be_a OpenSSL::X509::Certificate
|
|
119
134
|
end
|
|
@@ -122,7 +137,7 @@ describe SSLTest do
|
|
|
122
137
|
# Disable CRL fallback to see error message
|
|
123
138
|
expect(SSLTest).to receive(:test_crl_revocation).once.and_return([false, "skip CRL", nil])
|
|
124
139
|
expect(SSLTest).to receive(:follow_ocsp_redirects).once.and_call_original
|
|
125
|
-
valid, error, cert = SSLTest.test("https://
|
|
140
|
+
valid, error, cert = SSLTest.test("https://google.com")
|
|
126
141
|
expect(error).to eq ("Revocation test couldn't be performed: OCSP: Missing OCSP URI in authorityInfoAccess extension, CRL: skip CRL")
|
|
127
142
|
expect(valid).to eq(true)
|
|
128
143
|
expect(cert).to be_a OpenSSL::X509::Certificate
|
|
@@ -142,30 +157,69 @@ describe SSLTest do
|
|
|
142
157
|
# Disable OCSP to see error message
|
|
143
158
|
expect(SSLTest).to receive(:test_ocsp_revocation).once.and_return([false, "skip OCSP", nil])
|
|
144
159
|
expect(SSLTest).not_to receive(:follow_crl_redirects)
|
|
145
|
-
valid, error, cert = SSLTest.test("https://
|
|
160
|
+
valid, error, cert = SSLTest.test("https://github.com")
|
|
146
161
|
expect(error).to eq ("Revocation test couldn't be performed: OCSP: skip OCSP, CRL: Missing crlDistributionPoints extension")
|
|
147
162
|
expect(valid).to eq(true)
|
|
148
163
|
expect(cert).to be_a OpenSSL::X509::Certificate
|
|
149
164
|
end
|
|
150
165
|
|
|
151
|
-
it "works with OCSP for first cert and CRL for intermediate (
|
|
166
|
+
it "works with OCSP for first cert and CRL for intermediate (Google)" do
|
|
152
167
|
expect(SSLTest).to receive(:follow_ocsp_redirects).once.and_call_original
|
|
153
168
|
expect(SSLTest).to receive(:follow_crl_redirects).once.and_call_original
|
|
154
|
-
valid, error, cert = SSLTest.test("https://
|
|
169
|
+
valid, error, cert = SSLTest.test("https://google.com")
|
|
155
170
|
expect(error).to be_nil
|
|
156
171
|
expect(valid).to eq(true)
|
|
157
172
|
expect(cert).to be_a OpenSSL::X509::Certificate
|
|
173
|
+
# make sure both were used
|
|
174
|
+
expect(SSLTest.cache_size).to match({
|
|
175
|
+
crl: hash_including(lists: 1),
|
|
176
|
+
ocsp: hash_including(responses: 1, errors: 0)
|
|
177
|
+
})
|
|
158
178
|
end
|
|
159
179
|
|
|
160
|
-
it "
|
|
161
|
-
|
|
162
|
-
expect(SSLTest).to receive(:follow_crl_redirects).once.and_call_original
|
|
163
|
-
# Similar chain: https://www.demarches-simplifiees.fr
|
|
164
|
-
valid, error, cert = SSLTest.test("https://www.anonymisation.gov.pf")
|
|
180
|
+
it "accepts tcps scheme" do
|
|
181
|
+
valid, error, cert = SSLTest.test("tcps://updown.io:443")
|
|
165
182
|
expect(error).to be_nil
|
|
166
183
|
expect(valid).to eq(true)
|
|
167
184
|
expect(cert).to be_a OpenSSL::X509::Certificate
|
|
168
185
|
end
|
|
186
|
+
|
|
187
|
+
context 'when specifying a proxy' do
|
|
188
|
+
context 'when the proxy is active' do
|
|
189
|
+
let(:proxy_thread) do
|
|
190
|
+
thread = Thread.new do
|
|
191
|
+
dev_null = WEBrick::Log::new("/dev/null", 7)
|
|
192
|
+
$proxy = WEBrick::HTTPProxyServer.new Port: 8080, :Logger => dev_null, :AccessLog => []
|
|
193
|
+
$proxy.start
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
sleep 0.1 # wait for the proxy to start!
|
|
197
|
+
allow($proxy).to receive(:do_GET).and_call_original
|
|
198
|
+
|
|
199
|
+
thread
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
it 'uses the provided http proxy' do
|
|
203
|
+
proxy_thread
|
|
204
|
+
|
|
205
|
+
valid, error, cert = SSLTest.test("https://updown.io", proxy_host: '127.0.0.1', proxy_port: 8080)
|
|
206
|
+
expect(error).to be_nil
|
|
207
|
+
expect(valid).to eq(true)
|
|
208
|
+
expect(cert).to be_a OpenSSL::X509::Certificate
|
|
209
|
+
|
|
210
|
+
expect($proxy).to have_received(:do_GET).twice
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
context 'when the proxy is not reachable' do
|
|
215
|
+
it 'returns a http error' do
|
|
216
|
+
valid, error, cert = SSLTest.test("https://updown.io", proxy_host: '127.0.0.1', proxy_port: 55000)
|
|
217
|
+
expect(error).to include('(Connection refused - connect(2) for "127.0.0.1" port 55000)')
|
|
218
|
+
expect(valid).to be_nil
|
|
219
|
+
expect(cert).to be_nil
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
169
223
|
end
|
|
170
224
|
|
|
171
225
|
describe '.cache_size' do
|
|
@@ -179,17 +233,19 @@ describe SSLTest do
|
|
|
179
233
|
end
|
|
180
234
|
|
|
181
235
|
it "returns CRL cache size properly" do
|
|
182
|
-
SSLTest.send(:follow_crl_redirects, URI("http://crl.certigna.fr/certigna.crl")) # 1.
|
|
183
|
-
SSLTest.send(:follow_crl_redirects, URI("http://crl3.digicert.com/
|
|
236
|
+
SSLTest.send(:follow_crl_redirects, URI("http://crl.certigna.fr/certigna.crl")) # 1.1k
|
|
237
|
+
SSLTest.send(:follow_crl_redirects, URI("http://crl3.digicert.com/DigiCertTLSHybridECCSHA3842020CA1-1.crl")) # 26k
|
|
184
238
|
expect(SSLTest.cache_size[:crl][:lists]).to eq(2)
|
|
185
|
-
expect(SSLTest.cache_size[:crl][:bytes]).to be >
|
|
239
|
+
expect(SSLTest.cache_size[:crl][:bytes]).to be > 6000
|
|
186
240
|
end
|
|
187
241
|
|
|
188
242
|
it "returns OCSP cache size properly" do
|
|
189
|
-
SSLTest.test("https://
|
|
190
|
-
expect(SSLTest.cache_size[:ocsp][:responses]).to eq(
|
|
243
|
+
SSLTest.test("https://google.com")
|
|
244
|
+
expect(SSLTest.cache_size[:ocsp][:responses]).to eq(1)
|
|
191
245
|
expect(SSLTest.cache_size[:ocsp][:errors]).to eq(0)
|
|
192
|
-
expect(SSLTest.cache_size[:ocsp][:bytes]).to be >
|
|
246
|
+
expect(SSLTest.cache_size[:ocsp][:bytes]).to be > 150
|
|
247
|
+
expect(SSLTest.cache_size[:crl][:lists]).to eq(1)
|
|
248
|
+
expect(SSLTest.cache_size[:crl][:bytes]).to be > 500
|
|
193
249
|
end
|
|
194
250
|
end
|
|
195
251
|
|
|
@@ -199,7 +255,7 @@ describe SSLTest do
|
|
|
199
255
|
it "fetch CRL list and updates cache" do
|
|
200
256
|
uri = URI("http://crl.certigna.fr/certigna.crl")
|
|
201
257
|
body, error = SSLTest.send(:follow_crl_redirects, uri)
|
|
202
|
-
expect(body.bytesize).to equal
|
|
258
|
+
expect(body.bytesize).to equal 1417
|
|
203
259
|
expect(error).to be_nil
|
|
204
260
|
|
|
205
261
|
# Check cache status
|
|
@@ -223,4 +279,195 @@ describe SSLTest do
|
|
|
223
279
|
expect(body2).to be(body) # but we're still using cache because it's a 304
|
|
224
280
|
end
|
|
225
281
|
end
|
|
226
|
-
|
|
282
|
+
|
|
283
|
+
describe '.test_cert' do
|
|
284
|
+
it "returns no error on valid SNI website" do
|
|
285
|
+
cert = OpenSSL::X509::Certificate.new(File.read(File.join(__dir__, 'fixtures/www_mycs_com_client.pem')))
|
|
286
|
+
ca_bundle = OpenSSL::X509::Certificate.load(File.read(File.join(__dir__, 'fixtures/www_mycs_com_ca_bundle.pem')))
|
|
287
|
+
|
|
288
|
+
valid, error, cert = SSLTest.test_cert(cert, ca_bundle)
|
|
289
|
+
expect(error).to be_nil
|
|
290
|
+
expect(valid).to eq(true)
|
|
291
|
+
expect(cert).to eq(cert)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
it "returns no error on self signed certificates" do
|
|
295
|
+
cert = OpenSSL::X509::Certificate.new(File.read(File.join(__dir__, 'fixtures/self_signed_client.pem')))
|
|
296
|
+
ca_bundle = OpenSSL::X509::Certificate.load(File.read(File.join(__dir__, 'fixtures/self_signed_ca_bundle.pem')))
|
|
297
|
+
|
|
298
|
+
valid, error, cert = SSLTest.test_cert(cert, ca_bundle)
|
|
299
|
+
expect(error).to be_nil
|
|
300
|
+
expect(valid).to eq(true)
|
|
301
|
+
expect(cert).to eq(cert)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
it "returns error on expired cert" do
|
|
305
|
+
cert = OpenSSL::X509::Certificate.new(File.read(File.join(__dir__, 'fixtures/expired_cert_client.pem')))
|
|
306
|
+
ca_bundle = OpenSSL::X509::Certificate.load(File.read(File.join(__dir__, 'fixtures/expired_cert_ca_bundle.pem')))
|
|
307
|
+
|
|
308
|
+
valid, error, cert = SSLTest.test_cert(cert, ca_bundle)
|
|
309
|
+
expect(error).to eq ("error code 10: certificate has expired")
|
|
310
|
+
expect(valid).to eq(false)
|
|
311
|
+
expect(cert).to eq(cert)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
it "returns error on incomplete chain" do
|
|
315
|
+
cert = OpenSSL::X509::Certificate.new(File.read(File.join(__dir__, 'fixtures/incomplete_chain_client.pem')))
|
|
316
|
+
ca_bundle = OpenSSL::X509::Certificate.load(File.read(File.join(__dir__, 'fixtures/incomplete_chain_ca_bundle.pem')))
|
|
317
|
+
valid, error, cert = SSLTest.test_cert(cert, ca_bundle)
|
|
318
|
+
expect(error).to eq ("error code 20: unable to get local issuer certificate")
|
|
319
|
+
expect(valid).to eq(false)
|
|
320
|
+
expect(cert).to eq(cert)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
it "reports revocation exceptions" do
|
|
324
|
+
cert = OpenSSL::X509::Certificate.new(File.read(File.join(__dir__, 'fixtures/digicert_com_client.pem')))
|
|
325
|
+
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"))
|
|
327
|
+
valid, error, cert = SSLTest.test_cert(cert, ca_bundle)
|
|
328
|
+
expect(error).to eq("SSL certificate test failed: test")
|
|
329
|
+
expect(valid).to be_nil
|
|
330
|
+
expect(cert).to eq(cert)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
it "returns error on revoked cert (OCSP)" do
|
|
334
|
+
cert = OpenSSL::X509::Certificate.new(File.read(File.join(__dir__, 'fixtures/revoked_rsa_dv_client.pem')))
|
|
335
|
+
ca_bundle = OpenSSL::X509::Certificate.load(File.read(File.join(__dir__, 'fixtures/revoked_rsa_dv_ca_bundle.pem')))
|
|
336
|
+
|
|
337
|
+
expect(SSLTest).to receive(:follow_ocsp_redirects).once.and_call_original
|
|
338
|
+
expect(SSLTest).not_to receive(:follow_crl_redirects)
|
|
339
|
+
|
|
340
|
+
valid, error, cert = SSLTest.test_cert(cert, ca_bundle)
|
|
341
|
+
expect(error).to eq ("SSL certificate revoked: The certificate was revoked for an unknown reason (revocation date: 2025-06-09 15:07:39 UTC)")
|
|
342
|
+
expect(valid).to eq(false)
|
|
343
|
+
expect(cert).to eq(cert)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
it "returns error on revoked cert (CRL)" do
|
|
347
|
+
cert = OpenSSL::X509::Certificate.new(File.read(File.join(__dir__, 'fixtures/revoked_badssl_client.pem')))
|
|
348
|
+
ca_bundle = OpenSSL::X509::Certificate.load(File.read(File.join(__dir__, 'fixtures/revoked_badssl_ca_bundle.pem')))
|
|
349
|
+
|
|
350
|
+
expect(SSLTest).to receive(:test_ocsp_revocation).once.and_return([false, "skip OCSP", nil])
|
|
351
|
+
expect(SSLTest).to receive(:follow_crl_redirects).once.and_call_original
|
|
352
|
+
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)")
|
|
354
|
+
expect(valid).to eq(false)
|
|
355
|
+
expect(cert).to eq(cert)
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
it "stops following redirection after the limit for the revoked certs check" do
|
|
359
|
+
cert = OpenSSL::X509::Certificate.new(File.read(File.join(__dir__, 'fixtures/www_github_com_client.pem')))
|
|
360
|
+
ca_bundle = OpenSSL::X509::Certificate.load(File.read(File.join(__dir__, 'fixtures/www_github_com_ca_bundle.pem')))
|
|
361
|
+
|
|
362
|
+
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")
|
|
364
|
+
expect(error).to include("Too many redirections (> 0)")
|
|
365
|
+
expect(valid).to eq(true)
|
|
366
|
+
expect(cert).to eq(cert)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
it "warns when the OCSP URI is missing" do
|
|
370
|
+
cert = OpenSSL::X509::Certificate.new(File.read(File.join(__dir__, 'fixtures/google_com_client.pem')))
|
|
371
|
+
ca_bundle = OpenSSL::X509::Certificate.load(File.read(File.join(__dir__, 'fixtures/google_com_ca_bundle.pem')))
|
|
372
|
+
|
|
373
|
+
# Disable CRL fallback to see error message
|
|
374
|
+
expect(SSLTest).to receive(:test_crl_revocation).once.and_return([false, "skip CRL", nil])
|
|
375
|
+
expect(SSLTest).to receive(:follow_ocsp_redirects).once.and_call_original
|
|
376
|
+
|
|
377
|
+
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")
|
|
379
|
+
expect(valid).to eq(true)
|
|
380
|
+
expect(cert).to eq(cert)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
it "works with CRL only" do
|
|
384
|
+
cert = OpenSSL::X509::Certificate.new(File.read(File.join(__dir__, 'fixtures/www_demarches-simplifiees_fr_client.pem')))
|
|
385
|
+
ca_bundle = OpenSSL::X509::Certificate.load(File.read(File.join(__dir__, 'fixtures/www_demarches-simplifiees_fr_ca_bundle.pem')))
|
|
386
|
+
|
|
387
|
+
# Disable OCSP
|
|
388
|
+
expect(SSLTest).to receive(:test_ocsp_revocation).twice.and_return([false, "skip OCSP", nil])
|
|
389
|
+
expect(SSLTest).to receive(:follow_crl_redirects).twice.and_call_original
|
|
390
|
+
|
|
391
|
+
valid, error, cert = SSLTest.test_cert(cert, ca_bundle)
|
|
392
|
+
expect(error).to be_nil
|
|
393
|
+
expect(valid).to eq(true)
|
|
394
|
+
expect(cert).to eq(cert)
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
it "warns when the CRL URI is missing" do
|
|
398
|
+
cert = OpenSSL::X509::Certificate.new(File.read(File.join(__dir__, 'fixtures/www_github_com_client.pem')))
|
|
399
|
+
ca_bundle = OpenSSL::X509::Certificate.load(File.read(File.join(__dir__, 'fixtures/www_github_com_ca_bundle.pem')))
|
|
400
|
+
|
|
401
|
+
# Disable OCSP to see error message
|
|
402
|
+
expect(SSLTest).to receive(:test_ocsp_revocation).once.and_return([false, "skip OCSP", nil])
|
|
403
|
+
expect(SSLTest).not_to receive(:follow_crl_redirects)
|
|
404
|
+
|
|
405
|
+
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")
|
|
407
|
+
expect(valid).to eq(true)
|
|
408
|
+
expect(cert).to eq(cert)
|
|
409
|
+
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
it "works with OCSP for first cert and CRL for intermediate (Google)" do
|
|
413
|
+
expect(SSLTest).to receive(:follow_ocsp_redirects).once.and_call_original
|
|
414
|
+
expect(SSLTest).to receive(:follow_crl_redirects).once.and_call_original
|
|
415
|
+
|
|
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')))
|
|
418
|
+
|
|
419
|
+
valid, error, cert = SSLTest.test_cert(cert, ca_bundle)
|
|
420
|
+
expect(error).to be_nil
|
|
421
|
+
expect(valid).to eq(true)
|
|
422
|
+
expect(cert).to eq(cert)
|
|
423
|
+
# make sure both were used
|
|
424
|
+
expect(SSLTest.cache_size).to match({
|
|
425
|
+
crl: hash_including(lists: 1),
|
|
426
|
+
ocsp: hash_including(responses: 1, errors: 0)
|
|
427
|
+
})
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
context 'when specifying a proxy' do
|
|
431
|
+
context 'when the proxy is active' do
|
|
432
|
+
let(:proxy_thread) do
|
|
433
|
+
thread = Thread.new do
|
|
434
|
+
dev_null = WEBrick::Log::new("/dev/null", 7)
|
|
435
|
+
$proxy = WEBrick::HTTPProxyServer.new Port: 8080, :Logger => dev_null, :AccessLog => []
|
|
436
|
+
$proxy.start
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
sleep 0.1 # wait for the proxy to start!
|
|
440
|
+
allow($proxy).to receive(:do_GET).and_call_original
|
|
441
|
+
|
|
442
|
+
thread
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
it 'uses the provided http proxy' do
|
|
446
|
+
proxy_thread
|
|
447
|
+
|
|
448
|
+
cert = OpenSSL::X509::Certificate.new(File.read(File.join(__dir__, 'fixtures/google_com_client.pem')))
|
|
449
|
+
ca_bundle = OpenSSL::X509::Certificate.load(File.read(File.join(__dir__, 'fixtures/google_com_ca_bundle.pem')))
|
|
450
|
+
|
|
451
|
+
valid, error, cert = SSLTest.test_cert(cert, ca_bundle, proxy_host: '127.0.0.1', proxy_port: 8080)
|
|
452
|
+
expect(error).to be_nil
|
|
453
|
+
expect(valid).to eq(true)
|
|
454
|
+
expect(cert).to eq(cert)
|
|
455
|
+
|
|
456
|
+
expect($proxy).to have_received(:do_GET).once
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
context 'when the proxy is not reachable' do
|
|
461
|
+
it 'returns a http error' do
|
|
462
|
+
cert = OpenSSL::X509::Certificate.new(File.read(File.join(__dir__, 'fixtures/google_com_client.pem')))
|
|
463
|
+
ca_bundle = OpenSSL::X509::Certificate.load(File.read(File.join(__dir__, 'fixtures/google_com_ca_bundle.pem')))
|
|
464
|
+
|
|
465
|
+
valid, error, cert = SSLTest.test_cert(cert, ca_bundle, proxy_host: '127.0.0.1', proxy_port: 55000)
|
|
466
|
+
expect(error).to include('(Connection refused - connect(2) for "127.0.0.1" port 55000)')
|
|
467
|
+
expect(valid).to be_nil
|
|
468
|
+
expect(cert).to eq(cert)
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
end
|
data/ssl-test.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ssl-test
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Adrien Rey-Jarthon
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 2025-11-28 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: bundler
|
|
@@ -52,15 +51,29 @@ dependencies:
|
|
|
52
51
|
- - ">="
|
|
53
52
|
- !ruby/object:Gem::Version
|
|
54
53
|
version: '0'
|
|
55
|
-
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: webrick
|
|
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'
|
|
56
68
|
email:
|
|
57
69
|
- jobs@adrienjarthon.com
|
|
58
70
|
executables: []
|
|
59
71
|
extensions: []
|
|
60
72
|
extra_rdoc_files: []
|
|
61
73
|
files:
|
|
74
|
+
- ".github/dependabot.yml"
|
|
75
|
+
- ".github/workflows/ruby.yml"
|
|
62
76
|
- ".gitignore"
|
|
63
|
-
- ".travis.yml"
|
|
64
77
|
- Gemfile
|
|
65
78
|
- LICENSE.txt
|
|
66
79
|
- README.md
|
|
@@ -69,13 +82,32 @@ files:
|
|
|
69
82
|
- lib/ssl-test/crl.rb
|
|
70
83
|
- lib/ssl-test/object_size.rb
|
|
71
84
|
- lib/ssl-test/ocsp.rb
|
|
85
|
+
- spec/fixtures/digicert_com_ca_bundle.pem
|
|
86
|
+
- spec/fixtures/digicert_com_client.pem
|
|
87
|
+
- spec/fixtures/expired_cert_ca_bundle.pem
|
|
88
|
+
- spec/fixtures/expired_cert_client.pem
|
|
89
|
+
- spec/fixtures/google_com_ca_bundle.pem
|
|
90
|
+
- spec/fixtures/google_com_client.pem
|
|
91
|
+
- spec/fixtures/incomplete_chain_ca_bundle.pem
|
|
92
|
+
- spec/fixtures/incomplete_chain_client.pem
|
|
93
|
+
- spec/fixtures/revoked_badssl_ca_bundle.pem
|
|
94
|
+
- spec/fixtures/revoked_badssl_client.pem
|
|
95
|
+
- spec/fixtures/revoked_rsa_dv_ca_bundle.pem
|
|
96
|
+
- spec/fixtures/revoked_rsa_dv_client.pem
|
|
97
|
+
- spec/fixtures/self_signed_ca_bundle.pem
|
|
98
|
+
- spec/fixtures/self_signed_client.pem
|
|
99
|
+
- spec/fixtures/www_demarches-simplifiees_fr_ca_bundle.pem
|
|
100
|
+
- spec/fixtures/www_demarches-simplifiees_fr_client.pem
|
|
101
|
+
- spec/fixtures/www_github_com_ca_bundle.pem
|
|
102
|
+
- spec/fixtures/www_github_com_client.pem
|
|
103
|
+
- spec/fixtures/www_mycs_com_ca_bundle.pem
|
|
104
|
+
- spec/fixtures/www_mycs_com_client.pem
|
|
72
105
|
- spec/ssl-test_spec.rb
|
|
73
106
|
- ssl-test.gemspec
|
|
74
107
|
homepage: https://github.com/jarthod/ssl-test
|
|
75
108
|
licenses:
|
|
76
109
|
- MIT
|
|
77
110
|
metadata: {}
|
|
78
|
-
post_install_message:
|
|
79
111
|
rdoc_options: []
|
|
80
112
|
require_paths:
|
|
81
113
|
- lib
|
|
@@ -90,9 +122,28 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
90
122
|
- !ruby/object:Gem::Version
|
|
91
123
|
version: '0'
|
|
92
124
|
requirements: []
|
|
93
|
-
rubygems_version: 3.
|
|
94
|
-
signing_key:
|
|
125
|
+
rubygems_version: 3.6.2
|
|
95
126
|
specification_version: 4
|
|
96
127
|
summary: Test website SSL certificate validity
|
|
97
128
|
test_files:
|
|
129
|
+
- spec/fixtures/digicert_com_ca_bundle.pem
|
|
130
|
+
- spec/fixtures/digicert_com_client.pem
|
|
131
|
+
- spec/fixtures/expired_cert_ca_bundle.pem
|
|
132
|
+
- spec/fixtures/expired_cert_client.pem
|
|
133
|
+
- spec/fixtures/google_com_ca_bundle.pem
|
|
134
|
+
- spec/fixtures/google_com_client.pem
|
|
135
|
+
- spec/fixtures/incomplete_chain_ca_bundle.pem
|
|
136
|
+
- spec/fixtures/incomplete_chain_client.pem
|
|
137
|
+
- spec/fixtures/revoked_badssl_ca_bundle.pem
|
|
138
|
+
- spec/fixtures/revoked_badssl_client.pem
|
|
139
|
+
- spec/fixtures/revoked_rsa_dv_ca_bundle.pem
|
|
140
|
+
- spec/fixtures/revoked_rsa_dv_client.pem
|
|
141
|
+
- spec/fixtures/self_signed_ca_bundle.pem
|
|
142
|
+
- spec/fixtures/self_signed_client.pem
|
|
143
|
+
- spec/fixtures/www_demarches-simplifiees_fr_ca_bundle.pem
|
|
144
|
+
- spec/fixtures/www_demarches-simplifiees_fr_client.pem
|
|
145
|
+
- spec/fixtures/www_github_com_ca_bundle.pem
|
|
146
|
+
- spec/fixtures/www_github_com_client.pem
|
|
147
|
+
- spec/fixtures/www_mycs_com_ca_bundle.pem
|
|
148
|
+
- spec/fixtures/www_mycs_com_client.pem
|
|
98
149
|
- spec/ssl-test_spec.rb
|