Class: Rmega::Session
- Inherits:
-
Object
- Object
- Rmega::Session
- Extended by:
- Crypto
- Includes:
- Crypto, Loggable, Net, NotInspectable, Options
- Defined in:
- lib/rmega/session.rb
Instance Attribute Summary collapse
-
#master_key ⇒ Object
Returns the value of attribute master_key.
-
#request_id ⇒ Object
readonly
Returns the value of attribute request_id.
-
#rsa_privk ⇒ Object
readonly
Returns the value of attribute rsa_privk.
-
#shared_keys ⇒ Object
readonly
Returns the value of attribute shared_keys.
-
#sid ⇒ Object
readonly
Returns the value of attribute sid.
Class Method Summary collapse
Instance Method Summary collapse
- #decrypt_rsa_private_key(encrypted_privk) ⇒ Object
- #decrypt_session_id(csid) ⇒ Object
- #ephemeral_login(user_handle, password) ⇒ Object
- #hash_password(password) ⇒ Object
-
#initialize ⇒ Session
constructor
A new instance of Session.
-
#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.
- #random_request_id ⇒ Object
- #request(body, query_params = {}) ⇒ Object
- #request_url(params = {}) ⇒ Object
- #storage ⇒ Object
- #user_hash(aes_key, email) ⇒ Object
Methods included from Crypto::Rsa
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
Methods included from Net
#http_get_content, #http_post, #survive
Methods included from Loggable
Methods included from NotInspectable
Constructor Details
#initialize ⇒ Session
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_key ⇒ Object
Returns the value of attribute master_key.
11 12 13 |
# File 'lib/rmega/session.rb', line 11 def master_key @master_key end |
#request_id ⇒ Object (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_privk ⇒ Object (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_keys ⇒ Object (readonly)
Returns the value of attribute shared_keys.
10 11 12 |
# File 'lib/rmega/session.rb', line 10 def shared_keys @shared_keys end |
#sid ⇒ Object (readonly)
Returns the value of attribute sid.
10 11 12 |
# File 'lib/rmega/session.rb', line 10 def sid @sid end |
Class Method Details
.ephemeral ⇒ Object
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.ephemeral_login(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 ephemeral_login(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 login(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_id ⇒ Object
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 |
#storage ⇒ Object
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 |