예제 #1
0
        private static TelegramSession loadIfExists()
        {
            TelegramSession session;

            lock (typeof(TelegramSession)) {
                try {
                    using (IsolatedStorageFile fileStorage = IsolatedStorageFile.GetUserStoreForApplication())
                        using (Stream fileStream = new IsolatedStorageFileStream("session.dat", FileMode.Open, fileStorage))
                            using (BinaryReader fileReader = new BinaryReader(fileStream)) {
                                session = new TelegramSession(fileReader);
                                logger.info("loaded telegram session: {0}", session);
                            }
                } catch (Exception e) {
                    logger.info("error loading session, create new...: {0}", e);

                    logger.info("cleaning contacts");
                    CleanContacts();

                    ulong sessionId = Helpers.GenerateRandomUlong();
                    session = new TelegramSession(sessionId, 0);
                    // prod 173.240.5.1
                    // test 173.240.5.253
                    TelegramEndpoint endpoint = new TelegramEndpoint("173.240.5.1", 443);
                    TelegramDC       dc       = new TelegramDC();
                    dc.Endpoints.Add(endpoint);
                    session.Dcs.Add(1, dc);
                    session.SetMainDcId(1);


                    logger.info("created new telegram session: {0}", session);
                }
            }

            return(session);
        }
예제 #2
0
 public MTProtoGateway(TelegramDC dc, ISession session, bool highlevel, ulong salt)
 {
     this.dc        = dc;
     this.session   = session;
     this.highlevel = highlevel;
     this.salt      = salt;
 }
예제 #3
0
        public async Task <TLApi> GetFileSession(int dc)
        {
            logger.debug("Getting file session for dc {0}", dc);
            await             Established;
            ConfigConstructor config = (ConfigConstructor)gateway.Config;

            TelegramDC targetDc;

            if (dcs.ContainsKey(dc))
            {
                targetDc = dcs[dc];
            }
            else
            {
                targetDc = new TelegramDC();
                foreach (var dcOption in config.dc_options)
                {
                    DcOptionConstructor optionConstructor = (DcOptionConstructor)dcOption;
                    if (optionConstructor.id == dc)
                    {
                        TelegramEndpoint endpoint = new TelegramEndpoint(optionConstructor.ip_address, optionConstructor.port);
                        targetDc.Endpoints.Add(endpoint);
                    }
                }

                dcs[dc] = targetDc;
            }

            MTProtoGateway fileGateway = await targetDc.GetFileGateway(gateway.Salt);

            TLApi fileGatewayApi = new TLApi(fileGateway);

            if (targetDc.FileAuthorized || dc == mainDcId)
            {
                return(fileGatewayApi);
            }

            Task <auth_ExportedAuthorization>     exportAuthTask = Api.auth_exportAuthorization(dc);
            Auth_exportedAuthorizationConstructor exportedAuth   = (Auth_exportedAuthorizationConstructor)await exportAuthTask;
            auth_Authorization authorization = await fileGatewayApi.auth_importAuthorization(exportedAuth.id, exportedAuth.bytes);

            targetDc.SaveFileAuthorization(authorization);

            return(fileGatewayApi);
        }
예제 #4
0
        public async Task Migrate(int dc)
        {
            if (gateway == null)
            {
                logger.error("gateway not found, migration impossible");
                return;
            }

            if (gateway.Config == null)
            {
                logger.error("config in gateway not found, migration impossible");
                return;
            }

            ConfigConstructor config = (ConfigConstructor)gateway.Config;

            if (config.this_dc == dc)
            {
                logger.warning("migration to same dc: {0}", dc);
                return;
            }

            TelegramDC newDc = new TelegramDC();

            foreach (var dcOption in config.dc_options)
            {
                DcOptionConstructor optionConstructor = (DcOptionConstructor)dcOption;
                if (optionConstructor.id == dc)
                {
                    TelegramEndpoint endpoint = new TelegramEndpoint(optionConstructor.ip_address, optionConstructor.port);
                    newDc.Endpoints.Add(endpoint);
                }
            }

            dcs[dc]  = newDc;
            mainDcId = dc;

            gateway.Dispose();
            gateway         = null;
            establishedTask = new TaskCompletionSource <object>();
            ConnectAsync();
            await Established;
        }
예제 #5
0
 public async Task ConnectAsync(TelegramDC dc, int maxRetries) {
     await base.ConnectAsync(dc, maxRetries);
 }
예제 #6
0
        public async Task<TLApi> GetFileSession(int dc) {
            logger.debug("Getting file session for dc {0}", dc);
            await Established;
            ConfigConstructor config = (ConfigConstructor) gateway.Config;

            TelegramDC targetDc;
            if(dcs.ContainsKey(dc)) {
                targetDc = dcs[dc];
            } else {
                targetDc = new TelegramDC();
                foreach(var dcOption in config.dc_options) {
                    DcOptionConstructor optionConstructor = (DcOptionConstructor) dcOption;
                    if(optionConstructor.id == dc) {
                        TelegramEndpoint endpoint = new TelegramEndpoint(optionConstructor.ip_address, optionConstructor.port);
                        targetDc.Endpoints.Add(endpoint);
                    }
                }

                dcs[dc] = targetDc;
            }
            
            MTProtoGateway fileGateway = await targetDc.GetFileGateway(gateway.Salt);
            
            TLApi fileGatewayApi = new TLApi(fileGateway);

            if(targetDc.FileAuthorized || dc == mainDcId) {
                return fileGatewayApi;
            } 

            Task<auth_ExportedAuthorization> exportAuthTask = Api.auth_exportAuthorization(dc);
            Auth_exportedAuthorizationConstructor exportedAuth = (Auth_exportedAuthorizationConstructor) await exportAuthTask;
            auth_Authorization authorization = await fileGatewayApi.auth_importAuthorization(exportedAuth.id, exportedAuth.bytes);
            targetDc.SaveFileAuthorization(authorization);

            return fileGatewayApi;
        }
예제 #7
0
        public async Task Migrate(int dc) {
            if(gateway == null) {
                logger.error("gateway not found, migration impossible");
                return;
            }

            if (gateway.Config == null) {
                logger.error("config in gateway not found, migration impossible");
                return;
            }

            ConfigConstructor config = (ConfigConstructor) gateway.Config;
            if(config.this_dc == dc) {
                logger.warning("migration to same dc: {0}", dc);
                return;
            }

            TelegramDC newDc = new TelegramDC();
            foreach(var dcOption in config.dc_options) {
                DcOptionConstructor optionConstructor = (DcOptionConstructor) dcOption;
                if(optionConstructor.id == dc) {
                    TelegramEndpoint endpoint = new TelegramEndpoint(optionConstructor.ip_address, optionConstructor.port);
                    newDc.Endpoints.Add(endpoint);
                }
            }

            dcs[dc] = newDc;
            mainDcId = dc;

            gateway.Dispose();
            gateway = null;
            establishedTask = new TaskCompletionSource<object>();
            ConnectAsync();
            await Established;
        }
예제 #8
0
        private static TelegramSession loadIfExists() {
            TelegramSession session;
            lock(typeof(TelegramSession)) {
                try {
                    using(IsolatedStorageFile fileStorage = IsolatedStorageFile.GetUserStoreForApplication())
                    using(Stream fileStream = new IsolatedStorageFileStream("session.dat", FileMode.Open, fileStorage))
                    using(BinaryReader fileReader = new BinaryReader(fileStream)) {
                        session = new TelegramSession(fileReader);
                        logger.info("loaded telegram session: {0}", session);
                    }
                } catch(Exception e) {
                    logger.info("error loading session, create new...: {0}", e);

                    logger.info("cleaning contacts");
                    CleanContacts();

                    ulong sessionId = Helpers.GenerateRandomUlong();
                    session = new TelegramSession(sessionId, 0);
                    // prod 173.240.5.1 
                    // test 173.240.5.253
                    TelegramEndpoint endpoint = new TelegramEndpoint("173.240.5.1", 443);
                    TelegramDC dc = new TelegramDC();
                    dc.Endpoints.Add(endpoint);
                    session.Dcs.Add(1, dc);
                    session.SetMainDcId(1);


                    logger.info("created new telegram session: {0}", session);
                }
            }

            return session;
        }
예제 #9
0
 public async Task ConnectAsync(TelegramDC dc, int maxRetries)
 {
     await base.ConnectAsync(dc, maxRetries);
 }
