Added 2FA support

This commit is contained in:
Wizou 2021-08-10 08:25:37 +02:00
parent 3d540cdb8f
commit ba90b47831
5 changed files with 80 additions and 6 deletions

View file

@ -47,6 +47,7 @@ static string Config(string what)
if (what == "verification_code") { Console.Write("Code: "); return Console.ReadLine(); }
if (what == "first_name") return "John"; // if sign-up is required
if (what == "last_name") return "Doe"; // if sign-up is required
if (what == "password") return "secret!"; // if user has enabled 2FA
return null;
}
...
@ -116,6 +117,6 @@ Here are the main expected developments:
- [x] Convert API functions classes to real methods and serialize structures without using Reflection
- [ ] Separate task/thread for reading/handling update messages independently from CallAsync
- [ ] Support MTProto 2.0
- [ ] Support users with 2FA enabled
- [x] Support users with 2FA enabled
- [ ] Support secret chats end-to-end encryption & PFS
- [ ] Support all service messages

View file

@ -411,9 +411,12 @@ namespace WTelegram
{
authorization = await Auth_SignIn(phone_number, sentCode.phone_code_hash, verification_code);
}
catch (RpcException e) when (e.Code == 400 && e.Message == "SESSION_PASSWORD_NEEDED")
catch (RpcException e) when (e.Code == 401 && e.Message == "SESSION_PASSWORD_NEEDED")
{
throw new NotImplementedException("Library does not support 2FA yet"); //TODO: support 2FA
var accountPassword = await Account_GetPassword();
Helpers.Log(3, $"This account has enabled 2FA. A password is needed. {accountPassword.hint}");
var checkPasswordSRP = Check2FA(accountPassword, Config("password"));
authorization = await Auth_CheckPassword(checkPasswordSRP);
}
if (authorization is Auth_AuthorizationSignUpRequired signUpRequired)
{

View file

@ -257,5 +257,66 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
}
return output;
}
internal static InputCheckPasswordSRPBase Check2FA(Account_Password accountPassword, string password)
{
if (accountPassword.current_algo is not PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow algo)
throw new ApplicationException("2FA authentication uses an unsupported algo: " + accountPassword.current_algo?.GetType().Name);
var passwordBytes = Encoding.UTF8.GetBytes(password);
var g = new BigInteger(algo.g);
var p = new BigInteger(algo.p, true, true);
var g_b = new BigInteger(accountPassword.srp_B, true, true);
var g_b_256 = g_b.To256Bytes();
var g_256 = g.To256Bytes();
ValidityChecks(p, algo.g);
var sha256 = SHA256.Create();
sha256.TransformBlock(algo.salt1, 0, algo.salt1.Length, null, 0);
sha256.TransformBlock(passwordBytes, 0, passwordBytes.Length, null, 0);
sha256.TransformFinalBlock(algo.salt1, 0, algo.salt1.Length);
var hash = sha256.Hash;
sha256.TransformBlock(algo.salt2, 0, algo.salt2.Length, null, 0);
sha256.TransformBlock(hash, 0, 32, null, 0);
sha256.TransformFinalBlock(algo.salt2, 0, algo.salt2.Length);
hash = sha256.Hash;
var pbkdf2 = new Rfc2898DeriveBytes(hash, algo.salt1, 100000, HashAlgorithmName.SHA512).GetBytes(64);
sha256.TransformBlock(algo.salt2, 0, algo.salt2.Length, null, 0);
sha256.TransformBlock(pbkdf2, 0, 64, null, 0);
sha256.TransformFinalBlock(algo.salt2, 0, algo.salt2.Length);
var x = new BigInteger(sha256.Hash, true, true);
sha256.TransformBlock(algo.p, 0, 256, null, 0);
sha256.TransformFinalBlock(g_256, 0, 256);
var k = new BigInteger(sha256.Hash, true, true);
var v = BigInteger.ModPow(g, x, p);
var k_v = (k * v) % p;
var a = new BigInteger(new Int256(RNG).raw, true, true);
var g_a = BigInteger.ModPow(g, a, p);
var g_a_256 = g_a.To256Bytes();
sha256.TransformBlock(g_a_256, 0, 256, null, 0);
sha256.TransformFinalBlock(g_b_256, 0, 256);
var u = new BigInteger(sha256.Hash, true, true);
var t = (g_b - k_v) % p; //(positive modulo, if the result is negative increment by p)
if (t.Sign < 0) t += p;
var s_a = BigInteger.ModPow(t, a + u * x, p);
var k_a = SHA256.HashData(s_a.To256Bytes());
hash = SHA256.HashData(algo.p);
var h2 = SHA256.HashData(g_256);
for (int i = 0; i < 32; i++) hash[i] ^= h2[i];
sha256.TransformBlock(hash, 0, 32, null, 0);
sha256.TransformBlock(SHA256.HashData(algo.salt1), 0, 32, null, 0);
sha256.TransformBlock(SHA256.HashData(algo.salt2), 0, 32, null, 0);
sha256.TransformBlock(g_a_256, 0, 256, null, 0);
sha256.TransformBlock(g_b_256, 0, 256, null, 0);
sha256.TransformFinalBlock(k_a, 0, 32);
var m1 = sha256.Hash;
return new InputCheckPasswordSRP { A = g_a_256, M1 = m1, srp_id = accountPassword.srp_id };
}
}
}

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Numerics;
namespace WTelegram
{
@ -128,5 +129,13 @@ namespace WTelegram
}
}
}
internal static byte[] To256Bytes(this BigInteger bi)
{
var result = new byte[256];
var bigEndian = bi.ToByteArray(true, true);
bigEndian.CopyTo(result, 256 - bigEndian.Length);
return result;
}
}
}

View file

@ -58,7 +58,7 @@ namespace WTelegram
{
var utf8Json = JsonSerializer.SerializeToUtf8Bytes(this, Helpers.JsonOptions);
var finalBlock = new byte[16];
var output = new byte[(16 + 32 + utf8Json.Length + 15) & ~15];
var output = new byte[(16 + 32 + utf8Json.Length + 16) & ~15];
Encryption.RNG.GetBytes(output, 0, 16);
using var aes = Aes.Create();
using var encryptor = aes.CreateEncryptor(_apiHash, output[0..16]);