megar 0.0.1

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 (51) hide show
  1. data/.gitignore +19 -0
  2. data/.rspec +1 -0
  3. data/.rvmrc +1 -0
  4. data/.travis.yml +11 -0
  5. data/CHANGELOG +5 -0
  6. data/Gemfile +4 -0
  7. data/Guardfile +11 -0
  8. data/LICENSE +22 -0
  9. data/README.rdoc +218 -0
  10. data/Rakefile +33 -0
  11. data/bin/megar +16 -0
  12. data/lib/extensions/math.rb +13 -0
  13. data/lib/js_ref_impl/_README +9 -0
  14. data/lib/js_ref_impl/base64_1.js +83 -0
  15. data/lib/js_ref_impl/crypto_5.js +1795 -0
  16. data/lib/js_ref_impl/download_8.js +867 -0
  17. data/lib/js_ref_impl/hex_1.js +76 -0
  18. data/lib/js_ref_impl/index_9.js +666 -0
  19. data/lib/js_ref_impl/js.manifest +115 -0
  20. data/lib/js_ref_impl/rsa_1.js +456 -0
  21. data/lib/js_ref_impl/sjcl_1.js +1 -0
  22. data/lib/js_ref_impl/upload_10.js +691 -0
  23. data/lib/megar.rb +11 -0
  24. data/lib/megar/catalog.rb +5 -0
  25. data/lib/megar/catalog/catalog_item.rb +90 -0
  26. data/lib/megar/catalog/file.rb +14 -0
  27. data/lib/megar/catalog/files.rb +13 -0
  28. data/lib/megar/catalog/folder.rb +20 -0
  29. data/lib/megar/catalog/folders.rb +28 -0
  30. data/lib/megar/connection.rb +84 -0
  31. data/lib/megar/crypto.rb +399 -0
  32. data/lib/megar/exception.rb +55 -0
  33. data/lib/megar/session.rb +157 -0
  34. data/lib/megar/shell.rb +87 -0
  35. data/lib/megar/version.rb +3 -0
  36. data/megar.gemspec +30 -0
  37. data/spec/fixtures/crypto_expectations/sample_user.json +109 -0
  38. data/spec/spec_helper.rb +24 -0
  39. data/spec/support/crypto_expectations_helper.rb +44 -0
  40. data/spec/support/mocks_helper.rb +22 -0
  41. data/spec/unit/catalog/file_spec.rb +31 -0
  42. data/spec/unit/catalog/files_spec.rb +26 -0
  43. data/spec/unit/catalog/folder_spec.rb +28 -0
  44. data/spec/unit/catalog/folders_spec.rb +49 -0
  45. data/spec/unit/connection_spec.rb +50 -0
  46. data/spec/unit/crypto_spec.rb +476 -0
  47. data/spec/unit/exception_spec.rb +35 -0
  48. data/spec/unit/extensions/math_spec.rb +21 -0
  49. data/spec/unit/session_spec.rb +146 -0
  50. data/spec/unit/shell_spec.rb +18 -0
  51. metadata +238 -0
data/lib/megar.rb ADDED
@@ -0,0 +1,11 @@
1
+ require 'active_support/inflector'
2
+ require 'active_support/hash_with_indifferent_access'
3
+ require 'extensions/math'
4
+
5
+ require 'megar/version'
6
+ require 'megar/exception'
7
+ require 'megar/shell'
8
+ require 'megar/crypto'
9
+ require 'megar/connection'
10
+ require 'megar/session'
11
+ require 'megar/catalog'
@@ -0,0 +1,5 @@
1
+ require 'megar/catalog/catalog_item'
2
+ require 'megar/catalog/folders'
3
+ require 'megar/catalog/folder'
4
+ require 'megar/catalog/files'
5
+ require 'megar/catalog/file'
@@ -0,0 +1,90 @@
1
+ # This module defines the basic naming interface for catalog objects
2
+ # Override these methods as required
3
+ module Megar::CatalogItem
4
+ include Enumerable
5
+
6
+ # Adds an item to the local cached collection given +attributes+ hash:
7
+ # id: id / mega node handle
8
+ # payload: the literal mega node descriptor
9
+ # type: the folder type
10
+ # key: the decrypted folder key
11
+ # attributes: the decrypted attributes collection
12
+ def initialize(attributes={})
13
+ self.attributes = attributes
14
+ end
15
+
16
+ # The ID (Mega handle)
17
+ attr_accessor :id
18
+
19
+ # The folder name
20
+ attr_accessor :name
21
+ alias_method :n=, :name=
22
+
23
+ # The literal mega node descriptor (as received from API)
24
+ attr_accessor :payload
25
+
26
+ # Assigns the payload attribute, also splitting out separate attribute assignments from +value+ if a hash
27
+ def payload=(value)
28
+ self.attributes = value
29
+ @payload = value
30
+ end
31
+
32
+ # Assigns the attribute values splitting out separate attribute assignments from +value+ if a hash
33
+ def attributes=(value)
34
+ return unless value.respond_to?(:keys)
35
+ value.keys.each do |key|
36
+ if respond_to?(assignment = "#{key}=".to_sym)
37
+ send(assignment,value[key])
38
+ end
39
+ end
40
+ end
41
+
42
+ # The decrypted node key
43
+ attr_accessor :key
44
+
45
+ # The folder type id
46
+ # 0: File
47
+ # 1: Directory
48
+ # 2: Special node: Root (“Cloud Drive”)
49
+ # 3: Special node: Inbox
50
+ # 4: Special node: Trash Bin
51
+ attr_accessor :type
52
+
53
+
54
+ # Generic interface to return the currently applicable collection
55
+ def collection
56
+ @collection ||= []
57
+ end
58
+
59
+ # Returns the expected class of items in the collection
60
+ def resource_class
61
+ "#{self.class.name}".chomp('s').constantize
62
+ end
63
+
64
+ # Command: clears/re-initialises the collection
65
+ def reset!
66
+ @collection = []
67
+ end
68
+
69
+ # Implements Enumerable#each
70
+ def each
71
+ collection.each { |item| yield item }
72
+ end
73
+
74
+ # Returns indexed elements from the collection
75
+ def [](*args)
76
+ collection[*args]
77
+ end
78
+
79
+ # Equality based on ID
80
+ def ==(other)
81
+ self.id == other.id
82
+ end
83
+ alias_method :eql?, :==
84
+
85
+ # Returns the first record matching +type+
86
+ def find_by_type(type)
87
+ find { |r| r.type == type }
88
+ end
89
+
90
+ end
@@ -0,0 +1,14 @@
1
+ # File collection item
2
+ class Megar::File
3
+ include Megar::CatalogItem
4
+
5
+ # The file size
6
+ attr_accessor :size
7
+ alias_method :s=, :size=
8
+
9
+ # Return a pretty version of the file record
10
+ def to_s
11
+ format("%16d bytes %-10s %-60s", size.to_i, id, name)
12
+ end
13
+
14
+ end
@@ -0,0 +1,13 @@
1
+ # Collection manager for files
2
+ class Megar::Files
3
+ include Megar::CatalogItem
4
+
5
+ def initialize(options={})
6
+ end
7
+
8
+ # Adds an item to the local cached collection given +attributes+ hash.
9
+ def add(attributes)
10
+ collection << Megar::File.new(attributes)
11
+ end
12
+
13
+ end
@@ -0,0 +1,20 @@
1
+ # Folder collection item
2
+ class Megar::Folder
3
+ include Megar::CatalogItem
4
+
5
+ # Name assignment with special-purpose name support
6
+ def name=(value)
7
+ @name = if "#{value}" != ""
8
+ value
9
+ elsif type.is_a?(Fixnum)
10
+ ["Cloud Drive","Inbox","Trash Bin"][type-2]
11
+ end
12
+ end
13
+
14
+ # Override initialisation to set special-purpose names
15
+ def initialize(attributes={})
16
+ super
17
+ self.name = nil unless name
18
+ end
19
+
20
+ end
@@ -0,0 +1,28 @@
1
+ # Collection manager for folders
2
+ class Megar::Folders
3
+ include Megar::CatalogItem
4
+
5
+ def initialize(options={})
6
+ end
7
+
8
+ # Adds an item to the local cached collection given +attributes+ hash.
9
+ def add(attributes)
10
+ collection << Megar::Folder.new(attributes)
11
+ end
12
+
13
+ # Returns the root (cloud drive) folder
14
+ def root
15
+ @root ||= find_by_type(2)
16
+ end
17
+
18
+ # Returns the inbox folder
19
+ def inbox
20
+ @inbox ||= find_by_type(3)
21
+ end
22
+
23
+ # Returns the trash folder
24
+ def trash
25
+ @trash ||= find_by_type(4)
26
+ end
27
+
28
+ end
@@ -0,0 +1,84 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'uri'
4
+ require 'json'
5
+
6
+ # A simple module to encapsulate network interation
7
+ #
8
+ module Megar::Connection
9
+
10
+ attr_accessor :sid
11
+
12
+ # Returns the current session sequence_number.
13
+ # On first request, it is initialised to a random integer.
14
+ def sequence_number
15
+ @sequence_number ||= rand(0xFFFFFFFF)
16
+ end
17
+
18
+ # Set the secuence number to +value+ (Fixnum)
19
+ def sequence_number=(value)
20
+ @sequence_number = value
21
+ end
22
+
23
+ # Command: increments and returns the next sequence number
24
+ def next_sequence_number!
25
+ sequence_number && @sequence_number += 1
26
+ end
27
+
28
+ # There seem to be a number of regional API enpoints,
29
+ # but not sure if there is any guidance yet as to which you should use.
30
+ # Known endpoints: https://g.api.mega.co.nz/cs, https://eu.api.mega.co.nz/cs
31
+ DEFAULT_API_ENDPOINT = 'https://eu.api.mega.co.nz/cs'
32
+
33
+ # Return the API endpoint url (String) - defaults to DEFAULT_API_ENDPOINT
34
+ def api_endpoint
35
+ @api_endpoint ||= DEFAULT_API_ENDPOINT
36
+ end
37
+
38
+ # Set the API endpoint url to +value+ (String)
39
+ def api_endpoint=(value)
40
+ @api_endpoint = value
41
+ end
42
+
43
+ # Returns the API endpoint uri
44
+ def api_uri
45
+ @api_uri ||= URI.parse(api_endpoint)
46
+ end
47
+
48
+ # Command: Perform a single API request given +data+
49
+ def api_request(data)
50
+ params = {'id' => next_sequence_number!}
51
+ params['sid'] = sid if sid
52
+ json_data = [data].to_json
53
+
54
+ response_data = get_api_response(params,json_data).first
55
+
56
+ raise Megar::MegaRequestError.new(response_data) if response_data.is_a?(Fixnum)
57
+
58
+ response_data
59
+ end
60
+
61
+ # Command: low-level method to actually perform the API request and return the JSON response.
62
+ # Given +params+ Hash of query string parameters, and +data+ JSON data structure.
63
+ # Note: there is no handling of network errors or timeouts - any exceptions will bubble up.
64
+ def get_api_response(params,data)
65
+ http = Net::HTTP.new(api_uri.host, api_uri.port)
66
+ http.use_ssl = (api_uri.scheme == 'https')
67
+ uri_path = api_uri.path.empty? ? '/' : api_uri.path
68
+ uri_path << hash_to_query_string(params)
69
+ response = http.post(uri_path,data)
70
+ JSON.parse(response.body)
71
+ end
72
+ protected :get_api_response
73
+
74
+ # Returns Hash +h+ as an encoded query string '?a=b&c=d...'
75
+ def hash_to_query_string(h)
76
+ if qs = URI.escape(h.to_a.map{|e| e.join('=') }.join('&'))
77
+ '?' + qs
78
+ else
79
+ ''
80
+ end
81
+ end
82
+ protected :hash_to_query_string
83
+
84
+ end
@@ -0,0 +1,399 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+
4
+ # A straight-forward "quirks-mode" transcoding of core crypto methods required to talk to Mega.
5
+ # Some of this reeks a bit .. maybe more idiomatic ruby approaches are possible.
6
+ #
7
+ # Generally we're using signed 32-bit by default here ... I don't think it's necessary, but it makes comparison with
8
+ # the javascript implementation easier.
9
+ #
10
+ # Javascript reference implementations quoted here are taken from the Mega javascript source.
11
+ #
12
+ module Megar::Crypto
13
+
14
+ # Returns encrypted key given an array +a+ of 32-bit integers
15
+ #
16
+ # Javascript reference implementation: function prepare_key(a)
17
+ #
18
+ def prepare_key(a)
19
+ pkey = [0x93C467E3, 0x7DB0C7A4, 0xD1BE3F81, 0x0152CB56]
20
+ 0x10000.times do
21
+ (0..(a.length-1)).step(4) do |j|
22
+ key = [0,0,0,0]
23
+ 4.times {|i| key[i] = a[i+j] if (i+j < a.length) }
24
+ pkey = aes_encrypt_a32(pkey,key)
25
+ end
26
+ end
27
+ pkey
28
+ end
29
+
30
+ # Returns encrypted key given the plain-text +password+ string
31
+ #
32
+ # Javascript reference implementation: function prepare_key_pw(password)
33
+ #
34
+ def prepare_key_pw(password)
35
+ prepare_key(str_to_a32(password))
36
+ end
37
+
38
+ # Returns a decrypted given an array +a+ of 32-bit integers and +key+
39
+ #
40
+ # Javascript reference implementation: function decrypt_key(cipher,a)
41
+ #
42
+ def decrypt_key(a, key)
43
+ b=[]
44
+ (0..(a.length-1)).step(4) do |i|
45
+ b.concat aes_cbc_decrypt_a32(a[i,4], key)
46
+ end
47
+ b
48
+ end
49
+
50
+ # Returns decrypted array of 32-bit integers representation of base64 +data+ decrypted using +key+
51
+ def decrypt_base64_to_a32(data,key)
52
+ decrypt_key(base64_to_a32(data), key)
53
+ end
54
+
55
+ # Returns decrypted string representation of base64 +data+ decrypted using +key+
56
+ def decrypt_base64_to_str(data,key)
57
+ a32_to_str(decrypt_base64_to_a32(data, key))
58
+ end
59
+
60
+
61
+ # Returns AES-128 encrypted given +key+ and +data+ (arrays of 32-bit signed integers)
62
+ def aes_encrypt_a32(data, key)
63
+ aes = OpenSSL::Cipher::Cipher.new('AES-128-ECB')
64
+ aes.encrypt
65
+ aes.padding = 0
66
+ aes.key = key.pack('l>*')
67
+ aes.update(data.pack('l>*')).unpack('l>*')
68
+ # e = aes.update(data.pack('l>*')).unpack('l>*')
69
+ # e << aes.final
70
+ # e.unpack('l>*')
71
+ end
72
+
73
+ # Returns AES-128 decrypted given +key+ and +data+ (arrays of 32-bit signed integers)
74
+ def aes_cbc_decrypt_a32(data, key)
75
+ str_to_a32(aes_cbc_decrypt(a32_to_str(data), a32_to_str(key)))
76
+ end
77
+
78
+ # Returns AES-128 decrypted given +key+ and +data+ (String)
79
+ def aes_cbc_decrypt(data, key)
80
+ aes = OpenSSL::Cipher::Cipher.new('AES-128-CBC')
81
+ aes.decrypt
82
+ aes.padding = 0
83
+ aes.key = key
84
+ aes.iv = "\0" * 16
85
+ d = aes.update(data)
86
+ d = aes.final if d.empty?
87
+ d
88
+ end
89
+
90
+ # Returns an array of 32-bit signed integers representing the string +b+
91
+ #
92
+ # Javascript reference implementation: function str_to_a32(b)
93
+ #
94
+ def str_to_a32(b)
95
+ a = Array.new((b.length+3) >> 2,0)
96
+ b.length.times { |i| a[i>>2] |= (b.getbyte(i) << (24-(i & 3)*8)) }
97
+ a.pack('l>*').unpack('l>*') # hack to force to signed 32-bit ... I don't think we really need to do this, but it makes comparison with
98
+ end
99
+
100
+ # Returns a packed string given an array +a+ of 32-bit signed integers
101
+ #
102
+ # Javascript reference implementation: function a32_to_str(a)
103
+ #
104
+ def a32_to_str(a)
105
+ b = ''
106
+ (a.size * 4).times { |i| b << ((a[i>>2] >> (24-(i & 3)*8)) & 255).chr }
107
+ b
108
+ end
109
+
110
+ # Returns a base64-encoding of string +s+ hashed with +aeskey+ key
111
+ #
112
+ # Javascript reference implementation: function stringhash(s,aes)
113
+ #
114
+ def stringhash(s,aeskey)
115
+ s32 = str_to_a32(s)
116
+ h32 = [0,0,0,0]
117
+ s32.length.times {|i| h32[i&3] ^= s32[i] }
118
+ 16384.times {|i| h32 = aes_encrypt_a32(h32, aeskey) }
119
+ a32_to_base64([h32[0],h32[2]])
120
+ end
121
+
122
+ # Returns a base64-encoding given an array +a+ of 32-bit integers
123
+ #
124
+ # Javascript reference implementation: function a32_to_base64(a)
125
+ #
126
+ def a32_to_base64(a)
127
+ base64urlencode(a32_to_str(a))
128
+ end
129
+
130
+ # Returns an array +a+ of 32-bit integers given a base64-encoded +b+ (String)
131
+ #
132
+ # Javascript reference implementation: function base64_to_a32(s)
133
+ #
134
+ def base64_to_a32(s)
135
+ str_to_a32(base64urldecode(s))
136
+ end
137
+
138
+ # Returns a base64-encoding given +data+ (String).
139
+ #
140
+ # Javascript reference implementation: function base64urlencode(data)
141
+ #
142
+ def base64urlencode(data)
143
+ Base64.urlsafe_encode64(data).gsub(/=*$/,'')
144
+ end
145
+
146
+ # Returns a string given +data+ (base64-encoded String)
147
+ #
148
+ # Javascript reference implementation: function base64urldecode(data)
149
+ #
150
+ def base64urldecode(data)
151
+ Base64.urlsafe_decode64(data + '=' * ((4 - data.length % 4) % 4))
152
+ end
153
+
154
+ # Returns multiple precision integer (MPI) as an array of 32-bit unsigned integers decoded from raw string +s+
155
+ # This first 16-bits of the MPI is the MPI length in bits
156
+ #
157
+ # Javascript reference implementation: function mpi2b(s)
158
+ #
159
+ def mpi_to_a32(s)
160
+ bs=28
161
+ bx2=1<<bs
162
+ bm=bx2-1
163
+
164
+ bn=1
165
+ r=[0]
166
+ rn=0
167
+ sb=256
168
+ c = nil
169
+ sn=s.length
170
+ return 0 if(sn < 2)
171
+
172
+ len=(sn-2)*8
173
+ bits=s[0].ord*256+s[1].ord
174
+
175
+ return 0 if(bits > len || bits < len-8)
176
+
177
+ len.times do |n|
178
+ if ((sb<<=1) > 255)
179
+ sb=1
180
+ sn -= 1
181
+ c=s[sn].ord
182
+ end
183
+ if(bn > bm)
184
+ bn=1
185
+ rn += 1
186
+ r << 0
187
+ end
188
+ if(c & sb != 0)
189
+ r[rn]|=bn
190
+ end
191
+ bn<<=1
192
+ end
193
+ r
194
+ end
195
+
196
+ # Alternative mpi2b implementation; doesn't quite match the javascript implementation yet however
197
+ # def native_mpi_to_a32(s)
198
+ # len = s.length - 2
199
+ # short = len % 4
200
+ # base = len - short
201
+ # r = s[2,base].unpack('N*')
202
+ # case short
203
+ # when 1
204
+ # r.concat s[2+base,short].unpack('C*')
205
+ # when 2
206
+ # r.concat s[2+base,short].unpack('n*')
207
+ # when 3
208
+ # r.concat ("\0" + s[2+base,short]).unpack('N*')
209
+ # end
210
+ # r
211
+ # end
212
+
213
+ # Returns multiple precision integer (MPI) as an array of 32-bit signed integers decoded from base64 string +s+
214
+ #
215
+ def base64_mpi_to_a32(s)
216
+ mpi_to_a32(base64urldecode(s))
217
+ end
218
+
219
+ # Returns multiple precision integer (MPI) as a big integers decoded from base64 string +s+
220
+ #
221
+ def base64_mpi_to_bn(s)
222
+ data = base64urldecode(s)
223
+ len = ((data[0].ord * 256 + data[1].ord + 7) / 8) + 2
224
+ data[2,len+2].unpack('H*').first.to_i(16)
225
+ end
226
+
227
+
228
+ # Returns the 4-part RSA key as 32-bit signed integers [d, p, q, u] given +key+ (String)
229
+ #
230
+ # result[0] = p: The first factor of n, the RSA modulus
231
+ # result[1] = q: The second factor of n
232
+ # result[2] = d: The private exponent.
233
+ # result[3] = u: The CRT coefficient, equals to (1/p) mod q.
234
+ #
235
+ # Javascript reference implementation: function api_getsid2(res,ctx)
236
+ #
237
+ def decompose_rsa_private_key_a32(key)
238
+ privk = key.dup
239
+ decomposed_key = []
240
+ # puts "decomp: privk.len:#{privk.length}"
241
+ 4.times do
242
+ len = ((privk[0].ord * 256 + privk[1].ord + 7) / 8) + 2
243
+ privk_part = privk[0,len]
244
+ # puts "\nprivk_part #{base64urlencode(privk_part)}"
245
+ privk_part_a32 = mpi_to_a32(privk_part)
246
+ decomposed_key << privk_part_a32
247
+ # puts "decomp: len:#{len} privk_part_a32:#{privk_part_a32.length} first:#{privk_part_a32.first} last:#{privk_part_a32.last}"
248
+ privk.slice!(0,len)
249
+ end
250
+ decomposed_key
251
+ end
252
+
253
+ # Returns the 4-part RSA key as array of big integers [d, p, q, u] given +key+ (String)
254
+ #
255
+ # result[0] = p: The first factor of n, the RSA modulus
256
+ # result[1] = q: The second factor of n
257
+ # result[2] = d: The private exponent.
258
+ # result[3] = u: The CRT coefficient, equals to (1/p) mod q.
259
+ #
260
+ # Javascript reference implementation: function api_getsid2(res,ctx)
261
+ #
262
+ def decompose_rsa_private_key(key)
263
+ privk = key.dup
264
+ decomposed_key = []
265
+ offset = 0
266
+ 4.times do |i|
267
+ len = ((privk[0].ord * 256 + privk[1].ord + 7) / 8) + 2
268
+ privk_part = privk[0,len]
269
+ # puts "\nl: ", len
270
+ # puts "decrypted rsa part hex: \n", privk_part.unpack('H*').first
271
+ decomposed_key << privk_part[2,privk_part.length].unpack('H*').first.to_i(16)
272
+ privk.slice!(0,len)
273
+ end
274
+ decomposed_key
275
+ end
276
+
277
+ # Returns the decrypted session id given base64 MPI +csid+ and RSA +rsa_private_key+ as array of big integers [d, p, q, u]
278
+ #
279
+ # Javascript reference implementation: function api_getsid2(res,ctx)
280
+ #
281
+ def decrypt_session_id(csid,rsa_private_key)
282
+ csid_bn = base64_mpi_to_bn(csid)
283
+ sid_bn = rsa_decrypt(csid_bn,rsa_private_key)
284
+ sid_hs = sid_bn.to_s(16)
285
+ sid_hs = '0' + sid_hs if sid_hs.length % 2 > 0
286
+ sid = hexstr_to_bstr(sid_hs)[0,43]
287
+ base64urlencode(sid)
288
+ end
289
+
290
+ # Returns the private key decryption of +m+ given +pqdu+ (array of integer cipher components).
291
+ # Computes m**d (mod n).
292
+ #
293
+ # This implementation uses a Pure Ruby implementation of RSA private_decrypt
294
+ #
295
+ # p: The first factor of n, the RSA modulus
296
+ # q: The second factor of n
297
+ # d: The private exponent.
298
+ # u: The CRT coefficient, equals to (1/p) mod q.
299
+ #
300
+ # n = pq
301
+ # n is used as the modulus for both the public and private keys. Its length, usually expressed in bits, is the key length.
302
+ #
303
+ # φ(n) = (p – 1)(q – 1), where φ is Euler's totient function.
304
+ #
305
+ # Choose an integer e such that 1 < e < φ(n) and gcd(e, φ(n)) = 1; i.e., e and φ(n) are coprime.
306
+ # e is released as the public key exponent
307
+ #
308
+ # Determine d as d ≡ e−1 (mod φ(n)), i.e., d is the multiplicative inverse of e (modulo φ(n)).
309
+ # d is kept as the private key exponent.
310
+ #
311
+ # More info: http://en.wikipedia.org/wiki/RSA_(algorithm)#Operation
312
+ #
313
+ # Javascript reference implementation: function RSAdecrypt(m, d, p, q, u)
314
+ #
315
+ def rsa_decrypt(m, pqdu)
316
+ p, q, d, u = pqdu
317
+ if p && q && u
318
+ m1 = Math.powm(m, d % (p-1), p)
319
+ m2 = Math.powm(m, d % (q-1), q)
320
+ h = m2 - m1
321
+ h = h + q if h < 0
322
+ h = h*u % q
323
+ h*p+m1
324
+ else
325
+ Math.powm(m, d, p*q)
326
+ end
327
+ end
328
+
329
+
330
+ # Returns the private key decryption of +m+ given +pqdu+ (array of integer cipher components)
331
+ # This implementation uses OpenSSL RSA public key feature.
332
+ #
333
+ # NB: can't get this to work exactly right with Mega yet
334
+ def openssl_rsa_decrypt(m, pqdu)
335
+ rsa = openssl_rsa_cipher(pqdu)
336
+
337
+ chunk_size = 256 # hmm. need to figure out how to calc for "data greater than mod len"
338
+ # number.size(self.n) - 1 : Return the maximum number of bits that can be handled by this key.
339
+ decrypt_texts = []
340
+ (0..m.length - 1).step(chunk_size) do |i|
341
+ pt_part = m[i,chunk_size]
342
+ decrypt_texts << rsa.private_decrypt(pt_part,3)
343
+ end
344
+ decrypt_texts.join
345
+ end
346
+
347
+ # Returns an OpenSSL RSA cipher object initialised with +pqdu+ (array of integer cipher components)
348
+ # p: The first factor of n, the RSA modulus
349
+ # q: The second factor of n
350
+ # d: The private exponent.
351
+ # u: The CRT coefficient, equals to (1/p) mod q.
352
+ #
353
+ # NB: this hacks the RSA object creation n a way that should work, but can't get this to work exactly right with Mega yet
354
+ def openssl_rsa_cipher(pqdu)
355
+ rsa = OpenSSL::PKey::RSA.new
356
+ p, q, d, u = pqdu
357
+ rsa.p, rsa.q, rsa.d = p, q, d
358
+ rsa.n = rsa.p * rsa.q
359
+ # # dmp1 = d mod (p-1)
360
+ # rsa.dmp1 = rsa.d % (rsa.p - 1)
361
+ # # dmq1 = d mod (q-1)
362
+ # rsa.dmq1 = rsa.d % (rsa.q - 1)
363
+ # # iqmp = q^-1 mod p?
364
+ # rsa.iqmp = (rsa.q ** -1) % rsa.p
365
+ # # ipmq = (rsa.p ** -1) % rsa.q
366
+ # ipmq = rsa.p ** -1 % rsa.q
367
+ rsa.e = 0 # 65537
368
+ rsa
369
+ end
370
+
371
+ # Returns a binary string given a string +h+ of hex digits
372
+ def hexstr_to_bstr(h)
373
+ bstr = ''
374
+ (0..h.length-1).step(2) {|n| bstr << h[n,2].to_i(16).chr }
375
+ bstr
376
+ end
377
+
378
+ def decrypt_file_key(f)
379
+ key = f['k'].split(':')[1]
380
+ decrypt_key(base64_to_a32(key), self.master_key)
381
+ end
382
+
383
+ def decrypt_file_attributes(f,key)
384
+ k = f['t'] == 0 ? decompose_file_key(key) : key
385
+ rstr = aes_cbc_decrypt(base64urldecode(f['a']), a32_to_str(k))
386
+ JSON.parse( rstr.gsub("\x0",'').gsub(/^.*{/,'{'))
387
+ end
388
+
389
+ def decompose_file_key(key)
390
+ [
391
+ key[0] ^ key[4],
392
+ key[1] ^ key[5],
393
+ key[2] ^ key[6],
394
+ key[3] ^ key[7]
395
+ ]
396
+ end
397
+
398
+
399
+ end