예제 #10
0
        public async Task <AuthKey> Generate(TelegramDC dc, int maxRetries)
        {
            ConnectedEvent += delegate {};
            await ConnectAsync(dc, maxRetries);



            random.NextBytes(nonce);

            using (MemoryStream memoryStream = new MemoryStream()) {
                using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream)) {
                    binaryWriter.Write(0x60469778);
                    binaryWriter.Write(nonce);
                    Send(memoryStream.ToArray());
                }
            }

            completionSource = new TaskCompletionSource <byte[]>();
            byte[] response = await completionSource.Task;

            BigInteger    pq;
            List <byte[]> fingerprints = new List <byte[]>();

            using (var memoryStream = new MemoryStream(response, false)) {
                using (var binaryReader = new BinaryReader(memoryStream)) {
                    int responseCode = binaryReader.ReadInt32();
                    if (responseCode != 0x05162463)
                    {
                        logger.error("invalid response code: {0}", responseCode);
                        return(null);
                    }


                    byte[] nonceFromServer = binaryReader.ReadBytes(16);
                    if (!nonceFromServer.SequenceEqual(nonce))
                    {
                        logger.debug("invalid nonce from server");
                        return(null);
                    }


                    serverNonce = binaryReader.ReadBytes(16);

                    byte[] pqbytes = Serializers.Bytes.read(binaryReader);
                    pq = new BigInteger(1, pqbytes);

                    int vectorId = binaryReader.ReadInt32();

                    if (vectorId != 0x1cb5c415)
                    {
                        logger.debug("invalid fingerprints vector id: {0}", vectorId);
                        return(null);
                    }

                    int fingerprintCount = binaryReader.ReadInt32();
                    for (int i = 0; i < fingerprintCount; i++)
                    {
                        byte[] fingerprint = binaryReader.ReadBytes(8);
                        fingerprints.Add(fingerprint);
                    }
                }
            }

            FactorizedPair pqPair = Factorizator.Factorize(pq);

            logger.debug("stage 1: ok");

            random.NextBytes(newNonce);

            byte[] reqDhParamsBytes;

            using (MemoryStream pqInnerData = new MemoryStream(255)) {
                using (BinaryWriter pqInnerDataWriter = new BinaryWriter(pqInnerData)) {
                    pqInnerDataWriter.Write(0x83c95aec); // pq_inner_data
                    Serializers.Bytes.write(pqInnerDataWriter, pq.ToByteArrayUnsigned());
                    Serializers.Bytes.write(pqInnerDataWriter, pqPair.Min.ToByteArrayUnsigned());
                    Serializers.Bytes.write(pqInnerDataWriter, pqPair.Max.ToByteArrayUnsigned());
                    pqInnerDataWriter.Write(nonce);
                    pqInnerDataWriter.Write(serverNonce);
                    pqInnerDataWriter.Write(newNonce);

                    logger.debug("pq_inner_data: {0}", BitConverter.ToString(pqInnerData.GetBuffer()));

                    byte[] ciphertext        = null;
                    byte[] targetFingerprint = null;
                    foreach (byte[] fingerprint in fingerprints)
                    {
                        ciphertext = RSA.Encrypt(BitConverter.ToString(fingerprint).Replace("-", string.Empty),
                                                 pqInnerData.GetBuffer(), 0, (int)pqInnerData.Position);
                        if (ciphertext != null)
                        {
                            targetFingerprint = fingerprint;
                            break;
                        }
                    }

                    if (ciphertext == null)
                    {
                        logger.error("not found valid key for fingerprints: {0}", String.Join(", ", fingerprints));
                        return(null);
                    }

                    using (MemoryStream reqDHParams = new MemoryStream(1024)) {
                        using (BinaryWriter reqDHParamsWriter = new BinaryWriter(reqDHParams)) {
                            reqDHParamsWriter.Write(0xd712e4be); // req_dh_params
                            reqDHParamsWriter.Write(nonce);
                            reqDHParamsWriter.Write(serverNonce);
                            Serializers.Bytes.write(reqDHParamsWriter, pqPair.Min.ToByteArrayUnsigned());
                            Serializers.Bytes.write(reqDHParamsWriter, pqPair.Max.ToByteArrayUnsigned());
                            reqDHParamsWriter.Write(targetFingerprint);
                            Serializers.Bytes.write(reqDHParamsWriter, ciphertext);

                            logger.debug("sending req_dh_paras: {0}", BitConverter.ToString(reqDHParams.ToArray()));
                            reqDhParamsBytes = reqDHParams.ToArray();
                        }
                    }
                }
            }

            completionSource = new TaskCompletionSource <byte[]>();
            Send(reqDhParamsBytes);
            response = await completionSource.Task;

            logger.debug("dh response: {0}", BitConverter.ToString(response));

            byte[] encryptedAnswer;

            using (MemoryStream responseStream = new MemoryStream(response, false)) {
                using (BinaryReader responseReader = new BinaryReader(responseStream)) {
                    uint responseCode = responseReader.ReadUInt32();

                    if (responseCode == 0x79cb045d)
                    {
                        // server_DH_params_fail
                        logger.error("server_DH_params_fail: TODO");
                        return(null);
                    }

                    if (responseCode != 0xd0e8075c)
                    {
                        logger.error("invalid response code: {0}", responseCode);
                        return(null);
                    }

                    byte[] nonceFromServer = responseReader.ReadBytes(16);
                    if (!nonceFromServer.SequenceEqual(nonce))
                    {
                        logger.debug("invalid nonce from server");
                        return(null);
                    }

                    byte[] serverNonceFromServer = responseReader.ReadBytes(16);
                    if (!serverNonceFromServer.SequenceEqual(serverNonce))
                    {
                        logger.error("invalid server nonce from server");
                        return(null);
                    }

                    encryptedAnswer = Serializers.Bytes.read(responseReader);
                }
            }

            logger.debug("encrypted answer: {0}", BitConverter.ToString(encryptedAnswer));

            AESKeyData key = AES.GenerateKeyDataFromNonces(serverNonce, newNonce);

            byte[] plaintextAnswer = AES.DecryptAES(key, encryptedAnswer);

            logger.debug("plaintext answer: {0}", BitConverter.ToString(plaintextAnswer));

            int        g;
            BigInteger dhPrime;
            BigInteger ga;

            using (MemoryStream dhInnerData = new MemoryStream(plaintextAnswer)) {
                using (BinaryReader dhInnerDataReader = new BinaryReader(dhInnerData)) {
                    byte[] hashsum = dhInnerDataReader.ReadBytes(20);
                    uint   code    = dhInnerDataReader.ReadUInt32();
                    if (code != 0xb5890dba)
                    {
                        logger.error("invalid dh_inner_data code: {0}", code);
                        return(null);
                    }

                    logger.debug("valid code");

                    byte[] nonceFromServer1 = dhInnerDataReader.ReadBytes(16);
                    if (!nonceFromServer1.SequenceEqual(nonce))
                    {
                        logger.error("invalid nonce in encrypted answer");
                        return(null);
                    }

                    logger.debug("valid nonce");

                    byte[] serverNonceFromServer1 = dhInnerDataReader.ReadBytes(16);
                    if (!serverNonceFromServer1.SequenceEqual(serverNonce))
                    {
                        logger.error("invalid server nonce in encrypted answer");
                        return(null);
                    }

                    logger.debug("valid server nonce");

                    g       = dhInnerDataReader.ReadInt32();
                    dhPrime = new BigInteger(1, Serializers.Bytes.read(dhInnerDataReader));
                    ga      = new BigInteger(1, Serializers.Bytes.read(dhInnerDataReader));

                    int serverTime = dhInnerDataReader.ReadInt32();
                    timeOffset = serverTime - (int)(Convert.ToInt64((DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMilliseconds) / 1000);

                    logger.debug("g: {0}, dhprime: {1}, ga: {2}", g, dhPrime, ga);
                }
            }

            BigInteger b   = new BigInteger(2048, random);
            BigInteger gb  = BigInteger.ValueOf(g).ModPow(b, dhPrime);
            BigInteger gab = ga.ModPow(b, dhPrime);

            logger.debug("gab: {0}", gab);

            // prepare client dh inner data
            byte[] clientDHInnerDataBytes;
            using (MemoryStream clientDhInnerData = new MemoryStream()) {
                using (BinaryWriter clientDhInnerDataWriter = new BinaryWriter(clientDhInnerData)) {
                    clientDhInnerDataWriter.Write(0x6643b654); // client_dh_inner_data
                    clientDhInnerDataWriter.Write(nonce);
                    clientDhInnerDataWriter.Write(serverNonce);
                    clientDhInnerDataWriter.Write((long)0);  // TODO: retry_id
                    Serializers.Bytes.write(clientDhInnerDataWriter, gb.ToByteArrayUnsigned());

                    using (MemoryStream clientDhInnerDataWithHash = new MemoryStream()) {
                        using (BinaryWriter clientDhInnerDataWithHashWriter = new BinaryWriter(clientDhInnerDataWithHash)) {
                            using (SHA1 sha1 = new SHA1Managed()) {
                                clientDhInnerDataWithHashWriter.Write(sha1.ComputeHash(clientDhInnerData.GetBuffer(), 0, (int)clientDhInnerData.Position));
                                clientDhInnerDataWithHashWriter.Write(clientDhInnerData.GetBuffer(), 0, (int)clientDhInnerData.Position);
                                clientDHInnerDataBytes = clientDhInnerDataWithHash.ToArray();
                            }
                        }
                    }
                }
            }

            logger.debug("client dh inner data papared len {0}: {1}", clientDHInnerDataBytes.Length, BitConverter.ToString(clientDHInnerDataBytes).Replace("-", ""));

            // encryption
            byte[] clientDhInnerDataEncryptedBytes = AES.EncryptAES(key, clientDHInnerDataBytes);

            logger.debug("inner data encrypted {0}: {1}", clientDhInnerDataEncryptedBytes.Length, BitConverter.ToString(clientDhInnerDataEncryptedBytes).Replace("-", ""));

            // prepare set_client_dh_params
            byte[] setclientDhParamsBytes;
            using (MemoryStream setClientDhParams = new MemoryStream()) {
                using (BinaryWriter setClientDhParamsWriter = new BinaryWriter(setClientDhParams)) {
                    setClientDhParamsWriter.Write(0xf5045f1f);
                    setClientDhParamsWriter.Write(nonce);
                    setClientDhParamsWriter.Write(serverNonce);
                    Serializers.Bytes.write(setClientDhParamsWriter, clientDhInnerDataEncryptedBytes);

                    setclientDhParamsBytes = setClientDhParams.ToArray();
                }
            }

            logger.debug("set client dh params prepared: {0}", BitConverter.ToString(setclientDhParamsBytes));


            completionSource = new TaskCompletionSource <byte[]>();
            Send(setclientDhParamsBytes);
            response = await completionSource.Task;

            using (MemoryStream responseStream = new MemoryStream(response)) {
                using (BinaryReader responseReader = new BinaryReader(responseStream)) {
                    uint code = responseReader.ReadUInt32();
                    if (code == 0x3bcbf734)  // dh_gen_ok
                    {
                        logger.debug("dh_gen_ok");

                        byte[] nonceFromServer = responseReader.ReadBytes(16);
                        if (!nonceFromServer.SequenceEqual(nonce))
                        {
                            logger.error("invalid nonce");
                            return(null);
                        }

                        byte[] serverNonceFromServer = responseReader.ReadBytes(16);

                        if (!serverNonceFromServer.SequenceEqual(serverNonce))
                        {
                            logger.error("invalid server nonce");
                            return(null);
                        }

                        byte[] newNonceHash1 = responseReader.ReadBytes(16);
                        logger.debug("new nonce hash 1: {0}", BitConverter.ToString(newNonceHash1));

                        AuthKey authKey = new AuthKey(gab);

                        byte[] newNonceHashCalculated = authKey.CalcNewNonceHash(newNonce, 1);

                        if (!newNonceHash1.SequenceEqual(newNonceHashCalculated))
                        {
                            logger.error("invalid new nonce hash");
                            return(null);
                        }

                        logger.info("generated new auth key: {0}", gab);
                        logger.info("saving time offset: {0}", timeOffset);
                        TelegramSession.Instance.TimeOffset = timeOffset;
                        return(authKey);
                    }
                    else if (code == 0x46dc1fb9)  // dh_gen_retry
                    {
                        logger.debug("dh_gen_retry");
                        return(null);
                    }
                    else if (code == 0xa69dae02)
                    {
                        // dh_gen_fail
                        logger.debug("dh_gen_fail");
                        return(null);
                    }
                    else
                    {
                        logger.debug("dh_gen unknown: {0}", code);
                        return(null);
                    }
                }
            }
        }
예제 #11
0
        public async Task<AuthKey> Generate(TelegramDC dc, int maxRetries) {    
            ConnectedEvent += delegate {};
            await ConnectAsync(dc, maxRetries);

            

            random.NextBytes(nonce);

            using(MemoryStream memoryStream = new MemoryStream()) {
                using(BinaryWriter binaryWriter = new BinaryWriter(memoryStream)) {
                    binaryWriter.Write(0x60469778);
                    binaryWriter.Write(nonce);
                    Send(memoryStream.ToArray());
                }
            }

            completionSource = new TaskCompletionSource<byte[]>();
            byte[] response = await completionSource.Task;

            BigInteger pq;
            List<byte[]> fingerprints = new List<byte[]>();

            using(var memoryStream = new MemoryStream(response, false)) {
                using(var binaryReader = new BinaryReader(memoryStream)) {
                    int responseCode = binaryReader.ReadInt32();
                    if(responseCode != 0x05162463) {
                        logger.error("invalid response code: {0}", responseCode);
                        return null;
                    }


                    byte[] nonceFromServer = binaryReader.ReadBytes(16);
                    if(!nonceFromServer.SequenceEqual(nonce)) {
                        logger.debug("invalid nonce from server");
                        return null;
                    }


                    serverNonce = binaryReader.ReadBytes(16);

                    byte[] pqbytes = Serializers.Bytes.read(binaryReader);
                    pq = new BigInteger(1, pqbytes);

                    int vectorId = binaryReader.ReadInt32();

                    if(vectorId != 0x1cb5c415) {
                        logger.debug("invalid fingerprints vector id: {0}", vectorId);
                        return null;
                    }

                    int fingerprintCount = binaryReader.ReadInt32();
                    for(int i = 0; i < fingerprintCount; i++) {
                        byte[] fingerprint = binaryReader.ReadBytes(8);
                        fingerprints.Add(fingerprint);
                    }

                }
            }

            FactorizedPair pqPair = Factorizator.Factorize(pq);

            logger.debug("stage 1: ok");

            random.NextBytes(newNonce);

            byte[] reqDhParamsBytes;

            using(MemoryStream pqInnerData = new MemoryStream(255)) {
                using(BinaryWriter pqInnerDataWriter = new BinaryWriter(pqInnerData)) {
                    pqInnerDataWriter.Write(0x83c95aec); // pq_inner_data
                    Serializers.Bytes.write(pqInnerDataWriter, pq.ToByteArrayUnsigned());
                    Serializers.Bytes.write(pqInnerDataWriter, pqPair.Min.ToByteArrayUnsigned());
                    Serializers.Bytes.write(pqInnerDataWriter, pqPair.Max.ToByteArrayUnsigned());
                    pqInnerDataWriter.Write(nonce);
                    pqInnerDataWriter.Write(serverNonce);
                    pqInnerDataWriter.Write(newNonce);

                    logger.debug("pq_inner_data: {0}", BitConverter.ToString(pqInnerData.GetBuffer()));

                    byte[] ciphertext = null;
                    byte[] targetFingerprint = null;
                    foreach(byte[] fingerprint in fingerprints) {
                        ciphertext = RSA.Encrypt(BitConverter.ToString(fingerprint).Replace("-", string.Empty),
                                                 pqInnerData.GetBuffer(), 0, (int) pqInnerData.Position);
                        if(ciphertext != null) {
                            targetFingerprint = fingerprint;
                            break;
                        }
                    }

                    if(ciphertext == null) {
                        logger.error("not found valid key for fingerprints: {0}", String.Join(", ", fingerprints));
                        return null;
                    }

                    using(MemoryStream reqDHParams = new MemoryStream(1024)) {
                        using(BinaryWriter reqDHParamsWriter = new BinaryWriter(reqDHParams)) {
                            reqDHParamsWriter.Write(0xd712e4be); // req_dh_params
                            reqDHParamsWriter.Write(nonce);
                            reqDHParamsWriter.Write(serverNonce);
                            Serializers.Bytes.write(reqDHParamsWriter, pqPair.Min.ToByteArrayUnsigned());
                            Serializers.Bytes.write(reqDHParamsWriter, pqPair.Max.ToByteArrayUnsigned());
                            reqDHParamsWriter.Write(targetFingerprint);
                            Serializers.Bytes.write(reqDHParamsWriter, ciphertext);

                            logger.debug("sending req_dh_paras: {0}", BitConverter.ToString(reqDHParams.ToArray()));
                            reqDhParamsBytes = reqDHParams.ToArray();
                        }
                    }
                }
            }

            completionSource = new TaskCompletionSource<byte[]>();
            Send(reqDhParamsBytes);
            response = await completionSource.Task;

            logger.debug("dh response: {0}", BitConverter.ToString(response));

            byte[] encryptedAnswer;

            using(MemoryStream responseStream = new MemoryStream(response, false)) {
                using(BinaryReader responseReader = new BinaryReader(responseStream)) {
                    uint responseCode = responseReader.ReadUInt32();

                    if(responseCode == 0x79cb045d) {
                        // server_DH_params_fail
                        logger.error("server_DH_params_fail: TODO");
                        return null;
                    }

                    if(responseCode != 0xd0e8075c) {
                        logger.error("invalid response code: {0}", responseCode);
                        return null;
                    }

                    byte[] nonceFromServer = responseReader.ReadBytes(16);
                    if(!nonceFromServer.SequenceEqual(nonce)) {
                        logger.debug("invalid nonce from server");
                        return null;
                    }

                    byte[] serverNonceFromServer = responseReader.ReadBytes(16);
                    if(!serverNonceFromServer.SequenceEqual(serverNonce)) {
                        logger.error("invalid server nonce from server");
                        return null;
                    }

                    encryptedAnswer = Serializers.Bytes.read(responseReader);
                }
            }

            logger.debug("encrypted answer: {0}", BitConverter.ToString(encryptedAnswer));

            AESKeyData key = AES.GenerateKeyDataFromNonces(serverNonce, newNonce);
            byte[] plaintextAnswer = AES.DecryptAES(key, encryptedAnswer);

            logger.debug("plaintext answer: {0}", BitConverter.ToString(plaintextAnswer));

            int g;
            BigInteger dhPrime;
            BigInteger ga;

            using(MemoryStream dhInnerData = new MemoryStream(plaintextAnswer)) {
                using(BinaryReader dhInnerDataReader = new BinaryReader(dhInnerData)) {
                    byte[] hashsum = dhInnerDataReader.ReadBytes(20);
                    uint code = dhInnerDataReader.ReadUInt32();
                    if(code != 0xb5890dba) {
                        logger.error("invalid dh_inner_data code: {0}", code);
                        return null;
                    }

                    logger.debug("valid code");

                    byte[] nonceFromServer1 = dhInnerDataReader.ReadBytes(16);
                    if(!nonceFromServer1.SequenceEqual(nonce)) {
                        logger.error("invalid nonce in encrypted answer");
                        return null;
                    }

                    logger.debug("valid nonce");

                    byte[] serverNonceFromServer1 = dhInnerDataReader.ReadBytes(16);
                    if(!serverNonceFromServer1.SequenceEqual(serverNonce)) {
                        logger.error("invalid server nonce in encrypted answer");
                        return null;
                    }

                    logger.debug("valid server nonce");

                    g = dhInnerDataReader.ReadInt32();
                    dhPrime = new BigInteger(1, Serializers.Bytes.read(dhInnerDataReader));
                    ga = new BigInteger(1, Serializers.Bytes.read(dhInnerDataReader));

                    int serverTime = dhInnerDataReader.ReadInt32();
                    timeOffset = serverTime - (int)(Convert.ToInt64((DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMilliseconds) / 1000);

                    logger.debug("g: {0}, dhprime: {1}, ga: {2}", g, dhPrime, ga);
                }
            }

            BigInteger b = new BigInteger(2048, random);
            BigInteger gb = BigInteger.ValueOf(g).ModPow(b, dhPrime);
            BigInteger gab = ga.ModPow(b, dhPrime);

            logger.debug("gab: {0}", gab);

            // prepare client dh inner data
            byte[] clientDHInnerDataBytes;
            using(MemoryStream clientDhInnerData = new MemoryStream()) {
                using(BinaryWriter clientDhInnerDataWriter = new BinaryWriter(clientDhInnerData)) {
                    clientDhInnerDataWriter.Write(0x6643b654); // client_dh_inner_data
                    clientDhInnerDataWriter.Write(nonce);
                    clientDhInnerDataWriter.Write(serverNonce);
                    clientDhInnerDataWriter.Write((long) 0); // TODO: retry_id
                    Serializers.Bytes.write(clientDhInnerDataWriter, gb.ToByteArrayUnsigned());

                    using(MemoryStream clientDhInnerDataWithHash = new MemoryStream()) {
                        using(BinaryWriter clientDhInnerDataWithHashWriter = new BinaryWriter(clientDhInnerDataWithHash)) {
                            using(SHA1 sha1 = new SHA1Managed()) {
                                clientDhInnerDataWithHashWriter.Write(sha1.ComputeHash(clientDhInnerData.GetBuffer(), 0, (int)clientDhInnerData.Position));
                                clientDhInnerDataWithHashWriter.Write(clientDhInnerData.GetBuffer(), 0, (int)clientDhInnerData.Position);
                                clientDHInnerDataBytes = clientDhInnerDataWithHash.ToArray();
                            }
                        }
                    }
                }
            }

            logger.debug("client dh inner data papared len {0}: {1}", clientDHInnerDataBytes.Length, BitConverter.ToString(clientDHInnerDataBytes).Replace("-",""));

            // encryption
            byte[] clientDhInnerDataEncryptedBytes = AES.EncryptAES(key, clientDHInnerDataBytes);

            logger.debug("inner data encrypted {0}: {1}", clientDhInnerDataEncryptedBytes.Length, BitConverter.ToString(clientDhInnerDataEncryptedBytes).Replace("-",""));

            // prepare set_client_dh_params
            byte[] setclientDhParamsBytes;
            using(MemoryStream setClientDhParams = new MemoryStream()) {
                using(BinaryWriter setClientDhParamsWriter = new BinaryWriter(setClientDhParams)) {
                    setClientDhParamsWriter.Write(0xf5045f1f);
                    setClientDhParamsWriter.Write(nonce);
                    setClientDhParamsWriter.Write(serverNonce);
                    Serializers.Bytes.write(setClientDhParamsWriter, clientDhInnerDataEncryptedBytes);

                    setclientDhParamsBytes = setClientDhParams.ToArray();
                }
            }

            logger.debug("set client dh params prepared: {0}", BitConverter.ToString(setclientDhParamsBytes));


            completionSource = new TaskCompletionSource<byte[]>();
            Send(setclientDhParamsBytes);
            response = await completionSource.Task;

            using(MemoryStream responseStream = new MemoryStream(response)) {
                using(BinaryReader responseReader = new BinaryReader(responseStream)) {
                    uint code = responseReader.ReadUInt32();
                    if(code == 0x3bcbf734) { // dh_gen_ok
                        logger.debug("dh_gen_ok");

                        byte[] nonceFromServer = responseReader.ReadBytes(16);
                        if(!nonceFromServer.SequenceEqual(nonce)) {
                            logger.error("invalid nonce");
                            return null;
                        }

                        byte[] serverNonceFromServer = responseReader.ReadBytes(16);

                        if(!serverNonceFromServer.SequenceEqual(serverNonce)) {
                            logger.error("invalid server nonce");
                            return null;
                        }

                        byte[] newNonceHash1 = responseReader.ReadBytes(16);
                        logger.debug("new nonce hash 1: {0}", BitConverter.ToString(newNonceHash1));

                        AuthKey authKey = new AuthKey(gab);

                        byte[] newNonceHashCalculated = authKey.CalcNewNonceHash(newNonce, 1);

                        if(!newNonceHash1.SequenceEqual(newNonceHashCalculated)) {
                            logger.error("invalid new nonce hash");
                            return null;
                        }

                        logger.info("generated new auth key: {0}", gab);
                        logger.info("saving time offset: {0}", timeOffset);
                        TelegramSession.Instance.TimeOffset = timeOffset;
                        return authKey;
                    }
                    else if(code == 0x46dc1fb9) { // dh_gen_retry
                        logger.debug("dh_gen_retry");
                        return null;
                    }
                    else if(code == 0xa69dae02) {
                        // dh_gen_fail
                        logger.debug("dh_gen_fail");
                        return null;
                    } else {
                        logger.debug("dh_gen unknown: {0}", code);
                        return null;
                    }
                }
            }
        }