From f52b3a1959affb9f5fac88384bacbbe0121a1a7b Mon Sep 17 00:00:00 2001 From: Joakim Antman Date: Sat, 24 Oct 2020 00:38:03 +0300 Subject: [PATCH] Make the RbNaCl dependent tests optional --- .travis.yml | 6 +- Appraisals | 10 ++ gemfiles/openssl.gemfile | 7 + gemfiles/openssl_2.1.gemfile | 0 gemfiles/rbnacl.gemfile | 7 + ruby-jwt.gemspec | 1 + spec/integration/readme_examples_spec.rb | 2 +- spec/jwt_spec.rb | 162 +++++++++++++---------- 8 files changed, 124 insertions(+), 71 deletions(-) create mode 100644 Appraisals create mode 100644 gemfiles/openssl.gemfile delete mode 100644 gemfiles/openssl_2.1.gemfile create mode 100644 gemfiles/rbnacl.gemfile diff --git a/.travis.yml b/.travis.yml index 0d9c7495..6f6a1422 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,6 @@ +sudo: required +cache: bundler +dist: trusty language: ruby rvm: - 2.3 @@ -9,7 +12,8 @@ rvm: - truffleruby-head gemfile: - gemfiles/standalone.gemfile - - gemfiles/openssl_2.1.gemfile + - gemfiles/openssl.gemfile + - gemfiles/rbnacl.gemfile script: - bundle exec rspec - bundle exec codeclimate-test-reporter diff --git a/Appraisals b/Appraisals new file mode 100644 index 00000000..21954496 --- /dev/null +++ b/Appraisals @@ -0,0 +1,10 @@ +appraise 'standalone' do +end + +appraise 'openssl' do + gem 'openssl', '~> 2.1' +end + +appraise 'rbnacl' do + gem 'rbnacl' +end diff --git a/gemfiles/openssl.gemfile b/gemfiles/openssl.gemfile new file mode 100644 index 00000000..94ce8660 --- /dev/null +++ b/gemfiles/openssl.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "openssl", "~> 2.1" + +gemspec path: "../" diff --git a/gemfiles/openssl_2.1.gemfile b/gemfiles/openssl_2.1.gemfile deleted file mode 100644 index e69de29b..00000000 diff --git a/gemfiles/rbnacl.gemfile b/gemfiles/rbnacl.gemfile new file mode 100644 index 00000000..ad523a89 --- /dev/null +++ b/gemfiles/rbnacl.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rbnacl" + +gemspec path: "../" diff --git a/ruby-jwt.gemspec b/ruby-jwt.gemspec index ac41e2ae..a0415b7d 100644 --- a/ruby-jwt.gemspec +++ b/ruby-jwt.gemspec @@ -20,6 +20,7 @@ Gem::Specification.new do |spec| spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = %w[lib] + spec.add_development_dependency 'appraisal' spec.add_development_dependency 'bundler' spec.add_development_dependency 'rake' spec.add_development_dependency 'rspec' diff --git a/spec/integration/readme_examples_spec.rb b/spec/integration/readme_examples_spec.rb index 361a7ab2..7488541c 100644 --- a/spec/integration/readme_examples_spec.rb +++ b/spec/integration/readme_examples_spec.rb @@ -79,7 +79,7 @@ { 'data' => 'test' }, { 'alg' => 'PS256' } ] - end + end if OpenSSL::VERSION >= '2.1' end context 'claims' do diff --git a/spec/jwt_spec.rb b/spec/jwt_spec.rb index e49f8dee..1ffedc99 100644 --- a/spec/jwt_spec.rb +++ b/spec/jwt_spec.rb @@ -7,7 +7,7 @@ let(:payload) { { 'user_id' => 'some@user.tld' } } let :data do - { + data = { :secret => 'My$ecretK3y', :rsa_private => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'rsa-2048-private.pem'))), :rsa_public => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'rsa-2048-public.pem'))), @@ -19,8 +19,6 @@ 'ES384_public' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec384-public.pem'))), 'ES512_private' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec512-private.pem'))), 'ES512_public' => OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec512-public.pem'))), - 'ED25519_private' => RbNaCl::Signatures::Ed25519::SigningKey.new('abcdefghijklmnopqrstuvwxyzABCDEF'), - 'ED25519_public' => RbNaCl::Signatures::Ed25519::SigningKey.new('abcdefghijklmnopqrstuvwxyzABCDEF').verify_key, 'NONE' => 'eyJhbGciOiJub25lIn0.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.', 'HS256' => 'eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.kWOVtIOpWcG7JnyJG0qOkTDbOy636XrrQhMm_8JrRQ8', 'HS512256' => 'eyJhbGciOiJIUzUxMjI1NiJ9.eyJ1c2VyX2lkIjoic29tZUB1c2VyLnRsZCJ9.Ds_4ibvf7z4QOBoKntEjDfthy3WJ-3rKMspTEcHE2bA', @@ -36,6 +34,14 @@ 'PS384' => '', 'PS512' => '' } + + if defined?(RbNaCl) + data.merge!( + 'ED25519_private' => RbNaCl::Signatures::Ed25519::SigningKey.new('abcdefghijklmnopqrstuvwxyzABCDEF'), + 'ED25519_public' => RbNaCl::Signatures::Ed25519::SigningKey.new('abcdefghijklmnopqrstuvwxyzABCDEF').verify_key, + ) + end + data end after(:each) do @@ -99,7 +105,7 @@ expect(validator).to receive(:validate!) { true } payload = {} - JWT.encode payload, "secret", JWT::Algos::Hmac::SUPPORTED.sample + JWT.encode payload, "secret", 'HS256' end it 'does not validate the payload if it is not present' do @@ -107,11 +113,14 @@ expect(JWT::ClaimsValidator).not_to receive(:new) { validator } payload = nil - JWT.encode payload, "secret", JWT::Algos::Hmac::SUPPORTED.sample + JWT.encode payload, "secret", 'HS256' end end - %w[HS256 HS512256 HS384 HS512].each do |alg| + algorithms = %w[HS256 HS384 HS512] + algorithms << 'HS512256' if defined?(RbNaCl) + + algorithms.each do |alg| context "alg: #{alg}" do it 'should generate a valid token' do token = JWT.encode payload, data[:secret], alg @@ -180,38 +189,40 @@ end end - %w[ED25519].each do |alg| - context "alg: #{alg}" do - before(:each) do - data[alg] = JWT.encode payload, data["#{alg}_private"], alg - end + if defined?(RbNaCl) + %w[ED25519].each do |alg| + context "alg: #{alg}" do + before(:each) do + data[alg] = JWT.encode payload, data["#{alg}_private"], alg + end - let(:wrong_key) { OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec256-wrong-public.pem'))) } + let(:wrong_key) { OpenSSL::PKey.read(File.read(File.join(CERT_PATH, 'ec256-wrong-public.pem'))) } - it 'should generate a valid token' do - jwt_payload, header = JWT.decode data[alg], data["#{alg}_public"], true, algorithm: alg + it 'should generate a valid token' do + jwt_payload, header = JWT.decode data[alg], data["#{alg}_public"], true, algorithm: alg - expect(header['alg']).to eq alg - expect(jwt_payload).to eq payload - end + expect(header['alg']).to eq alg + expect(jwt_payload).to eq payload + end - it 'should decode a valid token' do - jwt_payload, header = JWT.decode data[alg], data["#{alg}_public"], true, algorithm: alg + it 'should decode a valid token' do + jwt_payload, header = JWT.decode data[alg], data["#{alg}_public"], true, algorithm: alg - expect(header['alg']).to eq alg - expect(jwt_payload).to eq payload - end + expect(header['alg']).to eq alg + expect(jwt_payload).to eq payload + end - it 'wrong key should raise JWT::DecodeError' do - expect do - JWT.decode data[alg], wrong_key - end.to raise_error JWT::DecodeError - end + it 'wrong key should raise JWT::DecodeError' do + expect do + JWT.decode data[alg], wrong_key + end.to raise_error JWT::DecodeError + end - it 'wrong key and verify = false should not raise JWT::DecodeError' do - expect do - JWT.decode data[alg], wrong_key, false - end.not_to raise_error + it 'wrong key and verify = false should not raise JWT::DecodeError' do + expect do + JWT.decode data[alg], wrong_key, false + end.not_to raise_error + end end end end @@ -252,51 +263,64 @@ end end - %w[PS256 PS384 PS512].each do |alg| - context "alg: #{alg}" do - before(:each) do - data[alg] = JWT.encode payload, data[:rsa_private], alg + unless OpenSSL::VERSION >= '2.1' + %w[PS256 PS384 PS512].each do |alg| + context "alg: #{alg}" do + it 'raises error about OpenSSL version' do + expect { JWT.encode payload, data[:rsa_private], alg }.to raise_error( + JWT::RequiredDependencyError, + /You currently have OpenSSL .*. PS support requires >= 2.1/ + ) + end end + end + else + %w[PS256 PS384 PS512].each do |alg| + context "alg: #{alg}" do + before(:each) do + data[alg] = JWT.encode payload, data[:rsa_private], alg + end - let(:wrong_key) { data[:wrong_rsa_public] } + let(:wrong_key) { data[:wrong_rsa_public] } - it 'should generate a valid token' do - token = data[alg] - - header, body, signature = token.split('.') - - expect(header).to eql(Base64.strict_encode64({ alg: alg }.to_json)) - expect(body).to eql(Base64.strict_encode64(payload.to_json)) - - # Validate signature is made of up header and body of JWT - translated_alg = alg.gsub('PS', 'sha') - valid_signature = data[:rsa_public].verify_pss( - translated_alg, - JWT::Base64.url_decode(signature), - [header, body].join('.'), - salt_length: :auto, - mgf1_hash: translated_alg - ) - expect(valid_signature).to be true - end + it 'should generate a valid token' do + token = data[alg] - it 'should decode a valid token' do - jwt_payload, header = JWT.decode data[alg], data[:rsa_public], true, algorithm: alg + header, body, signature = token.split('.') - expect(header['alg']).to eq alg - expect(jwt_payload).to eq payload - end + expect(header).to eql(Base64.strict_encode64({ alg: alg }.to_json)) + expect(body).to eql(Base64.strict_encode64(payload.to_json)) - it 'wrong key should raise JWT::DecodeError' do - expect do - JWT.decode data[alg], wrong_key - end.to raise_error JWT::DecodeError - end + # Validate signature is made of up header and body of JWT + translated_alg = alg.gsub('PS', 'sha') + valid_signature = data[:rsa_public].verify_pss( + translated_alg, + JWT::Base64.url_decode(signature), + [header, body].join('.'), + salt_length: :auto, + mgf1_hash: translated_alg + ) + expect(valid_signature).to be true + end - it 'wrong key and verify = false should not raise JWT::DecodeError' do - expect do - JWT.decode data[alg], wrong_key, false - end.not_to raise_error + it 'should decode a valid token' do + jwt_payload, header = JWT.decode data[alg], data[:rsa_public], true, algorithm: alg + + expect(header['alg']).to eq alg + expect(jwt_payload).to eq payload + end + + it 'wrong key should raise JWT::DecodeError' do + expect do + JWT.decode data[alg], wrong_key + end.to raise_error JWT::DecodeError + end + + it 'wrong key and verify = false should not raise JWT::DecodeError' do + expect do + JWT.decode data[alg], wrong_key, false + end.not_to raise_error + end end end end