File: ocsp_verifier_spec.rb

package info (click to toggle)
ruby-mongo 2.21.3-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 14,764 kB
  • sloc: ruby: 108,806; makefile: 5; sh: 2
file content (289 lines) | stat: -rw-r--r-- 8,544 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# frozen_string_literal: true
# rubocop:todo all

require 'lite_spec_helper'
require 'webrick'

describe Mongo::Socket::OcspVerifier do
  require_ocsp_verifier
  with_openssl_debug
  retry_test sleep: 5

  def self.with_ocsp_responder(port = 8100, path = '/', &setup)
    around do |example|
      server = WEBrick::HTTPServer.new(Port: port)
      server.mount_proc path, &setup
      Thread.new { server.start }
      begin
        example.run
      ensure
        server.shutdown
      end

      ::Utils.wait_for_port_free(port, 5)
    end
  end

  shared_examples 'verifies' do
    context 'mri' do
      fails_on_jruby

      it 'verifies' do
        verifier.verify.should be true
      end
    end

    context 'jruby' do
      require_jruby

      # JRuby does not return OCSP endpoints, therefore we never perform
      # any validation.
      # https://github.com/jruby/jruby-openssl/issues/210
      it 'does not verify' do
        verifier.verify.should be false
      end
    end
  end

  shared_examples 'fails verification' do
    context 'mri' do
      fails_on_jruby

      it 'raises an exception' do
        lambda do
          verifier.verify
        # Redirect tests receive responses from port 8101,
        # tests without redirects receive responses from port 8100.
        end.should raise_error(Mongo::Error::ServerCertificateRevoked, %r,TLS certificate of 'foo' has been revoked according to 'http://localhost:810[01]/status',)
      end

      it 'does not wait for the timeout' do
        lambda do
          lambda do
            verifier.verify
          end.should raise_error(Mongo::Error::ServerCertificateRevoked)
        end.should take_shorter_than 7
      end
    end

    context 'jruby' do
      require_jruby

      # JRuby does not return OCSP endpoints, therefore we never perform
      # any validation.
      # https://github.com/jruby/jruby-openssl/issues/210
      it 'does not verify' do
        verifier.verify.should be false
      end
    end
  end

  shared_examples 'does not verify' do
    it 'does not verify and does not raise an exception' do
      verifier.verify.should be false
    end
  end

  shared_context 'basic verifier' do

    let(:cert) { OpenSSL::X509::Certificate.new(File.read(cert_path)) }
    let(:ca_cert) { OpenSSL::X509::Certificate.new(File.read(ca_cert_path)) }

    let(:cert_store) do
      OpenSSL::X509::Store.new.tap do |store|
        store.add_cert(ca_cert)
      end
    end

    let(:verifier) do
      described_class.new('foo', cert, ca_cert, cert_store, timeout: 7)
    end
  end

  shared_context 'verifier' do |opts|
    algorithm = opts[:algorithm]

    let(:cert_path) { SpecConfig.instance.ocsp_files_dir.join("#{algorithm}/server.pem") }
    let(:ca_cert_path) { SpecConfig.instance.ocsp_files_dir.join("#{algorithm}/ca.pem") }

    include_context 'basic verifier'
  end

  %w(rsa ecdsa).each do |algorithm|
    context "when using #{algorithm} cert" do
      include_context 'verifier', algorithm: algorithm

      context 'responder not responding' do
        include_examples 'does not verify'

        it 'does not wait for the timeout' do
          # Loopback interface should be refusing connections, which will make
          # the operation complete quickly.
          lambda do
            verifier.verify
          end.should take_shorter_than 7
        end
      end

      %w(ca delegate).each do |responder_cert|
        responder_cert_file_name = {
          'ca' => 'ca',
          'delegate' => 'ocsp-responder',
        }.fetch(responder_cert)

        context "when responder uses #{responder_cert} cert" do
          context 'good response' do
            with_ocsp_mock(
              SpecConfig.instance.ocsp_files_dir.join("#{algorithm}/ca.pem"),
              SpecConfig.instance.ocsp_files_dir.join("#{algorithm}/#{responder_cert_file_name}.crt"),
              SpecConfig.instance.ocsp_files_dir.join("#{algorithm}/#{responder_cert_file_name}.key"),
            )

            include_examples 'verifies'

            it 'does not wait for the timeout' do
              lambda do
                verifier.verify
              end.should take_shorter_than 7
            end
          end

          context 'revoked response' do
            with_ocsp_mock(
              SpecConfig.instance.ocsp_files_dir.join("#{algorithm}/ca.pem"),
              SpecConfig.instance.ocsp_files_dir.join("#{algorithm}/#{responder_cert_file_name}.crt"),
              SpecConfig.instance.ocsp_files_dir.join("#{algorithm}/#{responder_cert_file_name}.key"),
              fault: 'revoked'
            )

            include_examples 'fails verification'
          end

          context 'unknown response' do
            with_ocsp_mock(
              SpecConfig.instance.ocsp_files_dir.join("#{algorithm}/ca.pem"),
              SpecConfig.instance.ocsp_files_dir.join("#{algorithm}/#{responder_cert_file_name}.crt"),
              SpecConfig.instance.ocsp_files_dir.join("#{algorithm}/#{responder_cert_file_name}.key"),
              fault: 'unknown',
            )

            include_examples 'does not verify'

            it 'does not wait for the timeout' do
              lambda do
                verifier.verify
              end.should take_shorter_than 7
            end
          end
        end
      end
    end
  end

  context 'when OCSP responder redirects' do
    algorithm = 'rsa'
    responder_cert_file_name = 'ca'
    let(:algorithm) { 'rsa' }
    let(:responder_cert_file_name) { 'ca' }

    context 'one time' do

      with_ocsp_responder do |req, res|
        res.status = 303
        res['locAtion'] = "http://localhost:8101#{req.path}"
        res.body = "See http://localhost:8101#{req.path}"
      end

      include_context 'verifier', algorithm: algorithm

      context 'good response' do
        with_ocsp_mock(
          SpecConfig.instance.ocsp_files_dir.join("#{algorithm}/ca.pem"),
          SpecConfig.instance.ocsp_files_dir.join("#{algorithm}/#{responder_cert_file_name}.crt"),
          SpecConfig.instance.ocsp_files_dir.join("#{algorithm}/#{responder_cert_file_name}.key"),
          port: 8101,
        )

        include_examples 'verifies'

        it 'does not wait for the timeout' do
          lambda do
            verifier.verify
          end.should take_shorter_than 7
        end
      end

      context 'revoked response' do
        with_ocsp_mock(
          SpecConfig.instance.ocsp_files_dir.join("#{algorithm}/ca.pem"),
          SpecConfig.instance.ocsp_files_dir.join("#{algorithm}/#{responder_cert_file_name}.crt"),
          SpecConfig.instance.ocsp_files_dir.join("#{algorithm}/#{responder_cert_file_name}.key"),
          fault: 'revoked',
          port: 8101,
        )

        include_examples 'fails verification'
      end

      context 'unknown response' do
        with_ocsp_mock(
          SpecConfig.instance.ocsp_files_dir.join("#{algorithm}/ca.pem"),
          SpecConfig.instance.ocsp_files_dir.join("#{algorithm}/#{responder_cert_file_name}.crt"),
          SpecConfig.instance.ocsp_files_dir.join("#{algorithm}/#{responder_cert_file_name}.key"),
          fault: 'unknown',
          port: 8101,
        )

        include_examples 'does not verify'

        it 'does not wait for the timeout' do
          lambda do
            verifier.verify
          end.should take_shorter_than 7
        end
      end
    end

    context 'infinitely' do
      with_ocsp_mock(
        SpecConfig.instance.ocsp_files_dir.join("#{algorithm}/ca.pem"),
        SpecConfig.instance.ocsp_files_dir.join("#{algorithm}/#{responder_cert_file_name}.crt"),
        SpecConfig.instance.ocsp_files_dir.join("#{algorithm}/#{responder_cert_file_name}.key"),
        port: 8101,
      )

      with_ocsp_responder do |req, res|
        res.status = 303
        res['locAtion'] = req.path
        res.body = "See #{req.path} indefinitely"
      end

      include_context 'verifier', algorithm: algorithm
      include_examples 'does not verify'
    end
  end

  context 'responder returns unexpected status code' do

    include_context 'verifier', algorithm: 'rsa'

    [400, 404, 500, 503].each do |code|
      context "code #{code}" do
        with_ocsp_responder do |req, res|
          res.status = code
          res.body = "HTTP #{code}"
        end

        include_examples 'does not verify'
      end
    end

    context 'code 204' do
      with_ocsp_responder do |req, res|
        res.status = 204
      end

      include_examples 'does not verify'
    end
  end
end