jwt-pq 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/jwt/pq.rb CHANGED
@@ -7,17 +7,58 @@ require_relative "pq/ml_dsa"
7
7
  require_relative "pq/key"
8
8
  require_relative "pq/algorithms/ml_dsa"
9
9
  require_relative "pq/jwk"
10
+ require_relative "pq/jwk_set"
11
+ require_relative "pq/jwks_loader"
10
12
  require_relative "pq/hybrid_key"
11
13
  require_relative "pq/algorithms/hybrid_eddsa"
12
14
 
13
15
  module JWT
16
+ # Post-quantum signature support for the ruby-jwt ecosystem.
17
+ #
18
+ # Provides ML-DSA (FIPS 204) signatures as JWT algorithms `ML-DSA-44`,
19
+ # `ML-DSA-65`, and `ML-DSA-87`, plus optional hybrid modes `EdDSA+ML-DSA-*`
20
+ # that concatenate an Ed25519 signature with an ML-DSA signature.
21
+ #
22
+ # @example Encode a JWT with ML-DSA-65
23
+ # key = JWT::PQ::Key.generate(:ml_dsa_65)
24
+ # token = JWT.encode({ sub: "user-1" }, key, "ML-DSA-65")
25
+ #
26
+ # @example Decode and verify
27
+ # decoded, _header = JWT.decode(token, key, true, algorithms: ["ML-DSA-65"])
28
+ #
29
+ # @see https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.pdf FIPS 204 (ML-DSA)
30
+ # @see https://datatracker.ietf.org/doc/draft-ietf-cose-dilithium/ draft-ietf-cose-dilithium
14
31
  module PQ
15
- # Whether jwt-eddsa / ed25519 is available for hybrid mode.
32
+ # Whether the `ed25519` (or `jwt-eddsa`) gem is available for hybrid mode.
33
+ #
34
+ # Hybrid `EdDSA+ML-DSA-*` algorithms require the `ed25519` gem at runtime.
35
+ # This method probes for it without raising, so callers can decide whether
36
+ # to offer hybrid options.
37
+ #
38
+ # @return [Boolean] true when `ed25519` can be required, false otherwise.
16
39
  def self.hybrid_available?
17
40
  require "ed25519"
18
41
  true
19
42
  rescue LoadError
20
43
  false
21
44
  end
45
+
46
+ # Drop the cache of liboqs signature handles held by this process.
47
+ #
48
+ # Call from a post-fork hook in clustered Rack/Rails servers so
49
+ # that child workers allocate fresh FFI handles instead of reusing
50
+ # pointers inherited from the parent via copy-on-write. The first
51
+ # sign/verify call in the child will lazily rebuild the handle.
52
+ #
53
+ # @example Puma (config/puma.rb)
54
+ # on_worker_boot { JWT::PQ.reset_handles! }
55
+ #
56
+ # @example Unicorn
57
+ # after_fork { |_server, _worker| JWT::PQ.reset_handles! }
58
+ #
59
+ # @return [void]
60
+ def self.reset_handles!
61
+ MlDsa.reset_handles!
62
+ end
22
63
  end
23
64
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jwt-pq
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marcelo Almeida
@@ -43,14 +43,14 @@ dependencies:
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '0.1'
46
+ version: 0.1.0
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '0.1'
53
+ version: 0.1.0
54
54
  description: Adds ML-DSA-44, ML-DSA-65, and ML-DSA-87 post-quantum signature algorithms
55
55
  to the ruby-jwt ecosystem, with optional hybrid EdDSA + ML-DSA mode. Uses liboqs
56
56
  via FFI.
@@ -61,15 +61,14 @@ extensions:
61
61
  - ext/jwt/pq/extconf.rb
62
62
  extra_rdoc_files: []
63
63
  files:
64
+ - ".yardopts"
64
65
  - CHANGELOG.md
65
66
  - Gemfile
66
67
  - LICENSE
67
68
  - README.md
