File: x5c_key_finder_spec.rb

package info (click to toggle)
ruby-jwt 3.1.2-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 876 kB
  • sloc: ruby: 5,550; makefile: 4
file content (200 lines) | stat: -rw-r--r-- 7,066 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
# frozen_string_literal: true

RSpec.describe JWT::X5cKeyFinder do
  let(:root_key) { test_pkey('rsa-2048-private.pem') }
  let(:root_dn) { OpenSSL::X509::Name.parse('/DC=org/DC=fake-ca/CN=Fake CA') }
  let(:root_certificate) { generate_root_cert(root_dn, root_key) }
  let(:leaf_key) { generate_key }
  let(:leaf_dn) { OpenSSL::X509::Name.parse('/DC=org/DC=fake/CN=Fake') }
  let(:leaf_serial) { 2 }
  let(:leaf_not_after) { Time.now + 3600 }
  let(:leaf_signing_key) { root_key }
  let(:leaf_certificate) do
    cert = generate_cert(
      leaf_dn,
      leaf_key.public_key,
      leaf_serial,
      issuer: root_certificate,
      not_after: leaf_not_after
    )
    ef = OpenSSL::X509::ExtensionFactory.new
    ef.config = OpenSSL::Config.parse(leaf_cdp)
    ef.subject_certificate = cert
    cert.add_extension(ef.create_extension('crlDistributionPoints', '@crlDistPts'))
    cert.sign(leaf_signing_key, 'sha256')
    cert
  end
  let(:leaf_cdp) { <<-_CNF_ }
    [crlDistPts]
    URI.1 = http://www.example.com/crl
  _CNF_

  let(:crl) { issue_crl([], issuer: root_certificate, issuer_key: root_key) }

  let(:x5c_header) { [Base64.strict_encode64(leaf_certificate.to_der)] }
  subject(:keyfinder) { described_class.new([root_certificate], [crl]).from(x5c_header) }

  it 'returns the public key from a certificate that is signed by trusted roots and not revoked' do
    expect(keyfinder).to be_a(OpenSSL::PKey::RSA)
    expect(keyfinder.public_key.to_der).to eq(leaf_certificate.public_key.to_der)
  end

  context 'already parsed certificates' do
    let(:x5c_header) { [leaf_certificate] }

    it 'returns the public key from a certificate that is signed by trusted roots and not revoked' do
      expect(keyfinder).to be_a(OpenSSL::PKey::RSA)
      expect(keyfinder.public_key.to_der).to eq(leaf_certificate.public_key.to_der)
    end
  end

  context '::JWT.decode' do
    let(:token_payload) { { 'data' => 'something' } }
    let(:encoded_token) { JWT.encode(token_payload, leaf_key, 'RS256', { 'x5c' => x5c_header }) }
    let(:decoded_payload) do
      JWT.decode(encoded_token, nil, true, algorithms: ['RS256'], x5c: { root_certificates: [root_certificate], crls: [crl] }).first
    end

    it 'returns the encoded payload after successful certificate path verification' do
      expect(decoded_payload).to eq(token_payload)
    end
  end

  context 'certificate' do
    context 'expired' do
      let(:leaf_not_after) { Time.now - 3600 }

      it 'raises an error' do
        error = 'Certificate verification failed: certificate has expired. Certificate subject: /DC=org/DC=fake/CN=Fake.'
        expect { keyfinder }.to raise_error(JWT::VerificationError, error)
      end
    end

    context 'signature could not be verified with the given trusted roots' do
      let(:leaf_signing_key) { generate_key }

      it 'raises an error' do
        error = 'Certificate verification failed: certificate signature failure. Certificate subject: /DC=org/DC=fake/CN=Fake.'
        expect { keyfinder }.to raise_error(JWT::VerificationError, error)
      end
    end

    context 'could not be chained to a trusted root certificate' do
      context 'given an array' do
        subject(:keyfinder) { described_class.new([], [crl]).from(x5c_header) }

        it 'raises a verification error' do
          error = 'Certificate verification failed: unable to get local issuer certificate. Certificate subject: /DC=org/DC=fake/CN=Fake.'
          expect { keyfinder }.to raise_error(JWT::VerificationError, error)
        end
      end

      context 'given nil' do
        subject(:keyfinder) { described_class.new(nil, [crl]).from(x5c_header) }

        it 'raises a decode error' do
          error = 'Root certificates must be specified'
          expect { keyfinder }.to raise_error(ArgumentError, error)
        end
      end
    end

    context 'revoked' do
      let(:revocation) { [leaf_serial, Time.now - 60, 1] }
      let(:crl) { issue_crl([revocation], issuer: root_certificate, issuer_key: root_key) }

      it 'raises an error' do
        error = 'Certificate verification failed: certificate revoked. Certificate subject: /DC=org/DC=fake/CN=Fake.'
        expect { keyfinder }.to raise_error(JWT::VerificationError, error)
      end
    end
  end

  context 'CRL' do
    context 'expired' do
      let(:next_up) { Time.now - 60 }
      let(:crl) { issue_crl([], next_up: next_up, issuer: root_certificate, issuer_key: root_key) }

      it 'raises an error' do
        error = 'Certificate verification failed: CRL has expired. Certificate subject: /DC=org/DC=fake/CN=Fake.'
        expect { keyfinder }.to raise_error(JWT::VerificationError, error)
      end
    end

    context 'signature could not be verified with the given trusted roots' do
      let(:crl) { issue_crl([], issuer: root_certificate, issuer_key: generate_key) }

      it 'raises an error' do
        error = 'Certificate verification failed: CRL signature failure. Certificate subject: /DC=org/DC=fake/CN=Fake.'
        expect { keyfinder }.to raise_error(JWT::VerificationError, error)
      end
    end

    context 'not given' do
      subject(:keyfinder) { described_class.new([root_certificate], nil).from(x5c_header) }

      it 'raises an error' do
        error = 'Certificate verification failed: unable to get certificate CRL. Certificate subject: /DC=org/DC=fake/CN=Fake.'
        expect { keyfinder }.to raise_error(JWT::VerificationError, error)
      end
    end
  end

  private

  def generate_key
    OpenSSL::PKey::RSA.new(2048)
  end

  def generate_root_cert(root_dn, root_key)
    cert = generate_cert(root_dn, root_key, 1)
    ef = OpenSSL::X509::ExtensionFactory.new
    cert.add_extension(ef.create_extension('basicConstraints', 'CA:TRUE', true))
    cert.sign(root_key, 'sha256')
    cert
  end

  def generate_cert(subject, key, serial, issuer: nil, not_after: nil)
    cert = OpenSSL::X509::Certificate.new
    issuer ||= cert
    cert.version = 2
    cert.serial = serial
    cert.subject = subject
    cert.issuer = issuer.subject
    cert.public_key = key
    now = Time.now
    cert.not_before = now - 3600
    cert.not_after = not_after || (now + 3600)
    cert
  end

  def issue_crl(revocations, issuer:, issuer_key:, next_up: nil)
    crl = OpenSSL::X509::CRL.new
    crl.issuer = issuer.subject
    crl.version = 1
    now = Time.now
    crl.last_update = now - 3600
    crl.next_update = next_up || (now + 3600)

    revocations.each do |rserial, time, reason_code|
      revoked = build_revoked(rserial, time, reason_code)
      crl.add_revoked(revoked)
    end

    crlnum = OpenSSL::ASN1::Integer(1)
    crl.add_extension(OpenSSL::X509::Extension.new('crlNumber', crlnum))

    crl.sign(issuer_key, 'sha256')
    crl
  end

  def build_revoked(rserial, time, reason_code)
    revoked = OpenSSL::X509::Revoked.new
    revoked.serial = rserial
    revoked.time = time
    enum = OpenSSL::ASN1::Enumerated(reason_code)
    ext = OpenSSL::X509::Extension.new('CRLReason', enum)
    revoked.add_extension(ext)
    revoked
  end
end