jose 0.1.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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +20 -0
  3. data/.gitignore +9 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +4 -0
  7. data/CODE_OF_CONDUCT.md +13 -0
  8. data/Gemfile +20 -0
  9. data/LICENSE.txt +373 -0
  10. data/README.md +41 -0
  11. data/Rakefile +10 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +7 -0
  14. data/jose.gemspec +36 -0
  15. data/lib/jose.rb +61 -0
  16. data/lib/jose/jwa.rb +21 -0
  17. data/lib/jose/jwa/aes_kw.rb +105 -0
  18. data/lib/jose/jwa/concat_kdf.rb +50 -0
  19. data/lib/jose/jwa/pkcs1.rb +269 -0
  20. data/lib/jose/jwa/pkcs7.rb +23 -0
  21. data/lib/jose/jwe.rb +290 -0
  22. data/lib/jose/jwe/alg.rb +12 -0
  23. data/lib/jose/jwe/alg_aes_gcm_kw.rb +98 -0
  24. data/lib/jose/jwe/alg_aes_kw.rb +57 -0
  25. data/lib/jose/jwe/alg_dir.rb +40 -0
  26. data/lib/jose/jwe/alg_ecdh_es.rb +123 -0
  27. data/lib/jose/jwe/alg_pbes2.rb +90 -0
  28. data/lib/jose/jwe/alg_rsa.rb +63 -0
  29. data/lib/jose/jwe/enc.rb +8 -0
  30. data/lib/jose/jwe/enc_aes_cbc_hmac.rb +80 -0
  31. data/lib/jose/jwe/enc_aes_gcm.rb +68 -0
  32. data/lib/jose/jwe/zip.rb +7 -0
  33. data/lib/jose/jwe/zip_def.rb +28 -0
  34. data/lib/jose/jwk.rb +347 -0
  35. data/lib/jose/jwk/kty.rb +34 -0
  36. data/lib/jose/jwk/kty_ec.rb +179 -0
  37. data/lib/jose/jwk/kty_oct.rb +104 -0
  38. data/lib/jose/jwk/kty_rsa.rb +185 -0
  39. data/lib/jose/jwk/pem.rb +19 -0
  40. data/lib/jose/jwk/set.rb +2 -0
  41. data/lib/jose/jws.rb +242 -0
  42. data/lib/jose/jws/alg.rb +10 -0
  43. data/lib/jose/jws/alg_ecdsa.rb +41 -0
  44. data/lib/jose/jws/alg_hmac.rb +41 -0
  45. data/lib/jose/jws/alg_rsa_pkcs1_v1_5.rb +41 -0
  46. data/lib/jose/jws/alg_rsa_pss.rb +41 -0
  47. data/lib/jose/jwt.rb +145 -0
  48. data/lib/jose/version.rb +3 -0
  49. metadata +162 -0
@@ -0,0 +1,40 @@
1
+ class JOSE::JWE::ALG_dir
2
+
3
+ # JOSE::JWE callbacks
4
+
5
+ def self.from_map(fields)
6
+ case fields['alg']
7
+ when 'dir'
8
+ return new(), fields.delete('alg')
9
+ else
10
+ raise ArgumentError, "invalid 'alg' for JWE: #{fields['alg'].inspect}"
11
+ end
12
+ end
13
+
14
+ def to_map(fields)
15
+ return fields.put('alg', 'dir')
16
+ end
17
+
18
+ # JOSE::JWE::ALG callbacks
19
+
20
+ def key_decrypt(key, enc, encrypted_key)
21
+ if key.is_a?(String)
22
+ return key
23
+ else
24
+ return key.kty.derive_key
25
+ end
26
+ end
27
+
28
+ def key_encrypt(key, enc, decrypted_key)
29
+ return '', self
30
+ end
31
+
32
+ def next_cek(key, enc)
33
+ if key.is_a?(String)
34
+ return key
35
+ else
36
+ return key.kty.derive_key
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,123 @@
1
+ class JOSE::JWE::ALG_ECDH_ES < Struct.new(:bits, :epk, :apu, :apv)
2
+
3
+ # JOSE::JWE callbacks
4
+
5
+ def self.from_map(fields)
6
+ bits = nil
7
+ case fields['alg']
8
+ when 'ECDH-ES'
9
+ bits = nil
10
+ when 'ECDH-ES+A128KW'
11
+ bits = 128
12
+ when 'ECDH-ES+A192KW'
13
+ bits = 192
14
+ when 'ECDH-ES+A256KW'
15
+ bits = 256
16
+ else
17
+ raise ArgumentError, "invalid 'alg' for JWE: #{fields['alg'].inspect}"
18
+ end
19
+ epk = nil
20
+ if fields.has_key?('epk')
21
+ epk = JOSE::JWK.from_map(fields['epk'])
22
+ end
23
+ apu = nil
24
+ if fields.has_key?('apu')
25
+ apu = JOSE.urlsafe_decode64(fields['apu'])
26
+ end
27
+ apv = nil
28
+ if fields.has_key?('apv')
29
+ apv = JOSE.urlsafe_decode64(fields['apv'])
30
+ end
31
+ return new(bits, epk, apu, apv), fields.except('alg', 'apu', 'apv', 'epk')
32
+ end
33
+
34
+ def to_map(fields)
35
+ fields = fields.put('alg', algorithm)
36
+ if epk
37
+ fields = fields.put('epk', epk.to_map)
38
+ end
39
+ if apu
40
+ fields = fields.put('apu', JOSE.urlsafe_encode64(apu))
41
+ end
42
+ if apv
43
+ fields = fields.put('apv', JOSE.urlsafe_encode64(apv))
44
+ end
45
+ return fields
46
+ end
47
+
48
+ # JOSE::JWE::ALG callbacks
49
+
50
+ def key_decrypt(box_keys, enc, encrypted_key)
51
+ other_public_key, my_private_key = box_keys
52
+ if my_private_key and epk and epk.to_key != other_public_key.to_key
53
+ raise ArgumentError, "other and ephemeral public key mismatch"
54
+ elsif epk and my_private_key.nil?
55
+ my_private_key = other_public_key
56
+ other_public_key = epk
57
+ else
58
+ raise ArgumentError, "missing 'epk' or my_private_key"
59
+ end
60
+ z = other_public_key.derive_key(my_private_key)
61
+ if bits.nil?
62
+ algorithm_id = enc.algorithm
63
+ key_data_len = enc.bits
64
+ supp_pub_info = [key_data_len].pack('N')
65
+ derived_key = JOSE::JWA::ConcatKDF.kdf(OpenSSL::Digest::SHA256, z, [algorithm_id, apu, apv, supp_pub_info], key_data_len)
66
+ return derived_key
67
+ else
68
+ algorithm_id = algorithm
69
+ key_data_len = bits
70
+ supp_pub_info = [key_data_len].pack('N')
71
+ derived_key = JOSE::JWA::ConcatKDF.kdf(OpenSSL::Digest::SHA256, z, [algorithm_id, apu, apv, supp_pub_info], key_data_len)
72
+ decrypted_key = JOSE::JWA::AES_KW.unwrap(encrypted_key, derived_key)
73
+ return decrypted_key
74
+ end
75
+ end
76
+
77
+ def key_encrypt(box_keys, enc, decrypted_key)
78
+ if bits.nil?
79
+ return '', self
80
+ else
81
+ other_public_key, my_private_key = box_keys
82
+ z = other_public_key.derive_key(my_private_key)
83
+ algorithm_id = algorithm
84
+ key_data_len = bits
85
+ supp_pub_info = [key_data_len].pack('N')
86
+ derived_key = JOSE::JWA::ConcatKDF.kdf(OpenSSL::Digest::SHA256, z, [algorithm_id, apu, apv, supp_pub_info], key_data_len)
87
+ encrypted_key = JOSE::JWA::AES_KW.wrap(decrypted_key, derived_key)
88
+ return encrypted_key, self
89
+ end
90
+ end
91
+
92
+ def next_cek(box_keys, enc)
93
+ if bits.nil?
94
+ other_public_key, my_private_key = box_keys
95
+ z = other_public_key.derive_key(my_private_key)
96
+ algorithm_id = enc.algorithm
97
+ key_data_len = enc.bits
98
+ supp_pub_info = [key_data_len].pack('N')
99
+ derived_key = JOSE::JWA::ConcatKDF.kdf(OpenSSL::Digest::SHA256, z, [algorithm_id, apu, apv, supp_pub_info], key_data_len)
100
+ return derived_key
101
+ else
102
+ return enc.next_cek
103
+ end
104
+ end
105
+
106
+ # API functions
107
+
108
+ def algorithm
109
+ case bits
110
+ when nil
111
+ return 'ECDH-ES'
112
+ when 128
113
+ return 'ECDH-ES+A128KW'
114
+ when 192
115
+ return 'ECDH-ES+A192KW'
116
+ when 256
117
+ return 'ECDH-ES+A256KW'
118
+ else
119
+ raise ArgumentError, "unhandled JOSE::JWE::ALG_ECDH_ES bits: #{bits.inspect}"
120
+ end
121
+ end
122
+
123
+ end
@@ -0,0 +1,90 @@
1
+ class JOSE::JWE::ALG_PBES2 < Struct.new(:hmac, :bits, :salt, :iter)
2
+
3
+ # JOSE::JWE callbacks
4
+
5
+ def self.from_map(fields)
6
+ bits = nil
7
+ hmac = nil
8
+ case fields['alg']
9
+ when 'PBES2-HS256+A128KW'
10
+ bits = 128
11
+ hmac = OpenSSL::Digest::SHA256
12
+ when 'PBES2-HS384+A192KW'
13
+ bits = 192
14
+ hmac = OpenSSL::Digest::SHA384
15
+ when 'PBES2-HS512+A256KW'
16
+ bits = 256
17
+ hmac = OpenSSL::Digest::SHA512
18
+ else
19
+ raise ArgumentError, "invalid 'alg' for JWE: #{fields['alg'].inspect}"
20
+ end
21
+ iter = nil
22
+ if fields['p2c'].is_a?(Integer) and fields['p2c'] >= 0
23
+ iter = fields['p2c']
24
+ else
25
+ raise ArgumentError, "invalid 'p2c' for JWE: #{fields['p2c'].inspect}"
26
+ end
27
+ salt = nil
28
+ if fields.has_key?('p2s') and fields['p2s'].is_a?(String)
29
+ salt = wrap_salt(fields['alg'], JOSE.urlsafe_decode64(fields['p2s']))
30
+ else
31
+ raise ArgumentError, "invalid 'p2s' for JWE: #{fields['p2s'].inspect}"
32
+ end
33
+ return new(hmac, bits, salt, iter), fields.except('alg', 'p2c', 'p2s')
34
+ end
35
+
36
+ def to_map(fields)
37
+ alg = if hmac == OpenSSL::Digest::SHA256
38
+ 'PBES2-HS256+A128KW'
39
+ elsif hmac == OpenSSL::Digest::SHA384
40
+ 'PBES2-HS384+A192KW'
41
+ elsif hmac == OpenSSL::Digest::SHA512
42
+ 'PBES2-HS512+A256KW'
43
+ else
44
+ raise ArgumentError, "unhandled JOSE::JWE::ALG_PBES2 hmac: #{hmac.inspect}"
45
+ end
46
+ p2c = iter
47
+ p2s = JOSE.urlsafe_encode64(unwrap_salt(alg, salt))
48
+ return fields.put('alg', alg).put('p2c', p2c).put('p2s', p2s)
49
+ end
50
+
51
+ # JOSE::JWE::ALG callbacks
52
+
53
+ def key_decrypt(key, enc, encrypted_key)
54
+ if key.is_a?(JOSE::JWK)
55
+ key = key.kty.derive_key
56
+ end
57
+ derived_key = OpenSSL::PKCS5.pbkdf2_hmac(key, salt, iter, bits.div(8) + (bits % 8), hmac.new)
58
+ decrypted_key = JOSE::JWA::AES_KW.unwrap(encrypted_key, derived_key)
59
+ return decrypted_key
60
+ end
61
+
62
+ def key_encrypt(key, enc, decrypted_key)
63
+ if key.is_a?(JOSE::JWK)
64
+ key = key.kty.derive_key
65
+ end
66
+ derived_key = OpenSSL::PKCS5.pbkdf2_hmac(key, salt, iter, bits.div(8) + (bits % 8), hmac.new)
67
+ encrypted_key = JOSE::JWA::AES_KW.wrap(decrypted_key, derived_key)
68
+ return encrypted_key, self
69
+ end
70
+
71
+ def next_cek(key, enc)
72
+ return enc.next_cek
73
+ end
74
+
75
+ private
76
+
77
+ def unwrap_salt(algorithm, salt)
78
+ salt_s = StringIO.new(salt)
79
+ if salt_s.read(algorithm.length) != algorithm or salt_s.getbyte != 0
80
+ raise ArgumentError, "unrecognized salt value"
81
+ else
82
+ return salt_s.read
83
+ end
84
+ end
85
+
86
+ def self.wrap_salt(algorithm, salt_input)
87
+ return [algorithm, 0x00, salt_input].pack('a*Ca*')
88
+ end
89
+
90
+ end
@@ -0,0 +1,63 @@
1
+ class JOSE::JWE::ALG_RSA < Struct.new(:rsa_padding, :rsa_oaep_md)
2
+
3
+ # JOSE::JWE callbacks
4
+
5
+ def self.from_map(fields)
6
+ rsa_padding = nil
7
+ rsa_oaep_md = nil
8
+ case fields['alg']
9
+ when 'RSA1_5'
10
+ rsa_padding = :rsa_pkcs1_padding
11
+ when 'RSA-OAEP'
12
+ rsa_padding = :rsa_pkcs1_oaep_padding
13
+ rsa_oaep_md = OpenSSL::Digest::SHA1
14
+ when 'RSA-OAEP-256'
15
+ rsa_padding = :rsa_pkcs1_oaep_padding
16
+ rsa_oaep_md = OpenSSL::Digest::SHA256
17
+ else
18
+ raise ArgumentError, "invalid 'alg' for JWE: #{fields['alg'].inspect}"
19
+ end
20
+ return new(rsa_padding, rsa_oaep_md), fields.except('alg')
21
+ end
22
+
23
+ def to_map(fields)
24
+ alg = nil
25
+ if rsa_padding == :rsa_pkcs1_padding
26
+ alg = 'RSA1_5'
27
+ elsif rsa_padding == :rsa_pkcs1_oaep_padding
28
+ if rsa_oaep_md == OpenSSL::Digest::SHA1
29
+ alg = 'RSA-OAEP'
30
+ elsif rsa_oaep_md == OpenSSL::Digest::SHA256
31
+ alg = 'RSA-OAEP-256'
32
+ else
33
+ raise ArgumentError, "unhandled JOSE::JWE::ALG_RSA rsa_oaep_md: #{rsa_oaep_md.inspect}"
34
+ end
35
+ else
36
+ raise ArgumentError, "unhandled JOSE::JWE::ALG_RSA rsa_padding: #{rsa_padding.inspect}"
37
+ end
38
+ return fields.put('alg', alg)
39
+ end
40
+
41
+ # JOSE::JWE::ALG callbacks
42
+
43
+ def key_decrypt(key, enc, encrypted_key)
44
+ if key.is_a?(JOSE::JWK)
45
+ return key.kty.decrypt_private(encrypted_key, rsa_padding: rsa_padding, rsa_oaep_md: rsa_oaep_md)
46
+ else
47
+ raise ArgumentError, "'key' must be a JOSE::JWK"
48
+ end
49
+ end
50
+
51
+ def key_encrypt(key, enc, decrypted_key)
52
+ if key.is_a?(JOSE::JWK)
53
+ return key.kty.encrypt_public(decrypted_key, rsa_padding: rsa_padding, rsa_oaep_md: rsa_oaep_md), self
54
+ else
55
+ raise ArgumentError, "'key' must be a JOSE::JWK"
56
+ end
57
+ end
58
+
59
+ def next_cek(key, enc)
60
+ return enc.next_cek
61
+ end
62
+
63
+ end
@@ -0,0 +1,8 @@
1
+ module JOSE::JWE::ENC
2
+
3
+ extend self
4
+
5
+ end
6
+
7
+ require 'jose/jwe/enc_aes_cbc_hmac'
8
+ require 'jose/jwe/enc_aes_gcm'
@@ -0,0 +1,80 @@
1
+ class JOSE::JWE::ENC_AES_CBC_HMAC < Struct.new(:cipher_name, :bits, :cek_len, :iv_len, :enc_len, :mac_len, :tag_len, :hmac)
2
+
3
+ # JOSE::JWE callbacks
4
+
5
+ def self.from_map(fields)
6
+ case fields['enc']
7
+ when 'A128CBC-HS256'
8
+ return new('aes-128-cbc', 256, 32, 16, 16, 16, 16, OpenSSL::Digest::SHA256), fields.delete('enc')
9
+ when 'A192CBC-HS384'
10
+ return new('aes-192-cbc', 384, 48, 16, 24, 24, 24, OpenSSL::Digest::SHA384), fields.delete('enc')
11
+ when 'A256CBC-HS512'
12
+ return new('aes-256-cbc', 512, 64, 16, 32, 32, 32, OpenSSL::Digest::SHA512), fields.delete('enc')
13
+ else
14
+ raise ArgumentError, "invalid 'enc' for JWE: #{fields['enc'].inspect}"
15
+ end
16
+ end
17
+
18
+ def to_map(fields)
19
+ return fields.put('enc', algorithm)
20
+ end
21
+
22
+ # JOSE::JWE::ENC callbacks
23
+
24
+ def algorithm
25
+ case cipher_name
26
+ when 'aes-128-cbc'
27
+ return 'A128CBC-HS256'
28
+ when 'aes-192-cbc'
29
+ return 'A192CBC-HS384'
30
+ when 'aes-256-cbc'
31
+ return 'A256CBC-HS512'
32
+ else
33
+ raise ArgumentError, "unhandled JOSE::JWE::ENC_AES_CBC_HMAC cipher name: #{cipher_name.inspect}"
34
+ end
35
+ end
36
+
37
+ def block_decrypt(aad_cipher_text_cipher_tag, cek, iv)
38
+ aad, cipher_text, cipher_tag = aad_cipher_text_cipher_tag
39
+ cek_s = StringIO.new(cek)
40
+ mac_key = cek_s.read(mac_len)
41
+ enc_key = cek_s.read(enc_len)
42
+ aad_len = [(aad.bytesize * 8)].pack('Q>')
43
+ mac_data = [aad, iv, cipher_text, aad_len].pack('a*a*a*a*')
44
+ if cipher_tag != OpenSSL::HMAC.digest(hmac.new, mac_key, mac_data)[0..tag_len]
45
+ raise ArgumentError, "decryption error"
46
+ else
47
+ cipher = OpenSSL::Cipher.new(cipher_name)
48
+ cipher.decrypt
49
+ cipher.key = cek
50
+ cipher.iv = iv
51
+ plain_text = JOSE::JWA::PKCS7.unpad(cipher.update(cipher_text) + cipher.final)
52
+ return plain_text
53
+ end
54
+ end
55
+
56
+ def block_encrypt(aad_plain_text, cek, iv)
57
+ aad, plain_text = aad_plain_text
58
+ cek_s = StringIO.new(cek)
59
+ mac_key = cek_s.read(mac_len)
60
+ enc_key = cek_s.read(enc_len)
61
+ cipher = OpenSSL::Cipher.new(cipher_name)
62
+ cipher.encrypt
63
+ cipher.key = cek
64
+ cipher.iv = iv
65
+ cipher_text = cipher.update(JOSE::JWA::PKCS7.pad(plain_text)) + cipher.final
66
+ aad_len = [(aad.bytesize * 8)].pack('Q>')
67
+ mac_data = [aad, iv, cipher_text, aad_len].pack('a*a*a*a*')
68
+ cipher_tag = OpenSSL::HMAC.digest(hmac.new, mac_key, mac_data)[0..tag_len]
69
+ return cipher_text, cipher_tag
70
+ end
71
+
72
+ def next_cek
73
+ return SecureRandom.random_bytes(cek_len)
74
+ end
75
+
76
+ def next_iv
77
+ return SecureRandom.random_bytes(iv_len)
78
+ end
79
+
80
+ end
@@ -0,0 +1,68 @@
1
+ class JOSE::JWE::ENC_AES_GCM < 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 'A128GCM'
8
+ return new('aes-128-gcm', 128, 16, 12), fields.delete('enc')
9
+ when 'A192GCM'
10
+ return new('aes-192-gcm', 192, 24, 12), fields.delete('enc')
11
+ when 'A256GCM'
12
+ return new('aes-256-gcm', 256, 32, 12), fields.delete('enc')
13
+ else
14
+ raise ArgumentError, "invalid 'enc' for JWE: #{fields['enc'].inspect}"
15
+ end
16
+ end
17
+
18
+ def to_map(fields)
19
+ return fields.put('enc', algorithm)
20
+ end
21
+
22
+ # JOSE::JWE::ENC callbacks
23
+
24
+ def algorithm
25
+ case cipher_name
26
+ when 'aes-128-gcm'
27
+ return 'A128GCM'
28
+ when 'aes-192-gcm'
29
+ return 'A192GCM'
30
+ when 'aes-256-gcm'
31
+ return 'A256GCM'
32
+ else
33
+ raise ArgumentError, "unhandled JOSE::JWE::ENC_AES_GCM cipher name: #{cipher_name.inspect}"
34
+ end
35
+ end
36
+
37
+ def block_decrypt(aad_cipher_text_cipher_tag, cek, iv)
38
+ aad, cipher_text, cipher_tag = aad_cipher_text_cipher_tag
39
+ cipher = OpenSSL::Cipher.new(cipher_name)
40
+ cipher.decrypt
41
+ cipher.key = cek
42
+ cipher.iv = iv
43
+ cipher.auth_data = aad
44
+ cipher.auth_tag = cipher_tag
45
+ plain_text = cipher.update(cipher_text) + cipher.final
46
+ return plain_text
47
+ end
48
+
49
+ def block_encrypt(aad_plain_text, cek, iv)
50
+ aad, plain_text = aad_plain_text
51
+ cipher = OpenSSL::Cipher.new(cipher_name)
52
+ cipher.encrypt
53
+ cipher.key = cek
54
+ cipher.iv = iv
55
+ cipher.auth_data = aad
56
+ cipher_text = cipher.update(plain_text) + cipher.final
57
+ return cipher_text, cipher.auth_tag
58
+ end
59
+
60
+ def next_cek
61
+ return SecureRandom.random_bytes(cek_len)
62
+ end
63
+
64
+ def next_iv
65
+ return SecureRandom.random_bytes(iv_len)
66
+ end
67
+
68
+ end