megar 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,6 +2,9 @@
2
2
  class Megar::File
3
3
  include Megar::CatalogItem
4
4
 
5
+ # Decomposed form the +key+
6
+ attr_accessor :decomposed_key
7
+
5
8
  # The file size
6
9
  attr_accessor :size
7
10
  alias_method :s=, :size=
@@ -11,4 +14,14 @@ class Megar::File
11
14
  format("%16d bytes %-10s %-60s", size.to_i, id, name)
12
15
  end
13
16
 
17
+ # Returns the body content of the file
18
+ def body
19
+ downloader.content
20
+ end
21
+
22
+ # Returns the a one-shot downloader
23
+ def downloader
24
+ Megar::FileDownloader.new(file: self)
25
+ end
26
+
14
27
  end
@@ -2,12 +2,4 @@
2
2
  class Megar::Files
3
3
  include Megar::CatalogItem
4
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
5
  end
@@ -17,4 +17,16 @@ class Megar::Folder
17
17
  self.name = nil unless name
18
18
  end
19
19
 
20
+ # Returns a collection of folders contained within this folder
21
+ def folders
22
+ return unless session
23
+ session.folders.find_all_by_parent_folder_id(id)
24
+ end
25
+
26
+ # Returns a collection of files contained within this folder
27
+ def files
28
+ return unless session
29
+ session.files.find_all_by_parent_folder_id(id)
30
+ end
31
+
20
32
  end
@@ -2,14 +2,6 @@
2
2
  class Megar::Folders
3
3
  include Megar::CatalogItem
4
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
5
  # Returns the root (cloud drive) folder
14
6
  def root
15
7
  @root ||= find_by_type(2)
@@ -1,399 +1 @@
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
1
+ require 'megar/crypto/support'