There is a concurrency issue when encoding and decoding across many threads.
The report below was against JRuby, but I confirmed it also happens on TruffleRuby, another parallel-threaded Ruby implementation. The fact that it fails on both of those indicated to me that it was not an issue in our respective openssl library implementations, but instead an issue in JWT.
It does not appear to affect CRuby.
I have a fix in progress.
Originally reported by @mohamedhafez to JRuby at jruby/jruby#8504.
The following script works just fine with jwt version 2.8.2 on CRuby 3.1.6 and JRuby 9.4.9.0 with jruby-openssl version 0.15.2. However if I upgrade jwt to 2.9.3, it will fail with JWT::VerificationError: Signature verification failed message in JRuby, while it continues to run just fine on C-Ruby.
require 'jwt'
require 'openssl'
puts "JWT version: #{JWT::VERSION::STRING}"
puts "OpenSSL version: #{OpenSSL::OPENSSL_VERSION}"
threads = []
10.times do
thread = Thread.new {
#generated on C-Ruby 3.1.6 with the following code. some of the methods not available in JRuby so had to generate offline
#curve = OpenSSL::PKey::EC.generate('prime256v1')
#curve.public_to_pem
#curve.private_to_pem
public_key_pem = "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcKuFOqoNEN+TXylz4MVAWREa9yA8\npOF9QgGchnAy6Ad4P7yCpk+R3wCGTDLfNboYqUmbK5Hd9uHszf+EMTi22g==\n-----END PUBLIC KEY-----\n"
private_key_pem = "-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgiF/iNuQem/yQyd16\nc9shf2Y9vMycOU7g6W6LTmkyj1ehRANCAARwq4U6qg0Q35NfKXPgxUBZERr3IDyk\n4X1CAZyGcDLoB3g/vIKmT5HfAIZMMt81uhipSZsrkd324ezN/4QxOLba\n-----END PRIVATE KEY-----\n"
full_pem = private_key_pem + public_key_pem
curve = OpenSSL::PKey.read(full_pem)
public_key = OpenSSL::PKey::EC.new(public_key_pem)
loop do
input_payload = {"aud" => "https://fcm.googleapis.com", "exp" => (Time.now.to_i + 600), "sub" => "mailto:example@example.com"}
input_header = { "typ" => "JWT", "alg" => "ES256" }
token = JWT.encode(input_payload, curve, 'ES256', input_header)
output_payload, output_header = JWT.decode(token, public_key, true, { algorithm: 'ES256', verify_expiration: true })
raise "output doesnt match input!" unless output_payload == input_payload && output_header == input_header
end
}
thread.abort_on_exception = true
threads << thread
end
loop do
sleep 10
puts "all threads still alive" if threads.all?(&:alive?)
end
The stack trace:
JWT::VerificationError: Signature verification failed
verify_signature at /Users/mohamed/.rbenv/versions/jruby-9.4.9.0/lib/ruby/gems/shared/gems/jwt-2.9.3/lib/jwt/decode.rb:47
decode_segments at /Users/mohamed/.rbenv/versions/jruby-9.4.9.0/lib/ruby/gems/shared/gems/jwt-2.9.3/lib/jwt/decode.rb:28
decode at /Users/mohamed/.rbenv/versions/jruby-9.4.9.0/lib/ruby/gems/shared/gems/jwt-2.9.3/lib/jwt.rb:35
context at /Users/mohamed/.rbenv/versions/jruby-9.4.9.0/lib/ruby/gems/shared/gems/jwt-2.9.3/lib/jwt/deprecations.rb:8
decode at /Users/mohamed/.rbenv/versions/jruby-9.4.9.0/lib/ruby/gems/shared/gems/jwt-2.9.3/lib/jwt.rb:34
<main> at /Users/mohamed/Library/Application Support/JetBrains/RubyMine2024.3/scratches/scratch.rb:26
loop at org/jruby/RubyKernel.java:1725
<main> at /Users/mohamed/Library/Application Support/JetBrains/RubyMine2024.3/scratches/scratch.rb:21
This only happens when running multiple threads all calling JWT.encode, if you edit the script above to only have 1 thread, then it'll run for millions of iterations without issue.
There is a concurrency issue when encoding and decoding across many threads.
The report below was against JRuby, but I confirmed it also happens on TruffleRuby, another parallel-threaded Ruby implementation. The fact that it fails on both of those indicated to me that it was not an issue in our respective
openssllibrary implementations, but instead an issue in JWT.It does not appear to affect CRuby.
I have a fix in progress.
Originally reported by @mohamedhafez to JRuby at jruby/jruby#8504.