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
|