Skip to content

JWT.encode creates invalid signatures when called from multiple threads for jwt gem > 2.8.2 #8504

Description

@mohamedhafez

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.

I'd guess it's probably an issue with jruby-openssl, but don't actually know so I created the issue here. feel free to close this issue move it to the jruby-openssl repo if that's more appropriate!

Hoping to get this fixed to stop encountering pushpad/web-push#16

Environment Information

jruby 9.4.9.0 (3.1.4) 2024-11-04 547c6b150e OpenJDK 64-Bit Server VM 21.0.5+11-LTS on 21.0.5+11-LTS +jit [arm64-darwin]
Darwin macbookpro.lan 24.1.0 Darwin Kernel Version 24.1.0: Thu Oct 10 21:03:15 PDT 2024; root:xnu-11215.41.3~2/RELEASE_ARM64_T6000 arm64
and
jruby 9.4.9.0 (3.1.4) 2024-11-04 547c6b150e OpenJDK 64-Bit Server VM 21.0.5+11-LTS on 21.0.5+11-LTS +jit [aarch64-linux]
Linux ip-172-30-0-10 6.8.0-1019-aws #21~22.04.1-Ubuntu SMP Thu Nov 7 17:35:43 UTC 2024 aarch64 aarch64 aarch64 GNU/Linux

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions