jose 0.1.0

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