Class: Rmega::Session

Inherits:
Object
  • Object
show all
Extended by:
Crypto
Includes:
Crypto, Loggable, Net, NotInspectable, Options
Defined in:
lib/rmega/session.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Crypto::Rsa

#powm, #rsa_decrypt

Methods included from Crypto::AesCtr

#aes_ctr_cipher, #aes_ctr_decrypt, #aes_ctr_encrypt

Methods included from Crypto::AesEcb

#aes_ecb_cipher, #aes_ecb_decrypt, #aes_ecb_encrypt

Methods included from Crypto::AesCbc

#aes_cbc_cipher, #aes_cbc_decrypt, #aes_cbc_encrypt, #aes_cbc_mac

Methods included from Options

included, #options

Methods included from Net

#http_get_content, #http_post, #survive

Methods included from Loggable

included, #logger

Methods included from NotInspectable

#inspect

Constructor Details

#initializeSession

Returns a new instance of Session.



13
14
15
16
# File 'lib/rmega/session.rb', line 13

def initialize
  @request_id = random_request_id
  @shared_keys = {}
end

Instance Attribute Details

#master_keyObject

Returns the value of attribute master_key.



11
12
13
# File 'lib/rmega/session.rb', line 11

def master_key
  @master_key
end

#request_idObject (readonly)

Returns the value of attribute request_id.



10
11
12
# File 'lib/rmega/session.rb', line 10

def request_id
  @request_id
end

#rsa_privkObject (readonly)

Returns the value of attribute rsa_privk.



10
11
12
# File 'lib/rmega/session.rb', line 10

def rsa_privk
  @rsa_privk
end

#shared_keysObject (readonly)

Returns the value of attribute shared_keys.



10
11
12
# File 'lib/rmega/session.rb', line 10

def shared_keys
  @shared_keys
end

#sidObject (readonly)

Returns the value of attribute sid.



10
11
12
# File 'lib/rmega/session.rb', line 10

def sid
  @sid
end

Class Method Details

.ephemeralObject



117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/rmega/session.rb', line 117

def self.ephemeral
  master_key = OpenSSL::Random.random_bytes(16)
  password = OpenSSL::Random.random_bytes(16)
  password_hash = hash_password(password)
  challenge = OpenSSL::Random.random_bytes(16)

  session = new

  user_handle = session.request(a: 'up', k: Utils.base64urlencode(aes_ecb_encrypt(password_hash, master_key)),
    ts: Utils.base64urlencode(challenge + aes_ecb_encrypt(master_key, challenge)))

  return session.(user_handle, password)
end

.hash_password(password) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/rmega/session.rb', line 42

def self.hash_password(password)
  pwd = password.dup.force_encoding('BINARY')
  pkey = "\x93\xc4\x67\xe3\x7d\xb0\xc7\xa4\xd1\xbe\x3f\x81\x1\x52\xcb\x56".force_encoding('BINARY')
  null_byte = "\x0".force_encoding('BINARY').freeze
  blank = (null_byte*16).force_encoding('BINARY').freeze
  keys = {}

  65536.times do
    (0..pwd.size-1).step(16) do |j|

      keys[j] ||= begin
        key = blank.dup
        16.times { |i| key[i] = pwd[i+j] || null_byte if i+j < pwd.size }
        key
      end

      pkey = aes_ecb_encrypt(keys[j], pkey)
    end
  end

  return pkey
end

Instance Method Details

#decrypt_rsa_private_key(encrypted_privk) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/rmega/session.rb', line 22

def decrypt_rsa_private_key(encrypted_privk)
  privk = aes_ecb_decrypt(@master_key, Utils.base64urldecode(encrypted_privk))

  # Decompose private key
  decomposed_key = []

  4.times do
    len = ((privk[0].ord * 256 + privk[1].ord + 7) >> 3) + 2
    privk_part = privk[0, len]
    decomposed_key << Utils.string_to_bignum(privk[0..len-1][2..-1])
    privk = privk[len..-1]
  end

  return decomposed_key
end

#decrypt_session_id(csid) ⇒ Object



65
66
67
68
69
70
71
72
73
# File 'lib/rmega/session.rb', line 65

def decrypt_session_id(csid)
  csid = Utils.base64_mpi_to_bn(csid)
  csid = rsa_decrypt(csid, @rsa_privk)
  csid = csid.to_s(16)
  csid = '0' + csid if csid.length % 2 > 0
  csid = Utils.hexstr_to_bstr(csid)[0,43]
  csid = Utils.base64urlencode(csid)
  return csid
end

#ephemeral_login(user_handle, password) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/rmega/session.rb', line 104

def (user_handle, password)
  resp = request(a: 'us', user: user_handle)

  password_hash = hash_password(password)

  @master_key = aes_cbc_decrypt(password_hash, Utils.base64urldecode(resp['k']))
  @sid = resp['tsid']
  @rsa_privk = nil
  @shared_keys = {}

  return self
end

#hash_password(password) ⇒ Object



38
39
40
# File 'lib/rmega/session.rb', line 38

def hash_password(password)
  self.class.hash_password(password)
end

#login(email, password) ⇒ Object

If the user_hash is found on the server it returns:

  • The user master_key (128 bit for AES) encrypted with the password_hash

  • The RSA private key ecrypted with the master_key

  • A brand new session_id encrypted with the RSA private key



89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/rmega/session.rb', line 89

def (email, password)
  # Derive an hash from the user password
  password_hash = hash_password(password)
  u_hash = user_hash(password_hash, email.strip.downcase)

  resp = request(a: 'us', user: email.strip, uh: u_hash)

  @master_key = aes_cbc_decrypt(password_hash, Utils.base64urldecode(resp['k']))
  @rsa_privk = decrypt_rsa_private_key(resp['privk'])
  @sid = decrypt_session_id(resp['csid'])
  @shared_keys = {}

  return self
end

#random_request_idObject



131
132
133
# File 'lib/rmega/session.rb', line 131

def random_request_id
  rand(1E7..1E9).to_i
end

#request(body, query_params = {}) ⇒ Object



143
144
145
146
147
148
149
150
151
152
153
# File 'lib/rmega/session.rb', line 143

def request(body, query_params = {})
  survive do
    @request_id += 1
    api_response = APIResponse.new(http_post(request_url(query_params), [body].to_json))
    if api_response.ok?
      return(api_response.as_json)
    else
      raise(api_response.as_error)
    end
  end
end

#request_url(params = {}) ⇒ Object



135
136
137
138
139
140
141
# File 'lib/rmega/session.rb', line 135

def request_url(params = {})
  params = params.merge(sid: @sid) if @sid
  params = params.to_a.map { |a| a.join("=") }.join("&")
  params = "&#{params}" unless params.empty?

  return "#{options.api_url}?id=#{@request_id}#{params}"
end

#storageObject



18
19
20
# File 'lib/rmega/session.rb', line 18

def storage
  @storage ||= Storage.new(self)
end

#user_hash(aes_key, email) ⇒ Object



75
76
77
78
79
80
81
82
83
# File 'lib/rmega/session.rb', line 75

def user_hash(aes_key, email)
  s_bytes = email.bytes.to_a
  hash = Array.new(16, 0)
  s_bytes.size.times { |n| hash[n & 15] = hash[n & 15] ^ s_bytes[n] }
  hash = hash.pack('c*')
  16384.times { hash = aes_ecb_encrypt(aes_key, hash) }
  hash = hash[0..4-1] + hash[8..12-1]
  return Utils.base64urlencode(hash)
end