jose 1.1.3 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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