Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
potatosalad committed Jan 8, 2024
2 parents 7987713 + 29d0942 commit f3172a6
Show file tree
Hide file tree
Showing 25 changed files with 541 additions and 394 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ name: CI
on: [push, pull_request]

env:
JOSE_CRYPTO_FALLBACK: "true"
COVERAGE: true
JOSE_CRYPTO_FALLBACK: true
RUBYOPT: "-W0"

jobs:
Expand Down
4 changes: 2 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ group :test do
gem "x25519"
gem 'minitest-focus', require: false
gem 'minitest-perf', require: false
gem 'rantly', github: 'abargnesi/rantly', ref: '8ba1d908659c1cf2a08487b2a4e758a6197a0802', require: false
gem 'rantly', github: 'rantly-rb/rantly', ref: '9ea88a43d6437db76a0b5341a3c41c2687e18cd8', require: false
gem 'simplecov', require: false
if ENV['CI']
gem 'coveralls', require: false
gem 'codecov', require: false
end
end

Expand Down
395 changes: 22 additions & 373 deletions LICENSE.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# JOSE

[![Travis](https://img.shields.io/travis/potatosalad/ruby-jose.svg?maxAge=86400)](https://travis-ci.org/potatosalad/ruby-jose) [![Coverage Status](https://coveralls.io/repos/github/potatosalad/ruby-jose/badge.svg?branch=master)](https://coveralls.io/github/potatosalad/ruby-jose?branch=master) [![Gem](https://img.shields.io/gem/v/jose.svg?maxAge=86400)](https://rubygems.org/gems/jose) [![Docs](https://img.shields.io/badge/yard-docs-blue.svg?maxAge=86400)](http://www.rubydoc.info/gems/jose) [![Inline docs](http://inch-ci.org/github/potatosalad/ruby-jose.svg?branch=master&style=shields)](http://inch-ci.org/github/potatosalad/ruby-jose)
[![CI](https://github.com/potatosalad/ruby-jose/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/potatosalad/ruby-jose/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/potatosalad/ruby-jose/branch/master/graph/badge.svg)](https://codecov.io/gh/potatosalad/ruby-jose) [![Gem](https://img.shields.io/gem/v/jose.svg?maxAge=86400)](https://rubygems.org/gems/jose) [![Docs](https://img.shields.io/badge/yard-docs-blue.svg?maxAge=86400)](http://www.rubydoc.info/gems/jose) [![Inline docs](http://inch-ci.org/github/potatosalad/ruby-jose.svg?branch=master&style=shields)](http://inch-ci.org/github/potatosalad/ruby-jose)

JSON Object Signing and Encryption (JOSE) for Ruby.

Expand Down Expand Up @@ -65,4 +65,4 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/potato

## License

The gem is available as open source under the terms of the [MPL-2.0 License](http://opensource.org/licenses/MPL-2.0).
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
1 change: 1 addition & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Rake::TestTask.new(:test) do |t|
t.libs << "test"
t.libs << "lib"
t.test_files = FileList['test/**/*_test.rb']
t.ruby_opts += ["-W0"]
end

task :default => :test
10 changes: 9 additions & 1 deletion jose.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
spec.summary = %q{JSON Object Signing and Encryption}
spec.description = %q{JSON Object Signing and Encryption}
spec.homepage = "https://github.com/potatosalad/ruby-jose"
spec.license = "MPL-2.0"
spec.license = "MIT"

# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
# delete this section to allow pushing this gem to any host.
Expand All @@ -29,4 +29,12 @@ Gem::Specification.new do |spec|

spec.add_dependency "base64"
spec.add_dependency "immutable-ruby"

spec.add_development_dependency "bundler", "~> 2.5"
spec.add_development_dependency "rake", "~> 13.1"
spec.add_development_dependency "minitest"
spec.add_development_dependency "json"
spec.add_development_dependency "rbnacl"
spec.add_development_dependency "ed25519"
spec.add_development_dependency "x25519"
end
20 changes: 20 additions & 0 deletions lib/jose.rb
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,31 @@ def self.urlsafe_encode64(binary)
return Base64.strict_encode64(binary).tr('+/', '-_').delete('=')
end

# Gets the current XChaCha20-Poly1305 module used by {JOSE::JWA::XChaCha20Poly1305 JOSE::JWA::XChaCha20Poly1305}, see {.xchacha20poly1305_module=} for default.
# @return [Module]
def self.xchacha20poly1305_module
return JOSE::JWA::XChaCha20Poly1305.__implementation__
end

# Sets the current XChaCha20Poly1305 module used by {JOSE::JWA::XChaCha20Poly1305 JOSE::JWA::XChaCha20Poly1305}.
#
# Currently supported XChaCha20Poly1305 modules (first found is used as default):
#
# * {https://github.com/cryptosphere/rbnacl `RbNaCl`}
#
# Additional modules that implement the functions specified in {JOSE::JWA::XChaCha20Poly1305 JOSE::JWA::XChaCha20Poly1305} may also be used.
# @param [Module] mod
# @return [Module]
def self.xchacha20poly1305_module=(mod)
JOSE::JWA::XChaCha20Poly1305.__implementation__ = mod
end

private

def self.__config_change__
JOSE::JWA::Curve25519.__config_change__
JOSE::JWA::Curve448.__config_change__
JOSE::JWA::XChaCha20Poly1305.__config_change__
end

def self.sort_maps(term)
Expand Down
11 changes: 8 additions & 3 deletions lib/jose/jwa.rb
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,9 @@ def supports
'A256GCM',
'A128CBC-HS256',
'A192CBC-HS384',
'A256CBC-HS512'
'A256CBC-HS512',
'C20P',
'XC20P'
])
jwe_alg = __jwe_alg_support_check__([
['A128GCMKW', :block],
Expand All @@ -136,6 +138,7 @@ def supports
['A128KW', :block],
['A192KW', :block],
['A256KW', :block],
['C20PKW', :block],
['ECDH-ES', :box],
['ECDH-ES+A128KW', :box],
['ECDH-ES+A192KW', :box],
Expand All @@ -146,6 +149,7 @@ def supports
['RSA1_5', :rsa],
['RSA-OAEP', :rsa],
['RSA-OAEP-256', :rsa],
['XC20PKW', :block],
['dir', :direct]
], jwe_enc)
jwe_zip = __jwe_zip_support_check__([
Expand Down Expand Up @@ -231,7 +235,7 @@ def __jwe_alg_support_check__(key_algorithms, encryption_algorithms)
cipher_text = recv_jwk.box_encrypt(plain_text, send_jwk).compact
next recv_jwk.box_decrypt(cipher_text).first == plain_text
elsif strategy == :rsa
rsa ||= JOSE::JWK.generate_key([:rsa, 1024])
rsa ||= JOSE::JWK.generate_key([:rsa, 2048])
cipher_text = rsa.block_encrypt(plain_text, { 'alg' => alg, 'enc' => enc }).compact
next rsa.block_decrypt(cipher_text).first == plain_text
elsif strategy == :direct
Expand Down Expand Up @@ -314,7 +318,7 @@ def __jws_alg_support_check__(signature_algorithms)
begin
jwk = nil
jwk ||= JOSE::JWK.generate_key([:oct, 0]).merge({ 'alg' => alg, 'use' => 'sig' }) if alg == 'none'
jwk ||= (rsa ||= JOSE::JWK.generate_key([:rsa, 1024])).merge({ 'alg' => alg, 'use' => 'sig' }) if alg.start_with?('RS') or alg.start_with?('PS')
jwk ||= (rsa ||= JOSE::JWK.generate_key([:rsa, 2048])).merge({ 'alg' => alg, 'use' => 'sig' }) if alg.start_with?('RS') or alg.start_with?('PS')
jwk ||= JOSE::JWS.generate_key({ 'alg' => alg })
signed_text = jwk.sign(plain_text).compact
next jwk.verify_strict(signed_text, [alg]).first
Expand All @@ -337,3 +341,4 @@ def __jws_alg_support_check__(signature_algorithms)
require 'jose/jwa/curve448'
require 'jose/jwa/pkcs1'
require 'jose/jwa/pkcs7'
require 'jose/jwa/xchacha20poly1305'
19 changes: 18 additions & 1 deletion lib/jose/jwa/ed25519_rbnacl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,24 @@ def sign_ph(m, sk)
end

def verify(sig, m, pk)
return RbNaCl::Signatures::Ed25519::VerifyKey.new(pk).verify(sig, m)
verify_key = RbNaCl::Signatures::Ed25519::VerifyKey.new(pk)
if m.respond_to?(:bytesize) and m.bytesize == 0
# RbNaCl does not allow empty message signatures.
key = verify_key.instance_variable_get(:@key)
signature = sig.to_str
signature_bytes = verify_key.signature_bytes
RbNaCl::Util.check_length(signature, signature_bytes, "signature")
signed_message = signature + m
raise RbNaCl::LengthError, "Signed message can not be nil" if signed_message.nil?
raise RbNaCl::LengthError, "Signed message can not be shorter than a signature" if signed_message.bytesize < signature_bytes
buffer = RbNaCl::Util.zeros(signed_message.bytesize)
buffer_len = RbNaCl::Util.zeros(FFI::Type::LONG_LONG.size)
success = verify_key.class.sign_ed25519_open(buffer, buffer_len, signed_message, signed_message.bytesize, key)
raise(RbNaCl::BadSignatureError, "signature was forged/corrupt") unless success
return true
else
return verify_key.verify(sig, m)
end
end

def verify_ph(sig, m, pk)
Expand Down
4 changes: 2 additions & 2 deletions lib/jose/jwa/pkcs1.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def emsa_pss_encode(hash, message, salt, em_bits)
if hash.is_a?(String)
hash = OpenSSL::Digest.new(hash)
end
salt ||= -2
salt ||= -1
if salt.is_a?(Integer)
salt_len = salt
if salt_len == -2
Expand Down Expand Up @@ -102,7 +102,7 @@ def emsa_pss_verify(hash, message, em, salt_len, em_bits)
if hash.is_a?(String)
hash = OpenSSL::Digest.new(hash)
end
salt_len ||= -2
salt_len ||= -1
if salt_len == -2
hash_len = hash.digest('').bytesize
em_len = (em_bits / 8.0).ceil
Expand Down
61 changes: 61 additions & 0 deletions lib/jose/jwa/xchacha20poly1305.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
module JOSE::JWA::XChaCha20Poly1305

extend self

MUTEX = Mutex.new

@__implementations__ = []
@__ruby_implementations__ = []

def __implementation__
return MUTEX.synchronize { @__implementation__ ||= __pick_best_implementation__ }
end

def __implementation__=(implementation)
return MUTEX.synchronize { @__implementation__ = implementation }
end

def __register__(implementation, ruby = false)
MUTEX.synchronize {
if ruby
@__ruby_implementations__.unshift(implementation)
else
@__implementations__.unshift(implementation)
end
__config_change__(false)
implementation
}
end

def __config_change__(lock = true)
MUTEX.lock if lock
@__implementation__ ||= nil
@__implementation__ = __pick_best_implementation__ if @__implementation__.nil? or @__implementation__.__ruby__? or not @__implementation__.__supported__?
MUTEX.unlock if lock
end

def xchacha20poly1305_aead_encrypt(key, nonce, aad, plaintext)
return (@__implementation__ || __implementation__).xchacha20poly1305_aead_encrypt(key, nonce, aad, plaintext)
end

def xchacha20poly1305_aead_decrypt(key, nonce, aad, ciphertext, tag)
return (@__implementation__ || __implementation__).xchacha20poly1305_aead_decrypt(key, nonce, aad, ciphertext, tag)
end

private
def __pick_best_implementation__
implementation = nil
implementation = @__implementations__.detect do |mod|
next mod.__supported__?
end
implementation ||= @__ruby_implementations__.detect do |mod|
next mod.__supported__?
end
implementation ||= JOSE::JWA::XChaCha20Poly1305_Unsupported
return implementation
end

end

require 'jose/jwa/xchacha20poly1305_unsupported'
require 'jose/jwa/xchacha20poly1305_rbnacl'
35 changes: 35 additions & 0 deletions lib/jose/jwa/xchacha20poly1305_rbnacl.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
module JOSE::JWA::XChaCha20Poly1305_RbNaCl

extend self

def __ruby__?; false; end

def __supported__?
return @supported ||= begin
begin
require 'rbnacl/libsodium'
rescue LoadError
end
begin
require 'rbnacl'
rescue LoadError
end
!!(defined?(RbNaCl::AEAD::XChaCha20Poly1305IETF))
end
end

def xchacha20poly1305_aead_encrypt(key, nonce, aad, plaintext)
cipher = RbNaCl::AEAD::XChaCha20Poly1305IETF.new(key)
ciphertext_with_tag = cipher.encrypt(nonce, plaintext, aad)
return [ciphertext_with_tag[0..-17], ciphertext_with_tag[-16..-1]]
end

def xchacha20poly1305_aead_decrypt(key, nonce, aad, ciphertext, tag)
cipher = RbNaCl::AEAD::XChaCha20Poly1305IETF.new(key)
ciphertext_with_tag = [ciphertext, tag].join()
return cipher.decrypt(nonce, ciphertext_with_tag, aad)
end

end

JOSE::JWA::XChaCha20Poly1305.__register__(JOSE::JWA::XChaCha20Poly1305_RbNaCl)
16 changes: 16 additions & 0 deletions lib/jose/jwa/xchacha20poly1305_unsupported.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module JOSE::JWA::XChaCha20Poly1305_Unsupported

extend self

def __ruby__?; true; end
def __supported__?; false; end

def xchacha20poly1305_aead_encrypt(key, nonce, aad, plaintext)
raise NotImplementedError
end

def xchacha20poly1305_aead_decrypt(key, nonce, aad, ciphertext, tag)
raise NotImplementedError
end

end
8 changes: 8 additions & 0 deletions lib/jose/jwe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,8 @@ def self.from_fields(jwe, modules)
JOSE::JWE::ALG_AES_KW
when 'A128GCMKW', 'A192GCMKW', 'A256GCMKW'
JOSE::JWE::ALG_AES_GCM_KW
when 'C20PKW'
JOSE::JWE::ALG_C20P_KW
when 'dir'
JOSE::JWE::ALG_dir
when 'ECDH-ES', 'ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW'
Expand All @@ -1084,6 +1086,8 @@ def self.from_fields(jwe, modules)
JOSE::JWE::ALG_PBES2
when 'RSA1_5', 'RSA-OAEP', 'RSA-OAEP-256'
JOSE::JWE::ALG_RSA
when 'XC20PKW'
JOSE::JWE::ALG_XC20P_KW
else
raise ArgumentError, "unknown 'alg': #{jwe.fields['alg'].inspect}"
end
Expand All @@ -1095,6 +1099,10 @@ def self.from_fields(jwe, modules)
JOSE::JWE::ENC_AES_CBC_HMAC
when 'A128GCM', 'A192GCM', 'A256GCM'
JOSE::JWE::ENC_AES_GCM
when 'C20P'
JOSE::JWE::ENC_C20P
when 'XC20P'
JOSE::JWE::ENC_XC20P
else
raise ArgumentError, "unknown 'enc': #{jwe.fields['enc'].inspect}"
end
Expand Down
2 changes: 2 additions & 0 deletions lib/jose/jwe/alg.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ def self.generate_key(parameters, algorithm, encryption)

require 'jose/jwe/alg_aes_gcm_kw'
require 'jose/jwe/alg_aes_kw'
require 'jose/jwe/alg_c20p_kw'
require 'jose/jwe/alg_dir'
require 'jose/jwe/alg_ecdh_es'
require 'jose/jwe/alg_pbes2'
require 'jose/jwe/alg_rsa'
require 'jose/jwe/alg_xc20p_kw'
2 changes: 1 addition & 1 deletion lib/jose/jwe/alg_aes_gcm_kw.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def algorithm
when 256
'A256GCMKW'
else
raise ArgumentError, "unhandled JOSE::JWE::ALG_AES_KW bits: #{bits.inspect}"
raise ArgumentError, "unhandled JOSE::JWE::ALG_AES_GCM_KW bits: #{bits.inspect}"
end
end

Expand Down
Loading

0 comments on commit f3172a6

Please sign in to comment.