package ru.denull.mtproto; import java.math.BigInteger; import java.nio.*; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchProviderException; import java.util.*; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; import tl.*; import tl.auth.ExportAuthorization; import static ru.denull.mtproto.CryptoUtils.*; public class Auth { private static final String TAG = "Auth"; public Server server; public byte[] auth_key; public long auth_key_id; public long server_salt; public boolean authorized; public Preferences pref; public KeyStore keyStore; public AuthState state = AuthState.NONE; private long retry_id; private ArrayList<AuthCallback> callbacks = new ArrayList<AuthCallback>(); private boolean importAuthorization; private byte[] nonce, server_nonce, new_nonce; private byte[] tmp_aes_key, tmp_aes_iv; private long g; private BigInteger g_a, b, dh_prime; private Random rand = new Random(); public enum AuthState { NONE, REQUESTED_PQ, REQUESTED_DH, SENT_DH, FAILED, COMPLETE }; public interface AuthCallback { public void done(Server server, byte[] auth_key); public void error(); } public Auth(Server server) { this.server = server; } public Auth(Server server, Preferences pref) { this.server = server; this.pref = pref; /*try { this.keyStore = KeyStore.getInstance("KeychainStore", "Apple"); } catch (Exception e) { try { this.keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); } catch (Exception e1) { e1.printStackTrace(); } e.printStackTrace(); }*/ System.out.println("auth key = " + (pref.get("auth_key", "").length() > 0 ? "true" : "false") + ", authorisation = " + pref.getBoolean("authorized", false)); this.auth_key = Base64.decode(pref.get("auth_key", "")); this.auth_key_id = pref.getLong("auth_key_id", 0); this.server_salt = pref.getLong("server_salt", 0); this.authorized = pref.getBoolean("authorized", false); this.state = (this.auth_key_id != 0) ? AuthState.COMPLETE : AuthState.NONE; } public Auth(Server server, byte[] auth_key, long auth_key_id, long server_salt) { this.server = server; this.auth_key = auth_key; this.auth_key_id = auth_key_id; this.server_salt = server_salt; this.state = (this.auth_key_id != 0) ? AuthState.COMPLETE : AuthState.NONE; } public void logged() { authorized = true; if (pref != null) { pref.putBoolean("authorized", true); } } public void loggedOut() { authorized = false; if (pref != null) { pref.putBoolean("authorized", false); } } private void reset(boolean full) { if (full) { auth_key = null; auth_key_id = 0; server_salt = 0; } retry_id = 0; nonce = null; server_nonce = null; new_nonce = null; tmp_aes_key = null; tmp_aes_iv = null; g = 0; g_a = null; b = null; dh_prime = null; } private void fail() { reset(true); state = AuthState.FAILED; ArrayList<AuthCallback> cb = callbacks; callbacks = new ArrayList<Auth.AuthCallback>(); for (AuthCallback callback : cb) { callback.error(); } } private void complete() { reset(false); state = AuthState.COMPLETE; if (pref != null) { pref.put("auth_key", Base64.encodeToString(auth_key, Base64.NO_WRAP)); pref.putLong("auth_key_id", auth_key_id); pref.putLong("server_salt", server_salt); pref.putBoolean("authorized", false); } if (server.old_session_id != 0) { server.call(new DestroySession(server.old_session_id), new Server.RPCCallback<TLObject>() { public void done(TLObject result) { Log.i(TAG, "Successfully destroyed old session"); } public void error(int code, String message) { Log.i(TAG, "Unable to destroy old session"); } }, false); server.old_session_id = 0; } try { server.sendQueued(); } catch (Exception e) { e.printStackTrace(); } ArrayList<AuthCallback> cb = callbacks; callbacks = new ArrayList<Auth.AuthCallback>(); for (AuthCallback callback : cb) { callback.done(server, auth_key); } } public boolean generateKey(boolean forceRegenerate, final AuthCallback callback) { return generateKey(forceRegenerate, callback, false); } public boolean generateKey(boolean forceRegenerate, final AuthCallback callback, boolean importAutorization) { if (callback != null) { callbacks.add(callback); } if (state != AuthState.NONE && state != AuthState.FAILED && state != AuthState.COMPLETE) { return false; // already in progress } this.importAuthorization = importAutorization; if (state == AuthState.COMPLETE && !forceRegenerate) { complete(); return true; } nonce = new byte[16]; rand.nextBytes(nonce); retry_id = 0; state = AuthState.REQUESTED_PQ; try { server.send(new ReqPq(nonce), false); } catch (Exception e) { Log.e(TAG, "Failed to request PQ"); e.printStackTrace(); fail(); return false; } return true; } public void generateKey(TLObject reply) throws Exception { switch (state) { case REQUESTED_PQ: { if (!(reply instanceof ResPQ)) { // unexpected response fail(); return; } ResPQ response = (ResPQ) reply; if (!Arrays.equals(nonce, response.nonce)) { fail(); return; } server_nonce = response.server_nonce; BigInteger p = factor(response.pq); BigInteger q = response.pq.divide(p); if (p.compareTo(q) > 0) { // swap BigInteger tmp = p; p = q; q = tmp; } new_nonce = new byte[32]; rand.nextBytes(new_nonce); PQInnerData innerData = new PQInnerData(response.pq, p, q, nonce, server_nonce, new_nonce); ByteBuffer buffer = ByteBuffer.allocateDirect(innerData.length(true)); buffer.order(ByteOrder.LITTLE_ENDIAN); innerData.writeTo(buffer); buffer.rewind(); byte[] hash = SHA1(buffer, 0, buffer.capacity()); byte[] data_with_hash = new byte[255]; System.arraycopy(hash, 0, data_with_hash, 0, hash.length); buffer.get(data_with_hash, hash.length, buffer.capacity()); byte[] encrypted_data = RSAEncrypt(data_with_hash, new BigInteger("0C150023E2F70DB7985DED064759CFECF0AF328E69A41DAF4D6F01B538135A6F91F8F8B2A0EC9BA9720CE352EFCF6C5680FFC424BD634864902DE0B4BD6D49F4E580230E3AE97D95C8B19442B3C0A10D8F5633FECEDD6926A7F6DAB0DDB7D457F9EA81B8465FCD6FFFEED114011DF91C059CAEDAF97625F6C96ECC74725556934EF781D866B34F011FCE4D835A090196E9A5F0E4449AF7EB697DDB9076494CA5F81104A305B6DD27665722C46B60E5DF680FB16B210607EF217652E60236C255F6A28315F4083A96791D7214BF64C1DF4FD0DB1944FB26A2A57031B32EEE64AD15A8BA68885CDE74A5BFC920F6ABF59BA5C75506373E7130F9042DA922179251F", 16), new BigInteger("010001", 16)); server.send(new ReqDHParams(nonce, server_nonce, p, q, response.server_public_key_fingerprints[0], encrypted_data), false); state = AuthState.REQUESTED_DH; break; } case REQUESTED_DH: { if (!(reply instanceof ServerDHParamsOk)) { // => ResponseServerDHFail fail(); return; } ServerDHParamsOk response = (ServerDHParamsOk) reply; if (!Arrays.equals(nonce, response.nonce)) { fail(); return; } if (!Arrays.equals(server_nonce, response.server_nonce)) { fail(); return; } byte[] sha_ns = SHA1(concat(new_nonce, server_nonce)); byte[] sha_sn = SHA1(concat(server_nonce, new_nonce)); byte[] sha_nn = SHA1(concat(new_nonce, new_nonce)); tmp_aes_key = concat(sha_ns, substr(sha_sn, 0, 12)); tmp_aes_iv = concat(substr(sha_sn, 12, 8), sha_nn, substr(new_nonce, 0, 4)); byte[] answer = AESDecrypt(response.encrypted_answer, 0, response.encrypted_answer.length, tmp_aes_key, tmp_aes_iv); //byte[] sha1_answer = Arrays.copyOfRange(answer, 0, 20); ByteBuffer answer_buf = ByteBuffer.wrap(Arrays.copyOfRange(answer, 20, answer.length)); answer_buf.order(ByteOrder.LITTLE_ENDIAN); ServerDHInnerData serverInnerData = (ServerDHInnerData) TL.read(answer_buf); if (!Arrays.equals(nonce, serverInnerData.nonce)) { fail(); return; } if (!Arrays.equals(server_nonce, serverInnerData.server_nonce)) { fail(); return; } g = serverInnerData.g; g_a = serverInnerData.g_a; dh_prime = serverInnerData.dh_prime; server.time_diff = (int) (serverInnerData.server_time - (System.currentTimeMillis() / 1000)); requestClientDH(); state = AuthState.SENT_DH; break; } case SENT_DH: { if (!(reply instanceof TSetClientDHParamsAnswer)) { fail(); return; } TSetClientDHParamsAnswer response = (TSetClientDHParamsAnswer) reply; auth_key = new byte[256]; byte[] buffer = g_a.modPow(b, dh_prime).toByteArray(); System.arraycopy(buffer, Math.max(buffer.length - 256, 0), auth_key, 256 - Math.min(buffer.length, 256), Math.min(buffer.length, 256)); ByteBuffer tmp = ByteBuffer.allocateDirect(8); tmp.order(ByteOrder.LITTLE_ENDIAN); for (int i = 0; i < 8; i++) { tmp.put((byte) (new_nonce[i] ^ server_nonce[i])); } server_salt = tmp.getLong(0); tmp = ByteBuffer.wrap(SHA1(auth_key)); tmp.order(ByteOrder.LITTLE_ENDIAN); auth_key_id = tmp.getLong(12); long auth_key_aux_hash = tmp.getLong(0); tmp = ByteBuffer.allocateDirect(new_nonce.length + 9); tmp.order(ByteOrder.LITTLE_ENDIAN); tmp.put(new_nonce); if (reply instanceof DhGenOk) { tmp.put((byte) 0x01); } else if (reply instanceof DhGenFail) { tmp.put((byte) 0x02); } else { tmp.put((byte) 0x03); } tmp.putLong(auth_key_aux_hash); byte[] new_nonce_hash1 = substr(SHA1(tmp, 0, tmp.capacity()), 4, 16); if (reply instanceof DhGenRetry) { // retry retry_id = auth_key_aux_hash; requestClientDH(); state = AuthState.SENT_DH; return; } if (reply instanceof DhGenFail) { fail(); return; } if (!Arrays.equals(new_nonce_hash1, ((DhGenOk) response).new_nonce_hash1)) { fail(); return; } debug("auth_key: " + (new BigInteger(buffer)), "0x" + (new BigInteger(buffer)).toString(16)); debug("server_salt: 0x" + Long.toHexString(server_salt)); debug("auth_key_id: 0x" + Long.toHexString(auth_key_id)); complete(); break; } } } private void requestClientDH() throws Exception { byte[] bytes = new byte[255]; rand.nextBytes(bytes); b = new BigInteger(bytes); BigInteger g_b = BigInteger.valueOf(g).modPow(b, dh_prime); ClientDHInnerData clientInnerData = new ClientDHInnerData(nonce, server_nonce, retry_id, g_b); ByteBuffer buffer = ByteBuffer.allocateDirect(clientInnerData.length(true)); buffer.order(ByteOrder.LITTLE_ENDIAN); clientInnerData.writeTo(buffer); buffer.rewind(); byte[] hash = SHA1(buffer, 0, buffer.capacity()); int len = hash.length + buffer.capacity(); while (len % 16 != 0) len++; byte[] data_with_hash = new byte[len]; System.arraycopy(hash, 0, data_with_hash, 0, hash.length); buffer.get(data_with_hash, hash.length, buffer.capacity()); byte[] encrypted_data = AESEncrypt(data_with_hash, 0, len, tmp_aes_key, tmp_aes_iv); server.send(new SetClientDHParams(nonce, server_nonce, encrypted_data), false); } }