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
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