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
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # JOSE
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/jose`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'jose'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install jose
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/jose. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
36
+
37
+
38
+ ## License
39
+
40
+ The gem is available as open source under the terms of the [MPL-2.0 License](http://opensource.org/licenses/MPL-2.0).
41
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "jose"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ require "pry"
11
+ Pry.start
12
+
13
+ # require "irb"
14
+ # IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
data/jose.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'jose/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "jose"
8
+ spec.version = JOSE::VERSION
9
+ spec.authors = ["Andrew Bennett"]
10
+ spec.email = ["andrew@pixid.com"]
11
+
12
+ spec.summary = %q{JSON Object Signing and Encryption}
13
+ spec.description = %q{JSON Object Signing and Encryption}
14
+ spec.homepage = "https://github.com/potatosalad/ruby-jose"
15
+ spec.license = "MPL-2.0"
16
+
17
+ # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
18
+ # delete this section to allow pushing this gem to any host.
19
+ # if spec.respond_to?(:metadata)
20
+ # spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
21
+ # else
22
+ # raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
23
+ # end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_dependency "hamster"
31
+
32
+ spec.add_development_dependency "bundler", "~> 1.10"
33
+ spec.add_development_dependency "rake", "~> 10.0"
34
+ spec.add_development_dependency "minitest"
35
+ spec.add_development_dependency "json"
36
+ end
data/lib/jose.rb ADDED
@@ -0,0 +1,61 @@
1
+ require 'jose/version'
2
+
3
+ require 'base64'
4
+ require 'hamster/hash'
5
+ require 'json'
6
+ require 'openssl'
7
+
8
+ module JOSE
9
+ class Map < Hamster::Hash; end
10
+ end
11
+
12
+ require 'jose/jwa'
13
+ require 'jose/jwe'
14
+ require 'jose/jwk'
15
+ require 'jose/jws'
16
+ require 'jose/jwt'
17
+
18
+ module JOSE
19
+
20
+ extend self
21
+
22
+ def decode(binary)
23
+ return JSON.load(binary)
24
+ end
25
+
26
+ def encode(term)
27
+ return JSON.dump(sort_maps(term))
28
+ end
29
+
30
+ def urlsafe_decode64(binary)
31
+ case binary.bytesize % 4
32
+ when 2
33
+ binary += '=='
34
+ when 3
35
+ binary += '='
36
+ end
37
+ return Base64.urlsafe_decode64(binary)
38
+ end
39
+
40
+ def urlsafe_encode64(binary)
41
+ return Base64.urlsafe_encode64(binary).tr('=', '')
42
+ end
43
+
44
+ private
45
+
46
+ def sort_maps(term)
47
+ case term
48
+ when Hash, JOSE::Map
49
+ return term.keys.sort.each_with_object(Hash.new) do |key, hash|
50
+ hash[key] = sort_maps(term[key])
51
+ end
52
+ when Array
53
+ return term.map do |item|
54
+ sort_maps(item)
55
+ end
56
+ else
57
+ return term
58
+ end
59
+ end
60
+
61
+ end
data/lib/jose/jwa.rb ADDED
@@ -0,0 +1,21 @@
1
+ module JOSE
2
+ module JWA
3
+
4
+ extend self
5
+
6
+ def constant_time_compare(a, b)
7
+ return false if a.empty? || b.empty? || a.bytesize != b.bytesize
8
+ l = a.unpack "C#{a.bytesize}"
9
+
10
+ res = 0
11
+ b.each_byte { |byte| res |= byte ^ l.shift }
12
+ return res == 0
13
+ end
14
+
15
+ end
16
+ end
17
+
18
+ require 'jose/jwa/aes_kw'
19
+ require 'jose/jwa/concat_kdf'
20
+ require 'jose/jwa/pkcs1'
21
+ require 'jose/jwa/pkcs7'
@@ -0,0 +1,105 @@
1
+ module JOSE::JWA::AES_KW
2
+
3
+ extend self
4
+
5
+ DEFAULT_IV = OpenSSL::BN.new(0xA6A6A6A6A6A6A6A6).to_s(2)
6
+
7
+ def unwrap(cipher_text, kek, iv = nil)
8
+ iv ||= DEFAULT_IV
9
+ bits = kek.bytesize * 8
10
+ if cipher_text.bytesize % 8 == 0 and (bits == 128 or bits == 192 or bits == 256)
11
+ block_count = cipher_text.bytesize.div(8) - 1
12
+ buffer = do_unwrap(cipher_text, 5, block_count, kek, bits)
13
+ buffer_s = StringIO.new(buffer)
14
+ if buffer_s.read(iv.bytesize) != iv
15
+ raise ArgumentError, "iv does not match"
16
+ else
17
+ plain_text = buffer_s.read
18
+ return plain_text
19
+ end
20
+ else
21
+ raise ArgumentError, "bad cipher_text, kek, or iv"
22
+ end
23
+ end
24
+
25
+ def wrap(plain_text, kek, iv = nil)
26
+ iv ||= DEFAULT_IV
27
+ bits = kek.bytesize * 8
28
+ if plain_text.bytesize % 8 == 0 and (bits == 128 or bits == 192 or bits == 256)
29
+ buffer = [iv, plain_text].join
30
+ block_count = buffer.bytesize.div(8) - 1
31
+ return do_wrap(buffer, 0, block_count, kek, bits)
32
+ else
33
+ raise ArgumentError, "bad plain_text, kek, or iv"
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def aes_ecb_decrypt(bits, key, cipher_text)
40
+ cipher = OpenSSL::Cipher::AES.new(bits, :ECB)
41
+ cipher.decrypt
42
+ cipher.key = key
43
+ cipher.padding = 0
44
+ return cipher.update(cipher_text) + cipher.final
45
+ end
46
+
47
+ def aes_ecb_encrypt(bits, key, plain_text)
48
+ cipher = OpenSSL::Cipher::AES.new(bits, :ECB)
49
+ cipher.encrypt
50
+ cipher.key = key
51
+ cipher.padding = 0
52
+ return cipher.update(plain_text) + cipher.final
53
+ end
54
+
55
+ def do_unwrap(buffer, j, block_count, kek, bits)
56
+ if j < 0
57
+ return buffer
58
+ else
59
+ return do_unwrap(do_unwrap_step(buffer, j, block_count, block_count, kek, bits), j - 1, block_count, kek, bits)
60
+ end
61
+ end
62
+
63
+ def do_unwrap_step(buffer, j, i, block_count, kek, bits)
64
+ if i < 1
65
+ return buffer
66
+ end
67
+ buffer_s = StringIO.new(buffer)
68
+ a0, = buffer_s.read(8).unpack('Q>')
69
+ head_size = (i - 1) * 8
70
+ head = buffer_s.read(head_size)
71
+ b0 = buffer_s.read(8)
72
+ tail = buffer_s.read
73
+ round = (block_count * j) + i
74
+ a1 = a0 ^ round
75
+ data = [a1, b0].pack('Q>a*')
76
+ a2, b1 = aes_ecb_decrypt(bits, kek, data).unpack('Q>a*')
77
+ return do_unwrap_step([a2, head, b1, tail].pack('Q>a*a*a*'), j, i - 1, block_count, kek, bits)
78
+ end
79
+
80
+ def do_wrap(buffer, j, block_count, kek, bits)
81
+ if j == 6
82
+ return buffer
83
+ else
84
+ return do_wrap(do_wrap_step(buffer, j, 1, block_count, kek, bits), j + 1, block_count, kek, bits)
85
+ end
86
+ end
87
+
88
+ def do_wrap_step(buffer, j, i, block_count, kek, bits)
89
+ if i > block_count
90
+ return buffer
91
+ end
92
+ buffer_s = StringIO.new(buffer)
93
+ a0 = buffer_s.read(8)
94
+ head_size = (i - 1) * 8
95
+ head = buffer_s.read(head_size)
96
+ b0 = buffer_s.read(8)
97
+ tail = buffer_s.read
98
+ round = (block_count * j) + i
99
+ data = [a0, b0].join
100
+ a1, b1 = aes_ecb_encrypt(bits, kek, data).unpack('Q>a*')
101
+ a2 = a1 ^ round
102
+ return do_wrap_step([a2, head, b1, tail].pack('Q>a*a*a*'), j, i + 1, block_count, kek, bits)
103
+ end
104
+
105
+ end
@@ -0,0 +1,50 @@
1
+ module JOSE::JWA::ConcatKDF
2
+
3
+ extend self
4
+
5
+ def kdf(hash, z, other_info, key_data_len = nil)
6
+ if hash.is_a?(String)
7
+ hash = OpenSSL::Digest.new(hash)
8
+ end
9
+ if key_data_len.nil?
10
+ key_data_len = hash.digest('').bytesize * 8
11
+ end
12
+ if other_info.is_a?(Array)
13
+ algorithm_id, party_u_info, party_v_info, supp_pub_info, supp_priv_info = other_info
14
+ supp_pub_info ||= ''
15
+ supp_priv_info ||= ''
16
+ other_info = [
17
+ algorithm_id.bytesize, algorithm_id,
18
+ party_u_info.bytesize, party_u_info,
19
+ party_v_info.bytesize, party_v_info,
20
+ supp_pub_info,
21
+ supp_priv_info
22
+ ].pack('Na*Na*Na*a*a*')
23
+ end
24
+ hash_len = hash.digest('').bytesize * 8
25
+ reps = (key_data_len / hash_len.to_f).ceil
26
+ if reps == 1
27
+ concatenation = [ 0, 0, 0, 1, z, other_info ].pack('C4a*a*')
28
+ derived_key = [hash.digest(concatenation).unpack('B*')[0][0...key_data_len]].pack('B*')
29
+ return derived_key
30
+ elsif reps > 0xFFFFFFFF
31
+ raise ArgumentError, "too many reps"
32
+ else
33
+ return derive_key(hash, 1, reps, key_data_len, [z, other_info].join, '')
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def derive_key(hash, counter, reps, key_data_len, z_other_info, derived_keying_material)
40
+ if counter == reps
41
+ concatenation = [counter, z_other_info].pack('Na*')
42
+ derived_key = [[derived_keying_material, hash.digest(concatenation)].join.unpack('B*')[0][0...key_data_len]].pack('B*')
43
+ return derived_key
44
+ else
45
+ concatenation = [counter, z_other_info].pack('Na*')
46
+ return derive_key(hash, counter + 1, reps, key_data_len, z_other_info, [derived_keying_material, hash.digest(concatenation)].join)
47
+ end
48
+ end
49
+
50
+ end
@@ -0,0 +1,269 @@
1
+ module JOSE::JWA::PKCS1
2
+
3
+ extend self
4
+
5
+ def eme_oaep_decode(hash, em, label, k)
6
+ if hash.is_a?(String)
7
+ hash = OpenSSL::Digest.new(hash)
8
+ end
9
+ h_len = hash.digest('').bytesize
10
+ l_hash = hash.digest(label)
11
+ masked_db_len = k - h_len - 1
12
+ em_s = StringIO.new(em)
13
+ y = em_s.getbyte
14
+ if y != 0x00
15
+ raise ArgumentError, "decryption_error"
16
+ end
17
+ masked_seed = em_s.read(h_len)
18
+ masked_db = em_s.read(masked_db_len)
19
+ seed_mask = mgf1(hash, masked_db, h_len)
20
+ seed = exor(masked_seed, seed_mask)
21
+ db_mask = mgf1(hash, seed, k - h_len - 1)
22
+ db = exor(masked_db, db_mask)
23
+ db_s = StringIO.new(db)
24
+ l_hash_prime = db_s.read(h_len)
25
+ if l_hash != l_hash_prime
26
+ raise ArgumentError, "decryption_error"
27
+ end
28
+ db_right = db_s.read
29
+ sep, m = unpad_zero(db_right).unpack('Ca*')
30
+ if sep != 0x01
31
+ raise ArgumentError, "decryption_error"
32
+ end
33
+ return m
34
+ end
35
+
36
+ def eme_oaep_encode(hash, dm, label, seed, k)
37
+ if hash.is_a?(String)
38
+ hash = OpenSSL::Digest.new(hash)
39
+ end
40
+ h_len = hash.digest('').bytesize
41
+ m_len = dm.bytesize
42
+ l_hash = hash.digest(label)
43
+ ps_len = (k - m_len - (2 * h_len) - 2)
44
+ ps = if ps_len > 0
45
+ ([0] * ps_len).pack('C*')
46
+ else
47
+ ''
48
+ end
49
+ db = [l_hash, ps, 0x01, dm].pack('a*a*Ca*')
50
+ db_mask = mgf1(hash, seed, k - h_len - 1)
51
+ masked_db = exor(db, db_mask)
52
+ seed_mask = mgf1(hash, masked_db, h_len)
53
+ masked_seed = exor(seed, seed_mask)
54
+ em = [0x00, masked_seed, masked_db].pack('Ca*a*')
55
+ return em
56
+ end
57
+
58
+ def emsa_pss_encode(hash, message, salt, em_bits)
59
+ if hash.is_a?(String)
60
+ hash = OpenSSL::Digest.new(hash)
61
+ end
62
+ salt ||= -2
63
+ if salt.is_a?(Integer)
64
+ salt_len = salt
65
+ if salt_len == -2
66
+ hash_len = hash.digest('').bytesize
67
+ em_len = (em_bits / 8.0).ceil
68
+ salt_len = em_len - hash_len - 2
69
+ if salt_len < 0
70
+ raise ArgumentError, "encoding_error"
71
+ end
72
+ elsif salt_len == -1
73
+ hash_len = hash.digest('').bytesize
74
+ salt_len = hash_len
75
+ end
76
+ if salt_len < 0
77
+ raise ArgumentError, "unhandled salt length: #{salt_len.inspect}"
78
+ end
79
+ salt = SecureRandom.random_bytes(salt_len)
80
+ end
81
+ m_hash = hash.digest(message)
82
+ hash_len = m_hash.bytesize
83
+ salt_len = salt.bytesize
84
+ em_len = (em_bits / 8.0).ceil
85
+ if em_len < (hash_len + salt_len + 2)
86
+ raise ArgumentError, "encoding_error"
87
+ else
88
+ m_prime = [0x00].pack('Q').concat(m_hash).concat(salt)
89
+ h = hash.digest(m_prime)
90
+ ps = ([0x00] * (em_len - salt_len - hash_len - 2)).pack('C*')
91
+ db = [ps, 0x01, salt].pack('a*Ca*')
92
+ db_mask = mgf1(hash, h, em_len - hash_len - 1)
93
+ left_bits = (em_len * 8) - em_bits
94
+ masked_db_right = exor(db, db_mask).unpack('B*')[0][left_bits..-1]
95
+ masked_db = [('0' * left_bits).concat(masked_db_right)].pack('B*')
96
+ em = [masked_db, h, 0xBC].pack('a*a*C')
97
+ return em
98
+ end
99
+ end
100
+
101
+ def emsa_pss_verify(hash, message, em, salt_len, em_bits)
102
+ if hash.is_a?(String)
103
+ hash = OpenSSL::Digest.new(hash)
104
+ end
105
+ salt_len ||= -2
106
+ if salt_len == -2
107
+ hash_len = hash.digest('').bytesize
108
+ em_len = (em_bits / 8.0).ceil
109
+ salt_len = em_len - hash_len - 2
110
+ if salt_len < 0
111
+ return false
112
+ end
113
+ elsif salt_len == -1
114
+ hash_len = hash.digest('').bytesize
115
+ salt_len = hash_len
116
+ end
117
+ if salt_len < 0
118
+ raise ArgumentError, "unhandled salt length: #{salt_len.inspect}"
119
+ end
120
+ m_hash = hash.digest(message)
121
+ hash_len = m_hash.bytesize
122
+ em_len = (em_bits / 8.0).ceil
123
+ masked_db_len = (em_len - hash_len - 1)
124
+ if (em.bytesize != em_len) or (em_len < (hash_len + salt_len + 2))
125
+ return false
126
+ else
127
+ em_s = StringIO.new(em)
128
+ masked_db = em_s.read(masked_db_len)
129
+ h = em_s.read(hash_len)
130
+ if em_s.getbyte != 0xBC
131
+ return false
132
+ end
133
+ left_bits = ((em_len * 8) - em_bits)
134
+ if (left_bits > 0) and (masked_db.unpack("B#{left_bits}").pack('B*').unpack('C')[0] != 0x00)
135
+ return false
136
+ end
137
+ db_mask = mgf1(hash, h, em_len - hash_len - 1) rescue nil
138
+ if db_mask.nil?
139
+ return false
140
+ end
141
+ db_right = exor(masked_db, db_mask).unpack('B*')[0][left_bits..-1]
142
+ db = [('0' * left_bits).concat(db_right)].pack('B*')
143
+ ps_len = (em_len - hash_len - salt_len - 2)
144
+ db_s = StringIO.new(db)
145
+ ps = OpenSSL::BN.new(db_s.read(ps_len), 2)
146
+ if ps != 0
147
+ return false
148
+ end
149
+ sep = db_s.getbyte
150
+ if sep != 0x01
151
+ return false
152
+ end
153
+ salt = db_s.read(salt_len)
154
+ m_prime = [0x00].pack('Q').concat(m_hash).concat(salt)
155
+ h_prime = hash.digest(m_prime)
156
+ return h == h_prime
157
+ end
158
+ end
159
+
160
+ def mgf1(hash, seed, mask_len)
161
+ hash_len = hash.digest('').bytesize
162
+ if mask_len > (0xFFFFFFFF * hash_len)
163
+ raise ArgumentError, "mask_too_long"
164
+ else
165
+ reps = (mask_len / hash_len.to_f).ceil
166
+ return derive_mgf1(hash, 0, reps, seed, mask_len, '')
167
+ end
168
+ end
169
+
170
+ def rsaes_oaep_decrypt(hash, cipher_text, rsa_private_key, label = nil)
171
+ if hash.is_a?(String)
172
+ hash = OpenSSL::Digest.new(hash)
173
+ end
174
+ label ||= ''
175
+ h_len = hash.digest('').bytesize
176
+ k = rsa_private_key.n.num_bytes
177
+ if cipher_text.bytesize != k or k < ((2 * h_len) + 2)
178
+ raise ArgumentError, "decryption_error"
179
+ end
180
+ em = pad_to_key_size(k, dp(OpenSSL::BN.new(cipher_text, 2), rsa_private_key).to_s(2))
181
+ return eme_oaep_decode(hash, em, label, k)
182
+ end
183
+
184
+ def rsaes_oaep_encrypt(hash, plain_text, rsa_public_key, label = nil, seed = nil)
185
+ if hash.is_a?(String)
186
+ hash = OpenSSL::Digest.new(hash)
187
+ end
188
+ label ||= ''
189
+ h_len = hash.digest('').bytesize
190
+ seed ||= SecureRandom.random_bytes(h_len)
191
+ m_len = plain_text.bytesize
192
+ k = rsa_public_key.n.num_bytes
193
+ if m_len > (k - (2 * h_len) - 2)
194
+ raise ArgumentError, "message_too_long"
195
+ else
196
+ em = eme_oaep_encode(hash, plain_text, label, seed, k)
197
+ c = pad_to_key_size(k, ep(OpenSSL::BN.new(em, 2), rsa_public_key).to_s(2))
198
+ return c
199
+ end
200
+ end
201
+
202
+ def rsassa_pss_sign(hash, message, rsa_private_key, salt = nil)
203
+ mod_bits = rsa_private_key.n.num_bits
204
+ em = emsa_pss_encode(hash, message, salt, mod_bits - 1)
205
+ mod_bytes = rsa_private_key.n.num_bytes
206
+ s = pad_to_key_size(mod_bytes, dp(OpenSSL::BN.new(em, 2), rsa_private_key).to_s(2))
207
+ return s
208
+ end
209
+
210
+ def rsassa_pss_verify(hash, message, signature, rsa_public_key, salt_len = nil)
211
+ mod_bytes = rsa_public_key.n.num_bytes
212
+ if signature.bytesize != mod_bytes
213
+ return false
214
+ else
215
+ mod_bits = rsa_public_key.n.num_bits
216
+ em = pad_to_key_size(((mod_bits - 1) / 8.0).ceil, ep(OpenSSL::BN.new(signature, 2), rsa_public_key).to_s(2))
217
+ return emsa_pss_verify(hash, message, em, salt_len, mod_bits - 1)
218
+ end
219
+ end
220
+
221
+ private
222
+
223
+ def derive_mgf1(hash, counter, reps, seed, mask_len, t)
224
+ if counter == reps
225
+ return t[0...mask_len]
226
+ else
227
+ counter_bin = [counter].pack('N')
228
+ new_t = [t, hash.digest([seed, counter_bin].pack('a*a*'))].pack('a*a*')
229
+ return derive_mgf1(hash, counter + 1, reps, seed, mask_len, new_t)
230
+ end
231
+ end
232
+
233
+ def dp(b, rsa)
234
+ return b.mod_exp(rsa.d, rsa.n)
235
+ end
236
+
237
+ def ep(b, rsa)
238
+ return b.mod_exp(rsa.e, rsa.n)
239
+ end
240
+
241
+ def exor(d1, d2)
242
+ if d1.bytesize != d2.bytesize
243
+ raise ArgumentError, "'d1' and 'd2' must have the same bytesize"
244
+ end
245
+ d1 = d1.unpack('C*')
246
+ d2 = d2.unpack('C*')
247
+ d1.length.times do |i|
248
+ d1[i] ^= d2[i]
249
+ end
250
+ return d1.pack('C*')
251
+ end
252
+
253
+ def pad_to_key_size(bytes, data)
254
+ if data.bytesize < bytes
255
+ return pad_to_key_size(bytes, [0x00].pack('C').concat(data))
256
+ else
257
+ return data
258
+ end
259
+ end
260
+
261
+ def unpad_zero(binary)
262
+ if binary.getbyte(0) == 0x00
263
+ return unpad_zero(binary[1..-1])
264
+ else
265
+ return binary
266
+ end
267
+ end
268
+
269
+ end