68
69
  - Rakefile
69
- - bench/fixtures/ml_dsa_65_sk.pem
70
- - bench/sign_throughput.rb
71
- - bench/verify_throughput.rb
72
- - bin/smoke.rb
70
+ - SECURITY.md
71
+ - SPEC.md
73
72
  - ext/jwt/pq/extconf.rb
74
73
  - jwt-pq.gemspec
75
74
  - lib/jwt/pq.rb
@@ -78,6 +77,8 @@ files:
78
77
  - lib/jwt/pq/errors.rb
79
78
  - lib/jwt/pq/hybrid_key.rb
80
79
  - lib/jwt/pq/jwk.rb
80
+ - lib/jwt/pq/jwk_set.rb
81
+ - lib/jwt/pq/jwks_loader.rb
81
82
  - lib/jwt/pq/key.rb
82
83
  - lib/jwt/pq/liboqs.rb
83
84
  - lib/jwt/pq/ml_dsa.rb
@@ -1,128 +0,0 @@
1
- -----BEGIN PRIVATE KEY-----
2
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- UgUFciIVeHh3NAAnNnATcIRyQEg1YBFVAnIVVmgzgYNwBQA3hEUEJxZ2Q1CGIYch
16
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- JDFWVjcEMFZmUTgIhycmRBQAZgJWB3cmgTZhUWcDYSModHVGMCNiaHM0RWiBV2Mn
26
- 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
- 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
- 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
- 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
- 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
- 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
- F1JWcgY2NXIBNTVBQHJQAgQYYUIyUCMmITR1NQgCJVInUhYnMVZyBGgjUlEzQVQ2
33
- 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
- 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
- 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
- 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
- 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
- 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
- kiuR+ghw70I3qxkVEpRQ/m1LZMHnjk5+8DYmEtm8RTmzQybG5TF69a5/BN3i1KTb
40
- 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
- y/fiuOdPak842ilksrvz5pv6e4cZLkQB1UXRlopIdVMZQY6FV7M+z+q6ngcoFeUY
42
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- PuLrB5cLl3rIMDvo7Ean89GgVc18+hvydO00b08h458AnjG3luGQnyVqt1DaJzXb
50
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- bcqpNZ4KE/SiDWI7pBz+m7LQH3C5VnTMwBEdLET/6IQ36C9qbYbTnW+ZlMLC4v4g
58
- znIAMh0MD+whYV5ZDr4t9fYoHyqKfD2dxrbi5YZZo/bthgZ75vvNXeKgd5z7VqJ+
59
- 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
- MgYJzfSOPpbfzFBJbYtRP3CjkCFldmKDf+rjnzdKe045jT1/uLX0kKC7q1/s7nrG
61
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- LKyMEri31Ei42spUhafV5csQf7X7sFpvJ/JDuSZVmu56kSbvql+Akhfid/XTETTn
80
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- ePmdjCsciV/GM0nxbXGgKYtODlF7k6GfxHus7A/WQFF+3sFRmNxoJPkcNV/mqLc3
93
- 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
- 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
- 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
- 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
- Lcz/pSmbDJowUzAuL5CQ0FdIsQGFZeHPWhsb30G/2FpBcUeNlcELRnWFvRL8mj9q
98
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- NxcprWYxu0/05/tVvFbUgRAC937zAP0ugaXZ4o0b4zr9mOmZgymMjw0qZuSM2CRb
110
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
127
- aEqLBCQtoXub0y7W
128
- -----END PRIVATE KEY-----
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "benchmark/ips"
4
- require "jwt"
5
- require "jwt/pq"
6
-
7
- ALG = "ML-DSA-65"
8
- PAYLOAD = { sub: "user-123", iat: 1_700_000_000, exp: 1_700_003_600 }.freeze
9
- FIXTURE = File.expand_path("fixtures/ml_dsa_65_sk.pem", __dir__)
10
-
11
- abort "Missing bench fixture: #{FIXTURE}" unless File.exist?(FIXTURE)
12
- key = JWT::PQ::Key.from_pem(File.read(FIXTURE))
13
-
14
- 100.times { JWT.encode(PAYLOAD, key, ALG) }
15
-
16
- report = Benchmark.ips(quiet: true) do |x|
17
- x.config(time: 5, warmup: 2)
18
- x.report("sign") { JWT.encode(PAYLOAD, key, ALG) }
19
- end
20
-
21
- entry = report.entries.first
22
- ips = entry.stats.central_tendency
23
- us_per_op = 1_000_000.0 / ips
24
-
25
- puts "METRIC sigs_per_sec=#{ips.round(2)}"
26
- puts "METRIC us_per_sig=#{us_per_op.round(2)}"
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "benchmark/ips"
4
- require "jwt"
5
- require "jwt/pq"
6
-
7
- ALG = "ML-DSA-65"
8
- PAYLOAD = { sub: "user-123", iat: 1_700_000_000, exp: 1_700_003_600 }.freeze
9
- FIXTURE = File.expand_path("fixtures/ml_dsa_65_sk.pem", __dir__)
10
-
11
- abort "Missing bench fixture: #{FIXTURE}" unless File.exist?(FIXTURE)
12
- key = JWT::PQ::Key.from_pem(File.read(FIXTURE))
13
- pub_key = JWT::PQ::Key.from_public_key(ALG, key.public_key)
14
-
15
- TOKEN = JWT.encode(PAYLOAD, key, ALG)
16
-
17
- 100.times { JWT.decode(TOKEN, pub_key, true, algorithms: [ALG], verify_expiration: false) }
18
-
19
- report = Benchmark.ips(quiet: true) do |x|
20
- x.config(time: 5, warmup: 2)
21
- x.report("verify") { JWT.decode(TOKEN, pub_key, true, algorithms: [ALG], verify_expiration: false) }
22
- end
23
-
24
- entry = report.entries.first
25
- ips = entry.stats.central_tendency
26
- us_per_op = 1_000_000.0 / ips
27
-
28
- puts "METRIC verifies_per_sec=#{ips.round(2)}"
29
- puts "METRIC us_per_verify=#{us_per_op.round(2)}"
data/bin/smoke.rb DELETED
@@ -1,40 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require "jwt/pq"
5
-
6
- puts "jwt-pq v#{JWT::PQ::VERSION} smoke test"
7
-
8
- # ML-DSA-65 sign/verify round-trip
9
- key = JWT::PQ::Key.generate(:ml_dsa_65)
10
- token = JWT.encode({ "sub" => "smoke-test" }, key, "ML-DSA-65")
11
- decoded = JWT.decode(token, key, true, algorithms: ["ML-DSA-65"])
12
-
13
- raise "Decode failed" unless decoded.first["sub"] == "smoke-test"
14
-
15
- puts "ML-DSA-65 sign/verify: OK"
16
-
17
- # PEM round-trip
18
- pub_pem = key.to_pem
19
- restored = JWT::PQ::Key.from_pem(pub_pem)
20
- decoded = JWT.decode(token, restored, true, algorithms: ["ML-DSA-65"])
21
-
22
- raise "PEM round-trip failed" unless decoded.first["sub"] == "smoke-test"
23
-
24
- puts "PEM round-trip: OK"
25
-
26
- # JWK round-trip
27
- jwk = JWT::PQ::JWK.new(key)
28
- imported = JWT::PQ::JWK.import(jwk.export)
29
- decoded = JWT.decode(token, imported, true, algorithms: ["ML-DSA-65"])
30
-
31
- raise "JWK round-trip failed" unless decoded.first["sub"] == "smoke-test"
32
-
33
- puts "JWK round-trip: OK"
34
-
35
- # Key destroy
36
- key.destroy!
37
- raise "destroy! failed" if key.private?
38
-
39
- puts "Key#destroy!: OK"
40
- puts "All smoke tests passed"