jose 1.1.3 → 1.2.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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +27 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +11 -0
- data/Gemfile +9 -2
- data/LICENSE.md +22 -373
- data/README.md +2 -2
- data/Rakefile +1 -0
- data/jose.gemspec +6 -5
- data/lib/jose/jwa/curve25519_rbnacl.rb +0 -4
- data/lib/jose/jwa/ed25519_rbnacl.rb +18 -1
- data/lib/jose/jwa/pkcs1.rb +2 -2
- data/lib/jose/jwa/xchacha20poly1305.rb +61 -0
- data/lib/jose/jwa/xchacha20poly1305_rbnacl.rb +35 -0
- data/lib/jose/jwa/xchacha20poly1305_unsupported.rb +16 -0
- data/lib/jose/jwa.rb +9 -4
- data/lib/jose/jwe/alg.rb +2 -0
- data/lib/jose/jwe/alg_aes_gcm_kw.rb +1 -1
- data/lib/jose/jwe/alg_c20p_kw.rb +100 -0
- data/lib/jose/jwe/alg_xc20p_kw.rb +86 -0
- data/lib/jose/jwe/enc.rb +2 -0
- data/lib/jose/jwe/enc_c20p.rb +62 -0
- data/lib/jose/jwe/enc_xc20p.rb +49 -0
- data/lib/jose/jwe.rb +8 -0
- data/lib/jose/jwk/kty_ec.rb +20 -5
- data/lib/jose/jwk/kty_rsa.rb +46 -32
- data/lib/jose/jwk/openssh_key.rb +0 -1
- data/lib/jose/jwk/set.rb +3 -3
- data/lib/jose/jwk.rb +1 -1
- data/lib/jose/version.rb +1 -1
- data/lib/jose.rb +23 -3
- metadata +35 -15
- data/.travis.yml +0 -18
@@ -0,0 +1,35 @@
|
|
1
|
+
module JOSE::JWA::XChaCha20Poly1305_RbNaCl
|
2
|
+
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def __ruby__?; false; end
|
6
|
+
|
7
|
+
def __supported__?
|
8
|
+
return @supported ||= begin
|
9
|
+
begin
|
10
|
+
require 'rbnacl/libsodium'
|
11
|
+
rescue LoadError
|
12
|
+
end
|
13
|
+
begin
|
14
|
+
require 'rbnacl'
|
15
|
+
rescue LoadError
|
16
|
+
end
|
17
|
+
!!(defined?(RbNaCl::AEAD::XChaCha20Poly1305IETF))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def xchacha20poly1305_aead_encrypt(key, nonce, aad, plaintext)
|
22
|
+
cipher = RbNaCl::AEAD::XChaCha20Poly1305IETF.new(key)
|
23
|
+
ciphertext_with_tag = cipher.encrypt(nonce, plaintext, aad)
|
24
|
+
return [ciphertext_with_tag[0..-17], ciphertext_with_tag[-16..-1]]
|
25
|
+
end
|
26
|
+
|
27
|
+
def xchacha20poly1305_aead_decrypt(key, nonce, aad, ciphertext, tag)
|
28
|
+
cipher = RbNaCl::AEAD::XChaCha20Poly1305IETF.new(key)
|
29
|
+
ciphertext_with_tag = [ciphertext, tag].join()
|
30
|
+
return cipher.decrypt(nonce, ciphertext_with_tag, aad)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
JOSE::JWA::XChaCha20Poly1305.__register__(JOSE::JWA::XChaCha20Poly1305_RbNaCl)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module JOSE::JWA::XChaCha20Poly1305_Unsupported
|
2
|
+
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def __ruby__?; true; end
|
6
|
+
def __supported__?; false; end
|
7
|
+
|
8
|
+
def xchacha20poly1305_aead_encrypt(key, nonce, aad, plaintext)
|
9
|
+
raise NotImplementedError
|
10
|
+
end
|
11
|
+
|
12
|
+
def xchacha20poly1305_aead_decrypt(key, nonce, aad, ciphertext, tag)
|
13
|
+
raise NotImplementedError
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
data/lib/jose/jwa.rb
CHANGED
@@ -127,7 +127,9 @@ module JOSE
|
|
127
127
|
'A256GCM',
|
128
128
|
'A128CBC-HS256',
|
129
129
|
'A192CBC-HS384',
|
130
|
-
'A256CBC-HS512'
|
130
|
+
'A256CBC-HS512',
|
131
|
+
'C20P',
|
132
|
+
'XC20P'
|
131
133
|
])
|
132
134
|
jwe_alg = __jwe_alg_support_check__([
|
133
135
|
['A128GCMKW', :block],
|
@@ -136,6 +138,7 @@ module JOSE
|
|
136
138
|
['A128KW', :block],
|
137
139
|
['A192KW', :block],
|
138
140
|
['A256KW', :block],
|
141
|
+
['C20PKW', :block],
|
139
142
|
['ECDH-ES', :box],
|
140
143
|
['ECDH-ES+A128KW', :box],
|
141
144
|
['ECDH-ES+A192KW', :box],
|
@@ -146,6 +149,7 @@ module JOSE
|
|
146
149
|
['RSA1_5', :rsa],
|
147
150
|
['RSA-OAEP', :rsa],
|
148
151
|
['RSA-OAEP-256', :rsa],
|
152
|
+
['XC20PKW', :block],
|
149
153
|
['dir', :direct]
|
150
154
|
], jwe_enc)
|
151
155
|
jwe_zip = __jwe_zip_support_check__([
|
@@ -231,7 +235,7 @@ module JOSE
|
|
231
235
|
cipher_text = recv_jwk.box_encrypt(plain_text, send_jwk).compact
|
232
236
|
next recv_jwk.box_decrypt(cipher_text).first == plain_text
|
233
237
|
elsif strategy == :rsa
|
234
|
-
rsa ||= JOSE::JWK.generate_key([:rsa,
|
238
|
+
rsa ||= JOSE::JWK.generate_key([:rsa, 2048])
|
235
239
|
cipher_text = rsa.block_encrypt(plain_text, { 'alg' => alg, 'enc' => enc }).compact
|
236
240
|
next rsa.block_decrypt(cipher_text).first == plain_text
|
237
241
|
elsif strategy == :direct
|
@@ -290,7 +294,7 @@ module JOSE
|
|
290
294
|
kty.push(key_type) if not kty_OKP_crv.empty?
|
291
295
|
when 'RSA'
|
292
296
|
begin
|
293
|
-
JOSE::JWK.generate_key([:rsa,
|
297
|
+
JOSE::JWK.generate_key([:rsa, 1024])
|
294
298
|
kty.push(key_type)
|
295
299
|
rescue StandardError, NotImplementedError
|
296
300
|
# do nothing
|
@@ -314,7 +318,7 @@ module JOSE
|
|
314
318
|
begin
|
315
319
|
jwk = nil
|
316
320
|
jwk ||= JOSE::JWK.generate_key([:oct, 0]).merge({ 'alg' => alg, 'use' => 'sig' }) if alg == 'none'
|
317
|
-
jwk ||= (rsa ||= JOSE::JWK.generate_key([:rsa,
|
321
|
+
jwk ||= (rsa ||= JOSE::JWK.generate_key([:rsa, 2048])).merge({ 'alg' => alg, 'use' => 'sig' }) if alg.start_with?('RS') or alg.start_with?('PS')
|
318
322
|
jwk ||= JOSE::JWS.generate_key({ 'alg' => alg })
|
319
323
|
signed_text = jwk.sign(plain_text).compact
|
320
324
|
next jwk.verify_strict(signed_text, [alg]).first
|
@@ -337,3 +341,4 @@ require 'jose/jwa/curve25519'
|
|
337
341
|
require 'jose/jwa/curve448'
|
338
342
|
require 'jose/jwa/pkcs1'
|
339
343
|
require 'jose/jwa/pkcs7'
|
344
|
+
require 'jose/jwa/xchacha20poly1305'
|
data/lib/jose/jwe/alg.rb
CHANGED
@@ -19,7 +19,9 @@ end
|
|
19
19
|
|
20
20
|
require 'jose/jwe/alg_aes_gcm_kw'
|
21
21
|
require 'jose/jwe/alg_aes_kw'
|
22
|
+
require 'jose/jwe/alg_c20p_kw'
|
22
23
|
require 'jose/jwe/alg_dir'
|
23
24
|
require 'jose/jwe/alg_ecdh_es'
|
24
25
|
require 'jose/jwe/alg_pbes2'
|
25
26
|
require 'jose/jwe/alg_rsa'
|
27
|
+
require 'jose/jwe/alg_xc20p_kw'
|
@@ -103,7 +103,7 @@ class JOSE::JWE::ALG_AES_GCM_KW < Struct.new(:cipher_name, :bits, :iv, :tag)
|
|
103
103
|
when 256
|
104
104
|
'A256GCMKW'
|
105
105
|
else
|
106
|
-
raise ArgumentError, "unhandled JOSE::JWE::
|
106
|
+
raise ArgumentError, "unhandled JOSE::JWE::ALG_AES_GCM_KW bits: #{bits.inspect}"
|
107
107
|
end
|
108
108
|
end
|
109
109
|
|
@@ -0,0 +1,100 @@
|
|
1
|
+
class JOSE::JWE::ALG_C20P_KW < Struct.new(:cipher_name, :bits, :iv, :tag)
|
2
|
+
|
3
|
+
# JOSE::JWE callbacks
|
4
|
+
|
5
|
+
def self.from_map(fields)
|
6
|
+
bits = nil
|
7
|
+
cipher_name = nil
|
8
|
+
case fields['alg']
|
9
|
+
when 'C20PKW'
|
10
|
+
bits = 256
|
11
|
+
cipher_name = 'chacha20-poly1305'
|
12
|
+
else
|
13
|
+
raise ArgumentError, "invalid 'alg' for JWE: #{fields['alg'].inspect}"
|
14
|
+
end
|
15
|
+
iv = nil
|
16
|
+
if fields.has_key?('iv')
|
17
|
+
iv = JOSE.urlsafe_decode64(fields['iv'])
|
18
|
+
end
|
19
|
+
tag = nil
|
20
|
+
if fields.has_key?('tag')
|
21
|
+
tag = JOSE.urlsafe_decode64(fields['tag'])
|
22
|
+
end
|
23
|
+
return new(cipher_name, bits, iv, tag), fields.except('alg', 'iv', 'tag')
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_map(fields)
|
27
|
+
alg = algorithm
|
28
|
+
fields = fields.put('alg', alg)
|
29
|
+
if iv
|
30
|
+
fields = fields.put('iv', JOSE.urlsafe_encode64(iv))
|
31
|
+
end
|
32
|
+
if tag
|
33
|
+
fields = fields.put('tag', JOSE.urlsafe_encode64(tag))
|
34
|
+
end
|
35
|
+
return fields
|
36
|
+
end
|
37
|
+
|
38
|
+
# JOSE::JWE::ALG callbacks
|
39
|
+
|
40
|
+
def generate_key(fields, enc)
|
41
|
+
return JOSE::JWE::ALG.generate_key([:oct, bits.div(8)], algorithm, enc.algorithm)
|
42
|
+
end
|
43
|
+
|
44
|
+
def key_decrypt(key, enc, encrypted_key)
|
45
|
+
if iv.nil? or tag.nil?
|
46
|
+
raise ArgumentError, "missing required fields for decryption: 'iv' and 'tag'"
|
47
|
+
end
|
48
|
+
if key.is_a?(JOSE::JWK)
|
49
|
+
key = key.kty.derive_key
|
50
|
+
end
|
51
|
+
derived_key = key
|
52
|
+
aad = ''
|
53
|
+
cipher_text = encrypted_key
|
54
|
+
cipher_tag = tag
|
55
|
+
cipher = OpenSSL::Cipher.new(cipher_name)
|
56
|
+
cipher.decrypt
|
57
|
+
cipher.key = derived_key
|
58
|
+
cipher.iv = iv
|
59
|
+
cipher.padding = 0
|
60
|
+
cipher.auth_data = aad
|
61
|
+
cipher.auth_tag = cipher_tag
|
62
|
+
plain_text = cipher.update(cipher_text) + cipher.final
|
63
|
+
return plain_text
|
64
|
+
end
|
65
|
+
|
66
|
+
def key_encrypt(key, enc, decrypted_key)
|
67
|
+
if key.is_a?(JOSE::JWK)
|
68
|
+
key = key.kty.derive_key
|
69
|
+
end
|
70
|
+
new_alg = JOSE::JWE::ALG_C20P_KW.new(cipher_name, bits, iv || SecureRandom.random_bytes(12))
|
71
|
+
derived_key = key
|
72
|
+
aad = ''
|
73
|
+
plain_text = decrypted_key
|
74
|
+
cipher = OpenSSL::Cipher.new(new_alg.cipher_name)
|
75
|
+
cipher.encrypt
|
76
|
+
cipher.key = derived_key
|
77
|
+
cipher.iv = new_alg.iv
|
78
|
+
cipher.padding = 0
|
79
|
+
cipher.auth_data = aad
|
80
|
+
cipher_text = cipher.update(plain_text) + cipher.final
|
81
|
+
new_alg.tag = cipher.auth_tag
|
82
|
+
return cipher_text, new_alg
|
83
|
+
end
|
84
|
+
|
85
|
+
def next_cek(key, enc)
|
86
|
+
return enc.next_cek, self
|
87
|
+
end
|
88
|
+
|
89
|
+
# API functions
|
90
|
+
|
91
|
+
def algorithm
|
92
|
+
case bits
|
93
|
+
when 256
|
94
|
+
'C20PKW'
|
95
|
+
else
|
96
|
+
raise ArgumentError, "unhandled JOSE::JWE::ALG_C20P_KW bits: #{bits.inspect}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
class JOSE::JWE::ALG_XC20P_KW < Struct.new(:cipher_name, :bits, :iv, :tag)
|
2
|
+
|
3
|
+
# JOSE::JWE callbacks
|
4
|
+
|
5
|
+
def self.from_map(fields)
|
6
|
+
bits = nil
|
7
|
+
cipher_name = nil
|
8
|
+
case fields['alg']
|
9
|
+
when 'XC20PKW'
|
10
|
+
bits = 256
|
11
|
+
cipher_name = 'xchacha20-poly1305'
|
12
|
+
else
|
13
|
+
raise ArgumentError, "invalid 'alg' for JWE: #{fields['alg'].inspect}"
|
14
|
+
end
|
15
|
+
iv = nil
|
16
|
+
if fields.has_key?('iv')
|
17
|
+
iv = JOSE.urlsafe_decode64(fields['iv'])
|
18
|
+
end
|
19
|
+
tag = nil
|
20
|
+
if fields.has_key?('tag')
|
21
|
+
tag = JOSE.urlsafe_decode64(fields['tag'])
|
22
|
+
end
|
23
|
+
return new(cipher_name, bits, iv, tag), fields.except('alg', 'iv', 'tag')
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_map(fields)
|
27
|
+
alg = algorithm
|
28
|
+
fields = fields.put('alg', alg)
|
29
|
+
if iv
|
30
|
+
fields = fields.put('iv', JOSE.urlsafe_encode64(iv))
|
31
|
+
end
|
32
|
+
if tag
|
33
|
+
fields = fields.put('tag', JOSE.urlsafe_encode64(tag))
|
34
|
+
end
|
35
|
+
return fields
|
36
|
+
end
|
37
|
+
|
38
|
+
# JOSE::JWE::ALG callbacks
|
39
|
+
|
40
|
+
def generate_key(fields, enc)
|
41
|
+
return JOSE::JWE::ALG.generate_key([:oct, bits.div(8)], algorithm, enc.algorithm)
|
42
|
+
end
|
43
|
+
|
44
|
+
def key_decrypt(key, enc, encrypted_key)
|
45
|
+
if iv.nil? or tag.nil?
|
46
|
+
raise ArgumentError, "missing required fields for decryption: 'iv' and 'tag'"
|
47
|
+
end
|
48
|
+
if key.is_a?(JOSE::JWK)
|
49
|
+
key = key.kty.derive_key
|
50
|
+
end
|
51
|
+
derived_key = key
|
52
|
+
aad = ''
|
53
|
+
cipher_text = encrypted_key
|
54
|
+
cipher_tag = tag
|
55
|
+
plain_text = JOSE.xchacha20poly1305_module().xchacha20poly1305_aead_decrypt(derived_key, iv, aad, cipher_text, cipher_tag)
|
56
|
+
return plain_text
|
57
|
+
end
|
58
|
+
|
59
|
+
def key_encrypt(key, enc, decrypted_key)
|
60
|
+
if key.is_a?(JOSE::JWK)
|
61
|
+
key = key.kty.derive_key
|
62
|
+
end
|
63
|
+
new_alg = JOSE::JWE::ALG_XC20P_KW.new(cipher_name, bits, iv || SecureRandom.random_bytes(24))
|
64
|
+
derived_key = key
|
65
|
+
aad = ''
|
66
|
+
plain_text = decrypted_key
|
67
|
+
cipher_text, new_alg.tag = JOSE.xchacha20poly1305_module().xchacha20poly1305_aead_encrypt(key, new_alg.iv, aad, plain_text)
|
68
|
+
return cipher_text, new_alg
|
69
|
+
end
|
70
|
+
|
71
|
+
def next_cek(key, enc)
|
72
|
+
return enc.next_cek, self
|
73
|
+
end
|
74
|
+
|
75
|
+
# API functions
|
76
|
+
|
77
|
+
def algorithm
|
78
|
+
case bits
|
79
|
+
when 256
|
80
|
+
'XC20PKW'
|
81
|
+
else
|
82
|
+
raise ArgumentError, "unhandled JOSE::JWE::ALG_XC20P_KW bits: #{bits.inspect}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
data/lib/jose/jwe/enc.rb
CHANGED
@@ -0,0 +1,62 @@
|
|
1
|
+
class JOSE::JWE::ENC_C20P < Struct.new(:cipher_name, :bits, :cek_len, :iv_len)
|
2
|
+
|
3
|
+
# JOSE::JWE callbacks
|
4
|
+
|
5
|
+
def self.from_map(fields)
|
6
|
+
case fields['enc']
|
7
|
+
when 'C20P'
|
8
|
+
return new('chacha20-poly1305', 256, 32, 12), fields.delete('enc')
|
9
|
+
else
|
10
|
+
raise ArgumentError, "invalid 'enc' for JWE: #{fields['enc'].inspect}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_map(fields)
|
15
|
+
return fields.put('enc', algorithm)
|
16
|
+
end
|
17
|
+
|
18
|
+
# JOSE::JWE::ENC callbacks
|
19
|
+
|
20
|
+
def algorithm
|
21
|
+
case cipher_name
|
22
|
+
when 'chacha20-poly1305'
|
23
|
+
return 'C20P'
|
24
|
+
else
|
25
|
+
raise ArgumentError, "unhandled JOSE::JWE::ENC_C20P cipher name: #{cipher_name.inspect}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def block_decrypt(aad_cipher_text_cipher_tag, cek, iv)
|
30
|
+
aad, cipher_text, cipher_tag = aad_cipher_text_cipher_tag
|
31
|
+
cipher = OpenSSL::Cipher.new(cipher_name)
|
32
|
+
cipher.decrypt
|
33
|
+
cipher.key = cek
|
34
|
+
cipher.iv = iv
|
35
|
+
cipher.padding = 0
|
36
|
+
cipher.auth_data = aad
|
37
|
+
cipher.auth_tag = cipher_tag
|
38
|
+
plain_text = cipher.update(cipher_text) + cipher.final
|
39
|
+
return plain_text
|
40
|
+
end
|
41
|
+
|
42
|
+
def block_encrypt(aad_plain_text, cek, iv)
|
43
|
+
aad, plain_text = aad_plain_text
|
44
|
+
cipher = OpenSSL::Cipher.new(cipher_name)
|
45
|
+
cipher.encrypt
|
46
|
+
cipher.key = cek
|
47
|
+
cipher.iv = iv
|
48
|
+
cipher.padding = 0
|
49
|
+
cipher.auth_data = aad
|
50
|
+
cipher_text = cipher.update(plain_text) + cipher.final
|
51
|
+
return cipher_text, cipher.auth_tag
|
52
|
+
end
|
53
|
+
|
54
|
+
def next_cek
|
55
|
+
return SecureRandom.random_bytes(cek_len)
|
56
|
+
end
|
57
|
+
|
58
|
+
def next_iv
|
59
|
+
return SecureRandom.random_bytes(iv_len)
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class JOSE::JWE::ENC_XC20P < Struct.new(:cipher_name, :bits, :cek_len, :iv_len)
|
2
|
+
|
3
|
+
# JOSE::JWE callbacks
|
4
|
+
|
5
|
+
def self.from_map(fields)
|
6
|
+
case fields['enc']
|
7
|
+
when 'XC20P'
|
8
|
+
return new('xchacha20-poly1305', 256, 32, 24), fields.delete('enc')
|
9
|
+
else
|
10
|
+
raise ArgumentError, "invalid 'enc' for JWE: #{fields['enc'].inspect}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_map(fields)
|
15
|
+
return fields.put('enc', algorithm)
|
16
|
+
end
|
17
|
+
|
18
|
+
# JOSE::JWE::ENC callbacks
|
19
|
+
|
20
|
+
def algorithm
|
21
|
+
case cipher_name
|
22
|
+
when 'xchacha20-poly1305'
|
23
|
+
return 'XC20P'
|
24
|
+
else
|
25
|
+
raise ArgumentError, "unhandled JOSE::JWE::ENC_XC20P cipher name: #{cipher_name.inspect}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def block_decrypt(aad_cipher_text_cipher_tag, cek, iv)
|
30
|
+
aad, cipher_text, cipher_tag = aad_cipher_text_cipher_tag
|
31
|
+
plain_text = JOSE.xchacha20poly1305_module().xchacha20poly1305_aead_decrypt(cek, iv, aad, cipher_text, cipher_tag)
|
32
|
+
return plain_text
|
33
|
+
end
|
34
|
+
|
35
|
+
def block_encrypt(aad_plain_text, cek, iv)
|
36
|
+
aad, plain_text = aad_plain_text
|
37
|
+
cipher_text, cipher_tag = JOSE.xchacha20poly1305_module().xchacha20poly1305_aead_encrypt(cek, iv, aad, plain_text)
|
38
|
+
return cipher_text, cipher_tag
|
39
|
+
end
|
40
|
+
|
41
|
+
def next_cek
|
42
|
+
return SecureRandom.random_bytes(cek_len)
|
43
|
+
end
|
44
|
+
|
45
|
+
def next_iv
|
46
|
+
return SecureRandom.random_bytes(iv_len)
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
data/lib/jose/jwe.rb
CHANGED
@@ -1076,6 +1076,8 @@ module JOSE
|
|
1076
1076
|
JOSE::JWE::ALG_AES_KW
|
1077
1077
|
when 'A128GCMKW', 'A192GCMKW', 'A256GCMKW'
|
1078
1078
|
JOSE::JWE::ALG_AES_GCM_KW
|
1079
|
+
when 'C20PKW'
|
1080
|
+
JOSE::JWE::ALG_C20P_KW
|
1079
1081
|
when 'dir'
|
1080
1082
|
JOSE::JWE::ALG_dir
|
1081
1083
|
when 'ECDH-ES', 'ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW'
|
@@ -1084,6 +1086,8 @@ module JOSE
|
|
1084
1086
|
JOSE::JWE::ALG_PBES2
|
1085
1087
|
when 'RSA1_5', 'RSA-OAEP', 'RSA-OAEP-256'
|
1086
1088
|
JOSE::JWE::ALG_RSA
|
1089
|
+
when 'XC20PKW'
|
1090
|
+
JOSE::JWE::ALG_XC20P_KW
|
1087
1091
|
else
|
1088
1092
|
raise ArgumentError, "unknown 'alg': #{jwe.fields['alg'].inspect}"
|
1089
1093
|
end
|
@@ -1095,6 +1099,10 @@ module JOSE
|
|
1095
1099
|
JOSE::JWE::ENC_AES_CBC_HMAC
|
1096
1100
|
when 'A128GCM', 'A192GCM', 'A256GCM'
|
1097
1101
|
JOSE::JWE::ENC_AES_GCM
|
1102
|
+
when 'C20P'
|
1103
|
+
JOSE::JWE::ENC_C20P
|
1104
|
+
when 'XC20P'
|
1105
|
+
JOSE::JWE::ENC_XC20P
|
1098
1106
|
else
|
1099
1107
|
raise ArgumentError, "unknown 'enc': #{jwe.fields['enc'].inspect}"
|
1100
1108
|
end
|
data/lib/jose/jwk/kty_ec.rb
CHANGED
@@ -14,16 +14,31 @@ class JOSE::JWK::KTY_EC < Struct.new(:key)
|
|
14
14
|
else
|
15
15
|
raise ArgumentError, "invalid 'EC' JWK"
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
x = JOSE.urlsafe_decode64(fields['x'])
|
19
19
|
y = JOSE.urlsafe_decode64(fields['y'])
|
20
|
-
|
20
|
+
point = OpenSSL::PKey::EC::Point.new(
|
21
21
|
OpenSSL::PKey::EC::Group.new(crv),
|
22
22
|
OpenSSL::BN.new([0x04, x, y].pack('Ca*a*'), 2)
|
23
23
|
)
|
24
|
-
|
25
|
-
|
24
|
+
|
25
|
+
sequence = if fields['d'].is_a?(String)
|
26
|
+
OpenSSL::ASN1::Sequence([
|
27
|
+
OpenSSL::ASN1::Integer(1),
|
28
|
+
OpenSSL::ASN1::OctetString(OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['d']), 2).to_s(2)),
|
29
|
+
OpenSSL::ASN1::ObjectId(crv, 0, :EXPLICIT),
|
30
|
+
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed), 1, :EXPLICIT)
|
31
|
+
])
|
32
|
+
else
|
33
|
+
OpenSSL::ASN1::Sequence([
|
34
|
+
OpenSSL::ASN1::Sequence([
|
35
|
+
OpenSSL::ASN1::ObjectId("id-ecPublicKey"),
|
36
|
+
OpenSSL::ASN1::ObjectId(crv)
|
37
|
+
]),
|
38
|
+
OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
|
39
|
+
])
|
26
40
|
end
|
41
|
+
ec = OpenSSL::PKey::EC.new(sequence.to_der)
|
27
42
|
return JOSE::JWK::KTY_EC.new(JOSE::JWK::PKeyProxy.new(ec)), fields.except('kty', 'crv', 'd', 'x', 'y')
|
28
43
|
else
|
29
44
|
raise ArgumentError, "invalid 'EC' JWK"
|
@@ -132,7 +147,7 @@ class JOSE::JWK::KTY_EC < Struct.new(:key)
|
|
132
147
|
curve_name
|
133
148
|
end
|
134
149
|
if curve_name.is_a?(String)
|
135
|
-
return from_key(OpenSSL::PKey::EC.
|
150
|
+
return from_key(OpenSSL::PKey::EC.generate(curve_name))
|
136
151
|
else
|
137
152
|
raise ArgumentError, "'curve_name' must be a String"
|
138
153
|
end
|
data/lib/jose/jwk/kty_rsa.rb
CHANGED
@@ -8,36 +8,32 @@ class JOSE::JWK::KTY_RSA < Struct.new(:key)
|
|
8
8
|
raise ArgumentError, "multi-prime RSA keys are not supported"
|
9
9
|
elsif fields['d'].is_a?(String)
|
10
10
|
if fields['dp'].is_a?(String) and fields['dq'].is_a?(String) and fields['p'].is_a?(String) and fields['q'].is_a?(String) and fields['qi'].is_a?(String)
|
11
|
-
|
12
|
-
|
13
|
-
OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['n']), 2),
|
14
|
-
OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['e']), 2),
|
15
|
-
OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['d']), 2)
|
16
|
-
|
17
|
-
|
18
|
-
OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['
|
19
|
-
OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['dq']), 2),
|
24
|
-
OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['qi']), 2)
|
25
|
-
)
|
11
|
+
asn1_sequence = OpenSSL::ASN1::Sequence.new([
|
12
|
+
OpenSSL::ASN1::Integer.new(0),
|
13
|
+
OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['n']), 2)),
|
14
|
+
OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['e']), 2)),
|
15
|
+
OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['d']), 2)),
|
16
|
+
OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['p']), 2)),
|
17
|
+
OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['q']), 2)),
|
18
|
+
OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['dp']), 2)),
|
19
|
+
OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['dq']), 2)),
|
20
|
+
OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['qi']), 2))
|
21
|
+
])
|
22
|
+
rsa = OpenSSL::PKey::RSA.new(asn1_sequence.to_der)
|
26
23
|
return JOSE::JWK::KTY_RSA.new(JOSE::JWK::PKeyProxy.new(rsa)), fields.except('kty', 'd', 'dp', 'dq', 'e', 'n', 'p', 'q', 'qi')
|
27
24
|
else
|
28
|
-
d
|
29
|
-
e
|
30
|
-
n
|
25
|
+
d = OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['d']), 2)
|
26
|
+
e = OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['e']), 2)
|
27
|
+
n = OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['n']), 2)
|
31
28
|
rsa = convert_sfm_to_crt(d, e, n)
|
32
29
|
return JOSE::JWK::KTY_RSA.new(JOSE::JWK::PKeyProxy.new(rsa)), fields.except('kty', 'd', 'dp', 'dq', 'e', 'n', 'p', 'q', 'qi')
|
33
30
|
end
|
34
31
|
else
|
35
|
-
|
36
|
-
|
37
|
-
OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['
|
38
|
-
|
39
|
-
|
40
|
-
)
|
32
|
+
asn1_sequence = OpenSSL::ASN1::Sequence.new([
|
33
|
+
OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['n']), 2)),
|
34
|
+
OpenSSL::ASN1::Integer.new(OpenSSL::BN.new(JOSE.urlsafe_decode64(fields['e']), 2))
|
35
|
+
])
|
36
|
+
rsa = OpenSSL::PKey::RSA.new(OpenSSL::PKey::RSA.new(asn1_sequence.to_der))
|
41
37
|
return JOSE::JWK::KTY_RSA.new(JOSE::JWK::PKeyProxy.new(rsa)), fields.except('kty', 'e', 'n')
|
42
38
|
end
|
43
39
|
else
|
@@ -140,7 +136,8 @@ class JOSE::JWK::KTY_RSA < Struct.new(:key)
|
|
140
136
|
end
|
141
137
|
end
|
142
138
|
if modulus_size.is_a?(Integer) and (exponent_size.nil? or exponent_size.is_a?(Integer))
|
143
|
-
return from_key(OpenSSL::PKey::RSA.generate(modulus_size
|
139
|
+
return from_key(OpenSSL::PKey::RSA.generate(modulus_size)) if exponent_size.nil?
|
140
|
+
return from_key(OpenSSL::PKey::RSA.generate(modulus_size, exponent_size)) if exponent_size.is_a?(Integer)
|
144
141
|
else
|
145
142
|
raise ArgumentError, "'modulus_size' must be an Integer and 'exponent_size' must be nil or an Integer"
|
146
143
|
end
|
@@ -160,7 +157,12 @@ class JOSE::JWK::KTY_RSA < Struct.new(:key)
|
|
160
157
|
when :rsa_pkcs1_padding
|
161
158
|
return key.sign(digest_type.new, message)
|
162
159
|
when :rsa_pkcs1_pss_padding
|
163
|
-
|
160
|
+
if key.respond_to?(:sign_pss)
|
161
|
+
digest_name = digest_type.new.name
|
162
|
+
return key.sign_pss(digest_name, message, salt_length: :digest, mgf1_hash: digest_name)
|
163
|
+
else
|
164
|
+
return JOSE::JWA::PKCS1.rsassa_pss_sign(digest_type, message, key)
|
165
|
+
end
|
164
166
|
else
|
165
167
|
raise ArgumentError, "unsupported RSA padding: #{padding.inspect}"
|
166
168
|
end
|
@@ -189,7 +191,12 @@ class JOSE::JWK::KTY_RSA < Struct.new(:key)
|
|
189
191
|
when :rsa_pkcs1_padding
|
190
192
|
return key.verify(digest_type.new, signature, message)
|
191
193
|
when :rsa_pkcs1_pss_padding
|
192
|
-
|
194
|
+
if key.respond_to?(:verify_pss)
|
195
|
+
digest_name = digest_type.new.name
|
196
|
+
return key.verify_pss(digest_name, signature, message, salt_length: :digest, mgf1_hash: digest_name)
|
197
|
+
else
|
198
|
+
return JOSE::JWA::PKCS1.rsassa_pss_verify(digest_type, message, signature, key)
|
199
|
+
end
|
193
200
|
else
|
194
201
|
raise ArgumentError, "unsupported RSA padding: #{padding.inspect}"
|
195
202
|
end
|
@@ -230,11 +237,18 @@ private
|
|
230
237
|
dq = d % (q - 1)
|
231
238
|
qi = q.mod_inverse(p)
|
232
239
|
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
240
|
+
asn1_sequence = OpenSSL::ASN1::Sequence.new([
|
241
|
+
OpenSSL::ASN1::Integer.new(0),
|
242
|
+
OpenSSL::ASN1::Integer.new(n),
|
243
|
+
OpenSSL::ASN1::Integer.new(e),
|
244
|
+
OpenSSL::ASN1::Integer.new(d),
|
245
|
+
OpenSSL::ASN1::Integer.new(p),
|
246
|
+
OpenSSL::ASN1::Integer.new(q),
|
247
|
+
OpenSSL::ASN1::Integer.new(dp),
|
248
|
+
OpenSSL::ASN1::Integer.new(dq),
|
249
|
+
OpenSSL::ASN1::Integer.new(qi)
|
250
|
+
])
|
251
|
+
rsa = OpenSSL::PKey::RSA.new(asn1_sequence.to_der)
|
238
252
|
return rsa
|
239
253
|
end
|
240
254
|
|
data/lib/jose/jwk/openssh_key.rb
CHANGED
data/lib/jose/jwk/set.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
require '
|
1
|
+
require 'immutable/vector'
|
2
2
|
|
3
|
-
# Immutable Set structure based on `
|
4
|
-
class JOSE::JWK::Set <
|
3
|
+
# Immutable Set structure based on `Immutable::Vector`.
|
4
|
+
class JOSE::JWK::Set < Immutable::Vector
|
5
5
|
|
6
6
|
def self.from_map(fields)
|
7
7
|
if fields['keys'].is_a?(Array)
|
data/lib/jose/jwk.rb
CHANGED
@@ -481,7 +481,7 @@ module JOSE
|
|
481
481
|
# Converts a private {JOSE::JWK JOSE::JWK} into a public {JOSE::JWK JOSE::JWK}.
|
482
482
|
#
|
483
483
|
# !!!ruby
|
484
|
-
# jwk_rsa = JOSE::JWK.generate_key([:rsa,
|
484
|
+
# jwk_rsa = JOSE::JWK.generate_key([:rsa, 1024]).to_map
|
485
485
|
# # => JOSE::Map[
|
486
486
|
# # "dq" => "Iv_BghpjRyv8hk4AgsX_3w",
|
487
487
|
# # "e" => "AQAB",
|
data/lib/jose/version.rb
CHANGED