Add totp authenticator
parent
4c52ad3a4c
commit
d8436e7353
@ -0,0 +1,91 @@
|
|||||||
|
using System;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using SteamKit2.Authentication;
|
||||||
|
|
||||||
|
namespace DepotDownloader;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implementation of <see cref="IAuthenticator"/> that uses a TOTP Authenticator key to generate TOTP verification codes.
|
||||||
|
/// Falls back to the provided fallback authenticator.
|
||||||
|
/// </summary>
|
||||||
|
public class TotpAuthenticator : IAuthenticator
|
||||||
|
{
|
||||||
|
private readonly string _totpKey;
|
||||||
|
private readonly IAuthenticator _fallbackAuthenticator;
|
||||||
|
|
||||||
|
public TotpAuthenticator(string totpKey, IAuthenticator fallbackAuthenticator)
|
||||||
|
{
|
||||||
|
_totpKey = totpKey;
|
||||||
|
_fallbackAuthenticator = fallbackAuthenticator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<string> GetDeviceCodeAsync(bool previousCodeWasIncorrect)
|
||||||
|
{
|
||||||
|
if (previousCodeWasIncorrect)
|
||||||
|
{
|
||||||
|
return _fallbackAuthenticator.GetDeviceCodeAsync(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var deviceCode = GetDeviceCode(_totpKey);
|
||||||
|
return deviceCode != null ? Task.FromResult(deviceCode) : _fallbackAuthenticator.GetDeviceCodeAsync(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<string> GetEmailCodeAsync(string email, bool previousCodeWasIncorrect)
|
||||||
|
{
|
||||||
|
return _fallbackAuthenticator.GetEmailCodeAsync(email, previousCodeWasIncorrect);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<bool> AcceptDeviceConfirmationAsync()
|
||||||
|
{
|
||||||
|
return _fallbackAuthenticator.AcceptDeviceConfirmationAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/bitwarden/mobile/blob/7a65bf7fd7b44424073201c2c574d45b64b9ec9d/src/Core/Services/TotpService.cs
|
||||||
|
private static string GetDeviceCode(string key)
|
||||||
|
{
|
||||||
|
const int Digits = 5;
|
||||||
|
const int TotpDefaultTimer = 30;
|
||||||
|
const string SteamChars = "23456789BCDFGHJKMNPQRTVWXY";
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(key))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyBytes = Util.DecodeBase32String(key);
|
||||||
|
if (keyBytes == null || keyBytes.Length == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var time = DateTimeOffset.Now.ToUnixTimeSeconds() / TotpDefaultTimer;
|
||||||
|
var timeBytes = BitConverter.GetBytes(time);
|
||||||
|
if (BitConverter.IsLittleEndian)
|
||||||
|
{
|
||||||
|
Array.Reverse(timeBytes, 0, timeBytes.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
var hash = new HMACSHA1(keyBytes).ComputeHash(timeBytes);
|
||||||
|
|
||||||
|
if (hash.Length == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset = hash[^1] & 0xf;
|
||||||
|
var binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16) |
|
||||||
|
((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);
|
||||||
|
|
||||||
|
var otp = string.Empty;
|
||||||
|
var fullCode = binary & 0x7fffffff;
|
||||||
|
for (var i = 0; i < Digits; i++)
|
||||||
|
{
|
||||||
|
otp += SteamChars[fullCode % SteamChars.Length];
|
||||||
|
fullCode = (int)Math.Truncate(fullCode / (double)SteamChars.Length);
|
||||||
|
}
|
||||||
|
return otp;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue