private bool CheckNewNonceHash(Int256 newNonce, Int128 newNonceHash) { byte[] hash = ComputeSHA1(newNonce.ToBytes()); Int128 nonceHash = hash.ToInt128(); return(nonceHash == newNonceHash); }
public static async Task <Step3Res> Do( Some <ServerDhParams.OkTag> someServerDhParams, Int256 newNonce, Some <MtProtoPlainTransport> transport ) { var dhParams = someServerDhParams.Value; var key = Aes.GenerateKeyDataFromNonces(dhParams.ServerNonce.ToBytes(true), newNonce.ToBytes(true)); var plaintextAnswer = Aes.DecryptAES(key, dhParams.EncryptedAnswer.ToArrayUnsafe()); var dh = plaintextAnswer.Apply(Deserialize(WithHashSumCheck(ServerDhInnerData.Deserialize))); Helpers.Assert(dh.Nonce == dhParams.Nonce, "auth step3: invalid nonce in encrypted answer"); Helpers.Assert(dh.ServerNonce == dhParams.ServerNonce, "auth step3: invalid server nonce in encrypted answer"); var currentEpochTime = Helpers.GetCurrentEpochTime(); var timeOffset = dh.ServerTime - currentEpochTime; var g = dh.G; var dhPrime = new BigInteger(1, dh.DhPrime.ToArrayUnsafe()); var ga = new BigInteger(1, dh.Ga.ToArrayUnsafe()); var b = new BigInteger(Rnd.NextBytes(2048)); var gb = BigInteger.ValueOf(g).ModPow(b, dhPrime); var gab = ga.ModPow(b, dhPrime); var dhInnerData = new ClientDhInnerData( nonce: dh.Nonce, serverNonce: dh.ServerNonce, retryId: 0, gb: gb.ToByteArrayUnsigned().ToBytesUnsafe() ); var dhInnerDataBts = Serialize(dhInnerData); var dhInnerDataHashedBts = WithHashAndPadding(dhInnerDataBts); var dhInnerDataHashedEncryptedBytes = Aes.EncryptAES(key, dhInnerDataHashedBts); var resp = await transport.Value.Call(new SetClientDhParams( nonce : dh.Nonce, serverNonce : dh.ServerNonce, encryptedData : dhInnerDataHashedEncryptedBytes.ToBytesUnsafe() )); var res = resp.Match( dhGenOkTag: identity, dhGenFailTag: _ => throw Helpers.FailedAssertion("auth step3: dh_gen_fail"), dhGenRetryTag: _ => throw Helpers.FailedAssertion("auth step3: dh_gen_retry") ); var authKey = AuthKey.FromGab(gab); var newNonceHash = authKey.CalcNewNonceHash(newNonce.ToBytes(true), 1).ToInt128(); Helpers.Assert(res.Nonce == dh.Nonce, "auth step3: invalid nonce"); Helpers.Assert(res.ServerNonce == dh.ServerNonce, "auth step3: invalid server nonce"); Helpers.Assert(res.NewNonceHash1 == newNonceHash, "auth step3: invalid new nonce hash"); return(new Step3Res(authKey, timeOffset)); }
/// <summary> /// Converts an <see cref="Int256" /> value to a byte array. /// </summary> /// <param name="value">Value.</param> /// <param name="asLittleEndian">Convert from little endian.</param> /// <param name="trimZeros">Trim zero bytes from left or right, depending on endian.</param> /// <returns>Array of bytes.</returns> public static byte[] ToBytes(this Int256 value, bool?asLittleEndian = null, bool trimZeros = false) { var buffer = new byte[32]; value.ToBytes(buffer, 0, asLittleEndian); if (trimZeros) { buffer = buffer.TrimZeros(asLittleEndian); } return(buffer); }
public async Task <AuthInfo> CreateAuthKey(CancellationToken cancellationToken) { Restart: IMTProtoClientConnection connection = _mtProtoBuilder.BuildConnection(_clientTransportConfig); var methods = connection.Methods; try { Int128 nonce = _nonceGenerator.GetNonce(16).ToInt128(); Console.WriteLine(string.Format("Creating auth key (nonce = {0:X16})...", nonce)); // Connecting. Console.WriteLine("Connecting..."); MTProtoConnectResult result = await connection.Connect(cancellationToken); if (result != MTProtoConnectResult.Success) { throw new CouldNotConnectException("Connection trial was unsuccessful.", result); } // Requesting PQ. Console.WriteLine("Requesting PQ..."); var resPQ = await methods.ReqPqAsync(new ReqPqArgs { Nonce = nonce }) as ResPQ; if (resPQ == null) { throw new InvalidResponseException(); } CheckNonce(nonce, resPQ.Nonce); Console.WriteLine(string.Format("Response PQ = {0}, server nonce = {1:X16}, {2}.", resPQ.Pq.ToHexString(), resPQ.ServerNonce, resPQ.ServerPublicKeyFingerprints.Aggregate("public keys fingerprints:", (text, fingerprint) => text + " " + fingerprint.ToString("X8")))); Int128 serverNonce = resPQ.ServerNonce; byte[] serverNonceBytes = serverNonce.ToBytes(); // Requesting DH params. PQInnerData pqInnerData; ReqDHParamsArgs reqDhParamsArgs = CreateReqDhParamsArgs(resPQ, out pqInnerData); Int256 newNonce = pqInnerData.NewNonce; byte[] newNonceBytes = newNonce.ToBytes(); Console.WriteLine(string.Format("Requesting DH params with the new nonce: {0:X32}...", newNonce)); IServerDHParams serverDHParams = await methods.ReqDHParamsAsync(reqDhParamsArgs); if (serverDHParams == null) { throw new InvalidResponseException(); } var dhParamsFail = serverDHParams as ServerDHParamsFail; if (dhParamsFail != null) { if (CheckNewNonceHash(newNonce, dhParamsFail.NewNonceHash)) { throw new MTProtoException("Requesting of the server DH params failed."); } throw new InvalidResponseException("The new nonce hash received from the server does NOT match with hash of the sent new nonce hash."); } var dhParamsOk = serverDHParams as ServerDHParamsOk; if (dhParamsOk == null) { throw new InvalidResponseException(); } CheckNonce(nonce, dhParamsOk.Nonce); CheckNonce(serverNonce, dhParamsOk.ServerNonce); Console.WriteLine("Received server DH params. Computing temp AES key and IV..."); byte[] tmpAesKey; byte[] tmpAesIV; ComputeTmpAesKeyAndIV(newNonceBytes, serverNonceBytes, out tmpAesKey, out tmpAesIV); Console.WriteLine("Decrypting server DH inner data..."); ServerDHInnerData serverDHInnerData = DecryptServerDHInnerData(dhParamsOk.EncryptedAnswer, tmpAesKey, tmpAesIV); // TODO: Implement checking. #region Checking instructions /**************************************************************************************************************************************** * * Client is expected to check whether p = dh_prime is a safe 2048-bit prime (meaning that both p and (p-1)/2 are prime, * and that 2^2047 < p < 2^2048), and that g generates a cyclic subgroup of prime order (p-1)/2, i.e. is a quadratic residue mod p. * Since g is always equal to 2, 3, 4, 5, 6 or 7, this is easily done using quadratic reciprocity law, * yielding a simple condition on p mod 4g — namely, p mod 8 = 7 for g = 2; p mod 3 = 2 for g = 3; * no extra condition for g = 4; p mod 5 = 1 or 4 for g = 5; p mod 24 = 19 or 23 for g = 6; and p mod 7 = 3, 5 or 6 for g = 7. * After g and p have been checked by the client, it makes sense to cache the result, so as not to repeat lengthy computations in future. * * If the verification takes too long time (which is the case for older mobile devices), one might initially * run only 15 Miller—Rabin iterations for verifying primeness of p and (p - 1)/2 with error probability not exceeding * one billionth, and do more iterations later in the background. * * Another optimization is to embed into the client application code a small table with some known “good” couples (g,p) * (or just known safe primes p, since the condition on g is easily verified during execution), * checked during code generation phase, so as to avoid doing such verification during runtime altogether. * Server changes these values rarely, thus one usually has to put the current value of server's dh_prime into such a table. * * For example, current value of dh_prime equals (in big-endian byte order): * C71CAEB9C6B1C9048E6C522F70F13F73980D40238E3E21C14934D037563D930F48198A0AA7C14058229493D22530F4DBFA336F6E0AC925139543AED44CCE7C3720FD51 * F69458705AC68CD4FE6B6B13ABDC9746512969328454F18FAF8C595F642477FE96BB2A941D5BCD1D4AC8CC49880708FA9B378E3C4F3A9060BEE67CF9A4A4A695811051 * 907E162753B56B0F6B410DBA74D8A84B2A14B3144E0EF1284754FD17ED950D5965B4B9DD46582DB1178D169C6BC465B0D6FF9CA3928FEF5B9AE4E418FC15E83EBEA0F8 * 7FA9FF5EED70050DED2849F47BF959D956850CE929851F0D8115F635B105EE2E4E15D04B2454BF6F4FADF034B10403119CD8E3B92FCC5B * * IMPORTANT: Apart from the conditions on the Diffie-Hellman prime dh_prime and generator g, * both sides are to check that g, g_a and g_b are greater than 1 and less than dh_prime - 1. * We recommend checking that g_a and g_b are between 2^{2048-64} and dh_prime - 2^{2048-64} as well. * ****************************************************************************************************************************************/ #endregion byte[] authKeyAuxHash = null; // Setting of client DH params. for (int retry = 0; retry < AuthRetryCount; retry++) { Console.WriteLine(string.Format("Trial #{0} to set client DH params...", retry + 1)); byte[] b = _nonceGenerator.GetNonce(256); byte[] g = serverDHInnerData.G.ToBytes(false); byte[] ga = serverDHInnerData.GA; byte[] p = serverDHInnerData.DhPrime; DHOutParams dhOutParams = _encryptionServices.DH(b, g, ga, p); byte[] authKey = dhOutParams.S; var clientDHInnerData = new ClientDHInnerData { Nonce = nonce, ServerNonce = serverNonce, RetryId = authKeyAuxHash == null ? 0 : authKeyAuxHash.ToUInt64(), GB = dhOutParams.GB }; Console.WriteLine(string.Format("DH data: B={0}, G={1}, GB={2}, P={3}, S={4}.", b.ToHexString(), g.ToHexString(), dhOutParams.GB.ToHexString(), p.ToHexString(), authKey.ToHexString())); // byte[] authKeyHash = ComputeSHA1(authKey).Skip(HashLength - 8).Take(8).ToArray(); // Not used in client. authKeyAuxHash = ComputeSHA1(authKey).Take(8).ToArray(); byte[] data = _tlRig.Serialize(clientDHInnerData); // data_with_hash := SHA1(data) + data + (0-15 random bytes); such that length be divisible by 16; byte[] dataWithHash = PrependHashAndAlign(data, 16); // encrypted_data := AES256_ige_encrypt (data_with_hash, tmp_aes_key, tmp_aes_iv); byte[] encryptedData = _encryptionServices.Aes256IgeEncrypt(dataWithHash, tmpAesKey, tmpAesIV); var setClientDHParamsArgs = new SetClientDHParamsArgs { Nonce = nonce, ServerNonce = serverNonce, EncryptedData = encryptedData }; Console.WriteLine("Setting client DH params..."); ISetClientDHParamsAnswer setClientDHParamsAnswer = await methods.SetClientDHParamsAsync(setClientDHParamsArgs); var dhGenOk = setClientDHParamsAnswer as DhGenOk; if (dhGenOk != null) { Console.WriteLine("OK."); CheckNonce(nonce, dhGenOk.Nonce); CheckNonce(serverNonce, dhGenOk.ServerNonce); var newNonceHash1 = ComputeNewNonceHash(newNonce, 1, authKeyAuxHash); try { CheckNonce(newNonceHash1, dhGenOk.NewNonceHash1); } catch { Console.WriteLine("Failed to match new nonce hash. Restarting authentication."); goto Restart; } Console.WriteLine(string.Format("Negotiated auth key: {0}", authKey.ToHexString())); var initialSalt = ComputeInitialSalt(newNonceBytes, serverNonceBytes); return(new AuthInfo(authKey, initialSalt)); } var dhGenRetry = setClientDHParamsAnswer as DhGenRetry; if (dhGenRetry != null) { Console.WriteLine("Retry."); CheckNonce(nonce, dhGenRetry.Nonce); CheckNonce(serverNonce, dhGenRetry.ServerNonce); Int128 newNonceHash2 = ComputeNewNonceHash(newNonce, 2, authKeyAuxHash); try { CheckNonce(newNonceHash2, dhGenRetry.NewNonceHash2); } catch { Console.WriteLine("Failed to match new nonce hash 2. Restarting authentication."); goto Restart; } continue; } var dhGenFail = setClientDHParamsAnswer as DhGenFail; if (dhGenFail != null) { Console.WriteLine("Fail."); CheckNonce(nonce, dhGenFail.Nonce); CheckNonce(serverNonce, dhGenFail.ServerNonce); Int128 newNonceHash3 = ComputeNewNonceHash(newNonce, 3, authKeyAuxHash); // REDUNDANT, as we'll fail anyway //CheckNonce(newNonceHash3, dhGenFail.NewNonceHash3); throw new MTProtoException("Failed to set client DH params."); } } throw new MTProtoException(string.Format("Failed to negotiate an auth key in {0} trials.", AuthRetryCount)); } catch (Exception e) { Console.WriteLine("Could not create auth key: " + e); throw; } finally { if (connection != null) { connection.Dispose(); } } }
public static void Serialize(Int256 src, BinaryWriter writer) { writer.Write(src.ToBytes(true)); }
/// <summary> /// Writes a 256-bit signed integer. /// </summary> public virtual void WriteInt256(Int256 value) { value.ToBytes(_buffer, 0, _streamAsLittleEndianInternal); Write(_buffer, 0, 32); }
private bool CheckNewNonceHash(Int256 newNonce, Int128 newNonceHash) { byte[] hash = ComputeSHA1(newNonce.ToBytes()); Int128 nonceHash = hash.ToInt128(); return nonceHash == newNonceHash; }
public static void WriteInt256(BinaryWriter bw, Int256 value) => bw.Write(value.ToBytes(true));