json-jwt 1.9.1 → 1.17.2
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.
- checksums.yaml +5 -5
- data/.github/FUNDING.yml +3 -0
- data/.github/workflows/spec.yml +31 -0
- data/.gitmodules +1 -1
- data/CHANGELOG.md +17 -0
- data/Gemfile +1 -1
- data/README.md +11 -2
- data/VERSION +1 -1
- data/json-jwt.gemspec +10 -5
- data/lib/json/jose.rb +22 -11
- data/lib/json/jwe.rb +55 -38
- data/lib/json/jwk/jwkizable.rb +13 -11
- data/lib/json/jwk/set/fetcher.rb +93 -0
- data/lib/json/jwk/set.rb +7 -1
- data/lib/json/jwk.rb +62 -34
- data/lib/json/jws.rb +38 -35
- data/lib/json/jwt.rb +43 -17
- metadata +55 -56
- data/.travis.yml +0 -11
- data/spec/fixtures/ecdsa/256/private_key.pem +0 -5
- data/spec/fixtures/ecdsa/256/public_key.pem +0 -4
- data/spec/fixtures/ecdsa/384/private_key.pem +0 -6
- data/spec/fixtures/ecdsa/384/public_key.pem +0 -5
- data/spec/fixtures/ecdsa/512/private_key.pem +0 -7
- data/spec/fixtures/ecdsa/512/public_key.pem +0 -6
- data/spec/fixtures/rsa/private_key.der +0 -0
- data/spec/fixtures/rsa/private_key.pem +0 -30
- data/spec/fixtures/rsa/public_key.pem +0 -8
- data/spec/helpers/nimbus_spec_helper.rb +0 -22
- data/spec/helpers/sign_key_fixture_helper.rb +0 -52
- data/spec/interop/with_jsrsasign_spec.rb +0 -49
- data/spec/interop/with_nimbus_jose_spec.rb +0 -99
- data/spec/interop/with_rfc_example_spec.rb +0 -19
- data/spec/json/jwe_spec.rb +0 -311
- data/spec/json/jwk/jwkizable_spec.rb +0 -49
- data/spec/json/jwk/set_spec.rb +0 -75
- data/spec/json/jwk_spec.rb +0 -194
- data/spec/json/jws_spec.rb +0 -324
- data/spec/json/jwt_spec.rb +0 -496
- data/spec/spec_helper.rb +0 -28
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c53a91e5d33b3d3faa375c424a1aacba227456756da3eafe1794014ee69dae44
|
|
4
|
+
data.tar.gz: 5edb64703e8c0a0b91bceeff2d6313abbfbd86eee1623567cd2335e2cea7b1da
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b1f8b09219b80b0e2350450522e53ead2dc344662617e517916063cc7a08f5c9dff5ba9d81f3ff3ebabadae708c71286db4b086d5093af0fb459cf1793ae3c28
|
|
7
|
+
data.tar.gz: da998f84ffaf2996b7ef02d9ad2f4aff6067ec14c352bb0f0f0811474506f00212171f8623363462b03dbe2f17832cb3873b9882dd70c5a136e4ba97e7b61217
|
data/.github/FUNDING.yml
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Spec
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
spec:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
name: Ruby ${{ matrix.ruby }}
|
|
16
|
+
strategy:
|
|
17
|
+
matrix:
|
|
18
|
+
ruby:
|
|
19
|
+
- '3.2'
|
|
20
|
+
- '3.3'
|
|
21
|
+
- '3.4'
|
|
22
|
+
- '4.0'
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v3
|
|
25
|
+
- name: Set up Ruby
|
|
26
|
+
uses: ruby/setup-ruby@v1
|
|
27
|
+
with:
|
|
28
|
+
ruby-version: ${{ matrix.ruby }}
|
|
29
|
+
bundler-cache: true
|
|
30
|
+
- name: Run Specs
|
|
31
|
+
run: bundle exec rake spec
|
data/.gitmodules
CHANGED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
## [1.16.0] - 2022-10-08
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Remove padding oracle by @btoews in https://github.com/nov/json-jwt/pull/109
|
|
8
|
+
|
|
9
|
+
## [1.16.0] - 2022-10-08
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- start recording CHANGELOG
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
* Switch from httpclient to faraday v2 https://github.com/nov/json-jwt/pull/110
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
JSON Web Token and its family (JSON Web Signature, JSON Web Encryption and JSON Web Key) in Ruby
|
|
4
4
|
|
|
5
|
-
[](http://travis-ci.org/nov/json-jwt)
|
|
6
|
-
|
|
7
5
|
## Installation
|
|
8
6
|
|
|
9
7
|
```
|
|
@@ -49,6 +47,17 @@ input = "jwt_header.jwt_claims.jwt_signature"
|
|
|
49
47
|
JSON::JWT.decode(input, public_key)
|
|
50
48
|
```
|
|
51
49
|
|
|
50
|
+
If you need to get a JWK from `jwks_uri` of OpenID Connect IdP, you can use `JSON::JWK::Set::Fetcher` to fetch (& optionally cache) it.
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
# JWK Set Fetching & Caching
|
|
54
|
+
# NOTE: Optionally by setting cache instance, JWKs are cached by kid.
|
|
55
|
+
JSON::JWK::Set::Fetcher.cache = Rails.cache
|
|
56
|
+
|
|
57
|
+
JSON::JWK::Set::Fetcher.fetch(jwks_uri, kid: kid)
|
|
58
|
+
# => returns JSON::JWK instance or raise JSON::JWK::Set::KidNotFound
|
|
59
|
+
```
|
|
60
|
+
|
|
52
61
|
For more details, read [Documentation Wiki](https://github.com/nov/json-jwt/wiki).
|
|
53
62
|
|
|
54
63
|
## Note on Patches/Pull Requests
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.17.2
|
data/json-jwt.gemspec
CHANGED
|
@@ -7,16 +7,21 @@ Gem::Specification.new do |gem|
|
|
|
7
7
|
gem.summary = %q{JSON Web Token and its family (JSON Web Signature, JSON Web Encryption and JSON Web Key) in Ruby}
|
|
8
8
|
gem.description = %q{JSON Web Token and its family (JSON Web Signature, JSON Web Encryption and JSON Web Key) in Ruby}
|
|
9
9
|
gem.license = 'MIT'
|
|
10
|
-
gem.files = `git ls-files`.split("\n")
|
|
11
|
-
|
|
10
|
+
gem.files = `git ls-files`.split("\n").reject do |f|
|
|
11
|
+
f.match(%r{^(test|spec|features)/})
|
|
12
|
+
end
|
|
12
13
|
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
13
14
|
gem.require_paths = ['lib']
|
|
14
|
-
gem.
|
|
15
|
-
gem.add_runtime_dependency 'activesupport'
|
|
15
|
+
gem.required_ruby_version = '>= 2.4'
|
|
16
|
+
gem.add_runtime_dependency 'activesupport', '>= 4.2'
|
|
17
|
+
gem.add_runtime_dependency 'base64'
|
|
16
18
|
gem.add_runtime_dependency 'bindata'
|
|
17
|
-
gem.add_runtime_dependency '
|
|
19
|
+
gem.add_runtime_dependency 'aes_key_wrap'
|
|
20
|
+
gem.add_runtime_dependency 'faraday', '~> 2.0'
|
|
21
|
+
gem.add_runtime_dependency 'faraday-follow_redirects'
|
|
18
22
|
gem.add_development_dependency 'rake'
|
|
19
23
|
gem.add_development_dependency 'simplecov'
|
|
24
|
+
gem.add_development_dependency 'webmock'
|
|
20
25
|
gem.add_development_dependency 'rspec'
|
|
21
26
|
gem.add_development_dependency 'rspec-its'
|
|
22
27
|
end
|
data/lib/json/jose.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'active_support/security_utils'
|
|
2
2
|
|
|
3
3
|
module JSON
|
|
4
4
|
module JOSE
|
|
@@ -6,11 +6,12 @@ module JSON
|
|
|
6
6
|
|
|
7
7
|
included do
|
|
8
8
|
extend ClassMethods
|
|
9
|
-
include SecureCompare
|
|
10
9
|
register_header_keys :alg, :jku, :jwk, :x5u, :x5t, :x5c, :kid, :typ, :cty, :crit
|
|
10
|
+
|
|
11
|
+
# NOTE: not used anymore in this gem, but keeping in case developers are calling it.
|
|
11
12
|
alias_method :algorithm, :alg
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
attr_writer :header
|
|
14
15
|
def header
|
|
15
16
|
@header ||= {}
|
|
16
17
|
end
|
|
@@ -25,14 +26,24 @@ module JSON
|
|
|
25
26
|
when JSON::JWK
|
|
26
27
|
key.to_key
|
|
27
28
|
when JSON::JWK::Set
|
|
28
|
-
key
|
|
29
|
-
jwk[:kid] && jwk[:kid] == kid
|
|
30
|
-
end.try(:to_key) or raise JWK::Set::KidNotFound
|
|
29
|
+
key[kid]&.to_key or raise JWK::Set::KidNotFound
|
|
31
30
|
else
|
|
32
31
|
key
|
|
33
32
|
end
|
|
34
33
|
end
|
|
35
34
|
|
|
35
|
+
def secure_compare(a, b)
|
|
36
|
+
if ActiveSupport::SecurityUtils.respond_to?(:fixed_length_secure_compare)
|
|
37
|
+
begin
|
|
38
|
+
ActiveSupport::SecurityUtils.fixed_length_secure_compare(a, b)
|
|
39
|
+
rescue ArgumentError
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
else
|
|
43
|
+
ActiveSupport::SecurityUtils.secure_compare(a, b)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
36
47
|
module ClassMethods
|
|
37
48
|
def register_header_keys(*keys)
|
|
38
49
|
keys.each do |header_key|
|
|
@@ -45,15 +56,15 @@ module JSON
|
|
|
45
56
|
end
|
|
46
57
|
end
|
|
47
58
|
|
|
48
|
-
def decode(input, key_or_secret = nil, algorithms = nil, encryption_methods = nil)
|
|
59
|
+
def decode(input, key_or_secret = nil, algorithms = nil, encryption_methods = nil, allow_blank_payload = false)
|
|
49
60
|
if input.is_a? Hash
|
|
50
|
-
decode_json_serialized input, key_or_secret, algorithms, encryption_methods
|
|
61
|
+
decode_json_serialized input, key_or_secret, algorithms, encryption_methods, allow_blank_payload
|
|
51
62
|
else
|
|
52
|
-
decode_compact_serialized input, key_or_secret, algorithms, encryption_methods
|
|
63
|
+
decode_compact_serialized input, key_or_secret, algorithms, encryption_methods, allow_blank_payload
|
|
53
64
|
end
|
|
54
|
-
rescue JSON::ParserError
|
|
65
|
+
rescue JSON::ParserError, ArgumentError
|
|
55
66
|
raise JWT::InvalidFormat.new("Invalid JSON Format")
|
|
56
67
|
end
|
|
57
68
|
end
|
|
58
69
|
end
|
|
59
|
-
end
|
|
70
|
+
end
|
data/lib/json/jwe.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require 'securerandom'
|
|
2
2
|
require 'bindata'
|
|
3
|
+
require 'aes_key_wrap'
|
|
3
4
|
|
|
4
5
|
module JSON
|
|
5
6
|
class JWE
|
|
@@ -13,9 +14,10 @@ module JSON
|
|
|
13
14
|
|
|
14
15
|
attr_accessor(
|
|
15
16
|
:public_key_or_secret, :private_key_or_secret,
|
|
16
|
-
:plain_text, :cipher_text, :
|
|
17
|
-
:content_encryption_key, :
|
|
17
|
+
:plain_text, :cipher_text, :iv, :auth_data,
|
|
18
|
+
:content_encryption_key, :encryption_key, :mac_key
|
|
18
19
|
)
|
|
20
|
+
attr_writer :jwe_encrypted_key, :authentication_tag
|
|
19
21
|
|
|
20
22
|
register_header_keys :enc, :epk, :zip, :apu, :apv
|
|
21
23
|
alias_method :encryption_method, :enc
|
|
@@ -31,7 +33,7 @@ module JSON
|
|
|
31
33
|
self.mac_key, self.encryption_key = derive_encryption_and_mac_keys
|
|
32
34
|
cipher.key = encryption_key
|
|
33
35
|
self.iv = cipher.random_iv # NOTE: 'iv' has to be set after 'key' for GCM
|
|
34
|
-
self.auth_data =
|
|
36
|
+
self.auth_data = Base64.urlsafe_encode64 header.to_json, padding: false
|
|
35
37
|
cipher.auth_data = auth_data if gcm?
|
|
36
38
|
self.cipher_text = cipher.update(plain_text) + cipher.final
|
|
37
39
|
self
|
|
@@ -41,17 +43,29 @@ module JSON
|
|
|
41
43
|
raise UnexpectedAlgorithm.new('Unexpected alg header') unless algorithms.blank? || Array(algorithms).include?(alg)
|
|
42
44
|
raise UnexpectedAlgorithm.new('Unexpected enc header') unless encryption_methods.blank? || Array(encryption_methods).include?(enc)
|
|
43
45
|
self.private_key_or_secret = with_jwk_support private_key_or_secret
|
|
44
|
-
cipher.decrypt
|
|
45
46
|
self.content_encryption_key = decrypt_content_encryption_key
|
|
46
47
|
self.mac_key, self.encryption_key = derive_encryption_and_mac_keys
|
|
48
|
+
|
|
49
|
+
verify_cbc_authentication_tag! if cbc?
|
|
50
|
+
|
|
51
|
+
cipher.decrypt
|
|
47
52
|
cipher.key = encryption_key
|
|
48
53
|
cipher.iv = iv # NOTE: 'iv' has to be set after 'key' for GCM
|
|
49
54
|
if gcm?
|
|
55
|
+
# https://github.com/ruby/openssl/issues/63
|
|
56
|
+
raise DecryptionFailed.new('Invalid authentication tag') if authentication_tag.length < 16
|
|
50
57
|
cipher.auth_tag = authentication_tag
|
|
51
58
|
cipher.auth_data = auth_data
|
|
52
59
|
end
|
|
53
|
-
|
|
54
|
-
|
|
60
|
+
|
|
61
|
+
begin
|
|
62
|
+
self.plain_text = cipher.update(cipher_text) + cipher.final
|
|
63
|
+
rescue OpenSSL::OpenSSLError
|
|
64
|
+
# Ensure that the same error is raised for invalid PKCS7 padding
|
|
65
|
+
# as for invalid signatures. This prevents padding-oracle attacks.
|
|
66
|
+
raise DecryptionFailed
|
|
67
|
+
end
|
|
68
|
+
|
|
55
69
|
self
|
|
56
70
|
end
|
|
57
71
|
|
|
@@ -63,7 +77,7 @@ module JSON
|
|
|
63
77
|
cipher_text,
|
|
64
78
|
authentication_tag
|
|
65
79
|
].collect do |segment|
|
|
66
|
-
|
|
80
|
+
Base64.urlsafe_encode64 segment.to_s, padding: false
|
|
67
81
|
end.join('.')
|
|
68
82
|
end
|
|
69
83
|
|
|
@@ -71,21 +85,21 @@ module JSON
|
|
|
71
85
|
case options[:syntax]
|
|
72
86
|
when :general
|
|
73
87
|
{
|
|
74
|
-
protected:
|
|
88
|
+
protected: Base64.urlsafe_encode64(header.to_json, padding: false),
|
|
75
89
|
recipients: [{
|
|
76
|
-
encrypted_key:
|
|
90
|
+
encrypted_key: Base64.urlsafe_encode64(jwe_encrypted_key, padding: false)
|
|
77
91
|
}],
|
|
78
|
-
iv:
|
|
79
|
-
ciphertext:
|
|
80
|
-
tag:
|
|
92
|
+
iv: Base64.urlsafe_encode64(iv, padding: false),
|
|
93
|
+
ciphertext: Base64.urlsafe_encode64(cipher_text, padding: false),
|
|
94
|
+
tag: Base64.urlsafe_encode64(authentication_tag, padding: false)
|
|
81
95
|
}
|
|
82
96
|
else
|
|
83
97
|
{
|
|
84
|
-
protected:
|
|
85
|
-
encrypted_key:
|
|
86
|
-
iv:
|
|
87
|
-
ciphertext:
|
|
88
|
-
tag:
|
|
98
|
+
protected: Base64.urlsafe_encode64(header.to_json, padding: false),
|
|
99
|
+
encrypted_key: Base64.urlsafe_encode64(jwe_encrypted_key, padding: false),
|
|
100
|
+
iv: Base64.urlsafe_encode64(iv, padding: false),
|
|
101
|
+
ciphertext: Base64.urlsafe_encode64(cipher_text, padding: false),
|
|
102
|
+
tag: Base64.urlsafe_encode64(authentication_tag, padding: false)
|
|
89
103
|
}
|
|
90
104
|
end
|
|
91
105
|
end
|
|
@@ -95,15 +109,15 @@ module JSON
|
|
|
95
109
|
# common
|
|
96
110
|
|
|
97
111
|
def gcm?
|
|
98
|
-
[:A128GCM, :A256GCM].include? encryption_method
|
|
112
|
+
[:A128GCM, :A256GCM].include? encryption_method&.to_sym
|
|
99
113
|
end
|
|
100
114
|
|
|
101
115
|
def cbc?
|
|
102
|
-
[:'A128CBC-HS256', :'A256CBC-HS512'].include? encryption_method
|
|
116
|
+
[:'A128CBC-HS256', :'A256CBC-HS512'].include? encryption_method&.to_sym
|
|
103
117
|
end
|
|
104
118
|
|
|
105
119
|
def dir?
|
|
106
|
-
:dir ==
|
|
120
|
+
:dir == alg&.to_sym
|
|
107
121
|
end
|
|
108
122
|
|
|
109
123
|
def cipher
|
|
@@ -112,7 +126,7 @@ module JSON
|
|
|
112
126
|
end
|
|
113
127
|
|
|
114
128
|
def cipher_name
|
|
115
|
-
case encryption_method
|
|
129
|
+
case encryption_method&.to_sym
|
|
116
130
|
when :A128GCM
|
|
117
131
|
'aes-128-gcm'
|
|
118
132
|
when :A256GCM
|
|
@@ -127,7 +141,7 @@ module JSON
|
|
|
127
141
|
end
|
|
128
142
|
|
|
129
143
|
def sha_size
|
|
130
|
-
case encryption_method
|
|
144
|
+
case encryption_method&.to_sym
|
|
131
145
|
when :'A128CBC-HS256'
|
|
132
146
|
256
|
|
133
147
|
when :'A256CBC-HS512'
|
|
@@ -155,15 +169,13 @@ module JSON
|
|
|
155
169
|
# encryption
|
|
156
170
|
|
|
157
171
|
def jwe_encrypted_key
|
|
158
|
-
@jwe_encrypted_key ||= case
|
|
172
|
+
@jwe_encrypted_key ||= case alg&.to_sym
|
|
159
173
|
when :RSA1_5
|
|
160
174
|
public_key_or_secret.public_encrypt content_encryption_key
|
|
161
175
|
when :'RSA-OAEP'
|
|
162
176
|
public_key_or_secret.public_encrypt content_encryption_key, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
|
|
163
|
-
when :A128KW
|
|
164
|
-
|
|
165
|
-
when :A256KW
|
|
166
|
-
raise NotImplementedError.new('A256KW not supported yet')
|
|
177
|
+
when :A128KW, :A256KW
|
|
178
|
+
AESKeyWrap.wrap content_encryption_key, public_key_or_secret
|
|
167
179
|
when :dir
|
|
168
180
|
''
|
|
169
181
|
when :'ECDH-ES'
|
|
@@ -209,15 +221,13 @@ module JSON
|
|
|
209
221
|
|
|
210
222
|
def decrypt_content_encryption_key
|
|
211
223
|
fake_content_encryption_key = generate_content_encryption_key # NOTE: do this always not to make timing difference
|
|
212
|
-
case
|
|
224
|
+
case alg&.to_sym
|
|
213
225
|
when :RSA1_5
|
|
214
226
|
private_key_or_secret.private_decrypt jwe_encrypted_key
|
|
215
227
|
when :'RSA-OAEP'
|
|
216
228
|
private_key_or_secret.private_decrypt jwe_encrypted_key, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
|
|
217
|
-
when :A128KW
|
|
218
|
-
|
|
219
|
-
when :A256KW
|
|
220
|
-
raise NotImplementedError.new('A256KW not supported yet')
|
|
229
|
+
when :A128KW, :A256KW
|
|
230
|
+
AESKeyWrap.unwrap jwe_encrypted_key, private_key_or_secret
|
|
221
231
|
when :dir
|
|
222
232
|
private_key_or_secret
|
|
223
233
|
when :'ECDH-ES'
|
|
@@ -244,18 +254,25 @@ module JSON
|
|
|
244
254
|
sha_digest, mac_key, secured_input
|
|
245
255
|
)[0, sha_size / 2 / 8]
|
|
246
256
|
unless secure_compare(authentication_tag, expected_authentication_tag)
|
|
247
|
-
raise DecryptionFailed
|
|
257
|
+
raise DecryptionFailed
|
|
248
258
|
end
|
|
249
259
|
end
|
|
250
260
|
|
|
251
261
|
class << self
|
|
252
|
-
def decode_compact_serialized(input, private_key_or_secret, algorithms = nil, encryption_methods = nil)
|
|
262
|
+
def decode_compact_serialized(input, private_key_or_secret, algorithms = nil, encryption_methods = nil, allow_blank_payload = false)
|
|
263
|
+
if allow_blank_payload
|
|
264
|
+
raise InvalidFormat.new("JWE w/ blank payload is not supported.")
|
|
265
|
+
end
|
|
253
266
|
unless input.count('.') + 1 == NUM_OF_SEGMENTS
|
|
254
267
|
raise InvalidFormat.new("Invalid JWE Format. JWE should include #{NUM_OF_SEGMENTS} segments.")
|
|
255
268
|
end
|
|
256
269
|
jwe = new
|
|
257
|
-
_header_json_, jwe.jwe_encrypted_key, jwe.iv, jwe.cipher_text, jwe.authentication_tag = input.split('.').collect do |segment|
|
|
258
|
-
|
|
270
|
+
_header_json_, jwe.jwe_encrypted_key, jwe.iv, jwe.cipher_text, jwe.authentication_tag = input.split('.', NUM_OF_SEGMENTS).collect do |segment|
|
|
271
|
+
begin
|
|
272
|
+
Base64.urlsafe_decode64 segment
|
|
273
|
+
rescue ArgumentError
|
|
274
|
+
raise DecryptionFailed
|
|
275
|
+
end
|
|
259
276
|
end
|
|
260
277
|
jwe.auth_data = input.split('.').first
|
|
261
278
|
jwe.header = JSON.parse(_header_json_).with_indifferent_access
|
|
@@ -265,7 +282,7 @@ module JSON
|
|
|
265
282
|
jwe
|
|
266
283
|
end
|
|
267
284
|
|
|
268
|
-
def decode_json_serialized(input, private_key_or_secret, algorithms = nil, encryption_methods = nil)
|
|
285
|
+
def decode_json_serialized(input, private_key_or_secret, algorithms = nil, encryption_methods = nil, allow_blank_payload = false)
|
|
269
286
|
input = input.with_indifferent_access
|
|
270
287
|
jwe_encrypted_key = if input[:recipients].present?
|
|
271
288
|
input[:recipients].first[:encrypted_key]
|
|
@@ -279,7 +296,7 @@ module JSON
|
|
|
279
296
|
input[:ciphertext],
|
|
280
297
|
input[:tag]
|
|
281
298
|
].join('.')
|
|
282
|
-
decode_compact_serialized compact_serialized, private_key_or_secret, algorithms, encryption_methods
|
|
299
|
+
decode_compact_serialized compact_serialized, private_key_or_secret, algorithms, encryption_methods, allow_blank_payload
|
|
283
300
|
end
|
|
284
301
|
end
|
|
285
302
|
end
|
data/lib/json/jwk/jwkizable.rb
CHANGED
|
@@ -5,17 +5,17 @@ module JSON
|
|
|
5
5
|
def to_jwk(ex_params = {})
|
|
6
6
|
params = {
|
|
7
7
|
kty: :RSA,
|
|
8
|
-
e:
|
|
9
|
-
n:
|
|
8
|
+
e: Base64.urlsafe_encode64(e.to_s(2), padding: false),
|
|
9
|
+
n: Base64.urlsafe_encode64(n.to_s(2), padding: false)
|
|
10
10
|
}.merge ex_params
|
|
11
11
|
if private?
|
|
12
12
|
params.merge!(
|
|
13
|
-
d:
|
|
14
|
-
p:
|
|
15
|
-
q:
|
|
16
|
-
dp:
|
|
17
|
-
dq:
|
|
18
|
-
qi:
|
|
13
|
+
d: Base64.urlsafe_encode64(d.to_s(2), padding: false),
|
|
14
|
+
p: Base64.urlsafe_encode64(p.to_s(2), padding: false),
|
|
15
|
+
q: Base64.urlsafe_encode64(q.to_s(2), padding: false),
|
|
16
|
+
dp: Base64.urlsafe_encode64(dmp1.to_s(2), padding: false),
|
|
17
|
+
dq: Base64.urlsafe_encode64(dmq1.to_s(2), padding: false),
|
|
18
|
+
qi: Base64.urlsafe_encode64(iqmp.to_s(2), padding: false),
|
|
19
19
|
)
|
|
20
20
|
end
|
|
21
21
|
JWK.new params
|
|
@@ -27,10 +27,10 @@ module JSON
|
|
|
27
27
|
params = {
|
|
28
28
|
kty: :EC,
|
|
29
29
|
crv: curve_name,
|
|
30
|
-
x:
|
|
31
|
-
y:
|
|
30
|
+
x: Base64.urlsafe_encode64([coordinates[:x]].pack('H*'), padding: false),
|
|
31
|
+
y: Base64.urlsafe_encode64([coordinates[:y]].pack('H*'), padding: false)
|
|
32
32
|
}.merge ex_params
|
|
33
|
-
params[:d] =
|
|
33
|
+
params[:d] = Base64.urlsafe_encode64([coordinates[:d]].pack('H*'), padding: false) if private_key?
|
|
34
34
|
JWK.new params
|
|
35
35
|
end
|
|
36
36
|
|
|
@@ -44,6 +44,8 @@ module JSON
|
|
|
44
44
|
:'P-384'
|
|
45
45
|
when 'secp521r1'
|
|
46
46
|
:'P-521'
|
|
47
|
+
when 'secp256k1'
|
|
48
|
+
:secp256k1
|
|
47
49
|
else
|
|
48
50
|
raise UnknownAlgorithm.new('Unknown EC Curve')
|
|
49
51
|
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
module JSON
|
|
2
|
+
class JWK
|
|
3
|
+
class Set
|
|
4
|
+
module Fetcher
|
|
5
|
+
class UnexpectedFormat < JWT::Exception; end
|
|
6
|
+
|
|
7
|
+
class Cache
|
|
8
|
+
def fetch(cache_key, options = {})
|
|
9
|
+
yield
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def delete(cache_key, options = {}); end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.logger
|
|
16
|
+
@@logger
|
|
17
|
+
end
|
|
18
|
+
def self.logger=(logger)
|
|
19
|
+
@@logger = logger
|
|
20
|
+
end
|
|
21
|
+
self.logger = Logger.new(STDOUT)
|
|
22
|
+
self.logger.progname = 'JSON::JWK::Set::Fetcher'
|
|
23
|
+
|
|
24
|
+
def self.debugging?
|
|
25
|
+
@@debugging
|
|
26
|
+
end
|
|
27
|
+
def self.debugging=(boolean)
|
|
28
|
+
@@debugging = boolean
|
|
29
|
+
end
|
|
30
|
+
def self.debug!
|
|
31
|
+
self.debugging = true
|
|
32
|
+
end
|
|
33
|
+
def self.debug(&block)
|
|
34
|
+
original = self.debugging?
|
|
35
|
+
debug!
|
|
36
|
+
yield
|
|
37
|
+
ensure
|
|
38
|
+
self.debugging = original
|
|
39
|
+
end
|
|
40
|
+
self.debugging = false
|
|
41
|
+
|
|
42
|
+
def self.http_client
|
|
43
|
+
Faraday.new(headers: {user_agent: "JSON::JWK::Set::Fetcher #{VERSION}"}) do |faraday|
|
|
44
|
+
faraday.response :raise_error
|
|
45
|
+
faraday.response :follow_redirects
|
|
46
|
+
faraday.response :logger, JSON::JWK::Set::Fetcher.logger if debugging?
|
|
47
|
+
faraday.adapter Faraday.default_adapter
|
|
48
|
+
http_config.try(:call, faraday)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
def self.http_config(&block)
|
|
52
|
+
@@http_config ||= block
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.cache=(cache)
|
|
56
|
+
@@cache = cache
|
|
57
|
+
end
|
|
58
|
+
def self.cache
|
|
59
|
+
@@cache
|
|
60
|
+
end
|
|
61
|
+
self.cache = Cache.new
|
|
62
|
+
|
|
63
|
+
def self.fetch(jwks_uri, kid:, auto_detect: true, **options)
|
|
64
|
+
cache_key = [
|
|
65
|
+
'json:jwk:set',
|
|
66
|
+
OpenSSL::Digest::MD5.hexdigest(jwks_uri),
|
|
67
|
+
kid
|
|
68
|
+
].collect(&:to_s).join(':')
|
|
69
|
+
|
|
70
|
+
parsed_jwks = JSON.parse(
|
|
71
|
+
cache.fetch(cache_key, options) do
|
|
72
|
+
http_client.get(jwks_uri).body
|
|
73
|
+
end
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
unless parsed_jwks.is_a?(Hash) && parsed_jwks['keys'].is_a?(Array)
|
|
77
|
+
cache.delete(cache_key, options)
|
|
78
|
+
raise UnexpectedFormat
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
jwks = Set.new(parsed_jwks)
|
|
82
|
+
cache.delete(cache_key, options) if jwks[kid].blank?
|
|
83
|
+
|
|
84
|
+
if auto_detect
|
|
85
|
+
jwks[kid] or raise KidNotFound
|
|
86
|
+
else
|
|
87
|
+
jwks
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
data/lib/json/jwk/set.rb
CHANGED
|
@@ -19,10 +19,16 @@ module JSON
|
|
|
19
19
|
'application/jwk-set+json'
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
def [](kid)
|
|
23
|
+
detect do |jwk|
|
|
24
|
+
jwk[:kid] && jwk[:kid] == kid
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
22
28
|
def as_json(options = {})
|
|
23
29
|
# NOTE: Array.new wrapper is requied to avoid CircularReferenceError
|
|
24
30
|
{keys: Array.new(self)}
|
|
25
31
|
end
|
|
26
32
|
end
|
|
27
33
|
end
|
|
28
|
-
end
|
|
34
|
+
end
|