add compatibility to Keepass 2.47 TimeOTP-Secret values (but not supporting {TIMEOTP} placeholder)

This commit is contained in:
Philipp Crocoll
2021-01-12 12:14:34 +01:00
parent d9bcac600c
commit 7df048263b
11 changed files with 159 additions and 67 deletions

View File

@@ -37,7 +37,7 @@ namespace KeePassLib.Utility
/// </summary>
public static class MemUtil
{
internal static readonly byte[] EmptyByteArray = new byte[0];
public static readonly byte[] EmptyByteArray = new byte[0];
private static readonly uint[] m_vSBox = new uint[256] {
0xCD2FACB3, 0xE78A7F5C, 0x6F0803FC, 0xBCF6E230,

View File

@@ -4,9 +4,8 @@ using KeePassLib.Collections;
namespace PluginTOTP
{
interface ITotpPluginAdapter
{
TotpData GetTotpData(IDictionary<string, string> entryFields, Context ctx, bool muteWarnings);
}
interface ITotpPluginAdapter
{
TotpData GetTotpData(IDictionary<string, string> entryFields, Context ctx, bool muteWarnings);
}
}

View File

@@ -55,7 +55,7 @@ namespace PluginTOTP
res.Duration = GetIntOrDefault(parameters, StepParameter, 30).ToString();
res.Length = GetIntOrDefault(parameters, SizeParameter, 6).ToString();
res.IsTotpEnry = true;
res.IsTotpEntry = true;
return res;
}

View File

@@ -46,7 +46,7 @@ namespace keepass2android
return res;
}
res.IsTotpEnry = true;
res.IsTotpEntry = true;
return res;
}
}

View File

@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using Android.Content;
using keepass2android;
using KeePassLib;
using KeePassLib.Cryptography;
using KeePassLib.Utility;
namespace PluginTOTP
{
class Keepass2TotpPluginAdapter : ITotpPluginAdapter
{
public TotpData GetTotpData(IDictionary<string, string> entryFields, Context ctx, bool muteWarnings)
{
TotpData res = new TotpData();
byte[] pbSecret = (GetOtpSecret(entryFields, "TimeOtp-") ?? MemUtil.EmptyByteArray);
if (pbSecret == null)
return res;
string strPeriod;
uint uPeriod = 0;
if (entryFields.TryGetValue("TimeOtp-Period", out strPeriod))
{
uint.TryParse(strPeriod, out uPeriod);
}
res.IsTotpEntry = true;
if (uPeriod == 0)
uPeriod = 30U;
string strLength;
uint uLength = 0;
if (entryFields.TryGetValue("TimeOtp-Length", out strLength))
{
uint.TryParse(strLength, out uLength);
}
if (uLength == 0) uLength = 6;
string strAlg;
entryFields.TryGetValue("TimeOtp-Algorithm", out strAlg);
res.TotpSecret = pbSecret;
res.Length = uLength.ToString();
res.Duration = uPeriod.ToString();
return res;
}
private static byte[] GetOtpSecret(IDictionary<string, string> entryFields, string strPrefix)
{
try
{
string str;
entryFields.TryGetValue(strPrefix + "Secret", out str);
if (!string.IsNullOrEmpty(str))
return StrUtil.Utf8.GetBytes(str);
entryFields.TryGetValue(strPrefix + "Secret-Hex", out str);
if (!string.IsNullOrEmpty(str))
return MemUtil.HexStringToByteArray(str);
entryFields.TryGetValue(strPrefix + "Secret-Base32", out str);
if (!string.IsNullOrEmpty(str))
return MemUtil.ParseBase32(str);
entryFields.TryGetValue(strPrefix + "Secret-Base64", out str);
if (!string.IsNullOrEmpty(str))
return Convert.FromBase64String(str);
}
catch (Exception e)
{
Kp2aLog.LogUnexpectedError(e);
}
return null;
}
}
}

View File

@@ -11,7 +11,13 @@ namespace keepass2android
class Kp2aTotp
{
readonly ITotpPluginAdapter[] _pluginAdapters = new ITotpPluginAdapter[] { new TrayTotpPluginAdapter(), new KeeOtpPluginAdapter(), new KeeWebOtpPluginAdapter() };
readonly ITotpPluginAdapter[] _pluginAdapters = new ITotpPluginAdapter[]
{
new TrayTotpPluginAdapter(),
new KeeOtpPluginAdapter(),
new KeeWebOtpPluginAdapter(),
new Keepass2TotpPluginAdapter(),
};
public ITotpPluginAdapter TryGetAdapter(PwEntryOutput entry)
{
@@ -20,7 +26,7 @@ namespace keepass2android
foreach (ITotpPluginAdapter adapter in _pluginAdapters)
{
TotpData totpData = adapter.GetTotpData(App.Kp2a.LastOpenedEntry.OutputStrings.ToDictionary(pair => StrUtil.SafeXmlString(pair.Key), pair => pair.Value.ReadString()), Application.Context, false);
if (totpData.IsTotpEnry)
if (totpData.IsTotpEntry)
{
return adapter;
}

View File

@@ -1,6 +1,8 @@
using System;
using System.Security;
using System.Security.Cryptography;
using keepass2android;
using PluginTOTP;
namespace KeeTrayTOTP.Libraries
{
@@ -110,24 +112,23 @@ namespace KeeTrayTOTP.Libraries
/// Instanciates a new TOTP_Generator.
/// </summary>
/// <param name="initSettings">Saved Settings.</param>
public TOTPProvider(string[] Settings)
public TOTPProvider(TotpData data)
{
this.duration = Convert.ToInt16(Settings[0]);
this.duration = Convert.ToInt16(data.Duration);
if (Settings[1] == "S")
if (data.Encoder == TotpData.EncoderSteam)
{
this.length = 5;
this.encoder = TOTPEncoder.steam;
}
else
{
this.length = Convert.ToInt16(Settings[1]);
this.length = Convert.ToInt16(data.Length);
this.encoder = TOTPEncoder.rfc6238;
}
if(Settings.Length > 2 && Settings[2] != String.Empty)
if(data.Url != null)
{
{
this.TimeCorrection = TimeSpan.Zero;
this.timeCorrectionError = false;
@@ -138,9 +139,12 @@ namespace KeeTrayTOTP.Libraries
this.TimeCorrection = TimeSpan.Zero;
}
this.HashAlgorithm = data.HashAlgorithm;
}
public string HashAlgorithm { get; set; }
/// <summary>
/// Returns current time with correction int UTC format.
/// </summary>
@@ -195,32 +199,6 @@ namespace KeeTrayTOTP.Libraries
return b;
}
/// <summary>
/// Generate a TOTP using provided binary data.
/// </summary>
/// <param name="key">Binary data.</param>
/// <returns>Time-based One Time Password encoded byte array.</returns>
public byte[] Generate(byte[] key)
{
System.Security.Cryptography.HMACSHA1 hmac = new System.Security.Cryptography.HMACSHA1(key, true); //Instanciates a new hash provider with a key.
byte[] hash = hmac.ComputeHash(GetBytes((ulong)Counter)); //Generates hash from key using counter.
hmac.Clear(); //Clear hash instance securing the key.
/*int binary = //Math.
((hash[offset] & 0x7f) << 24) //Math.
| ((hash[offset + 1] & 0xff) << 16) //Math.
| ((hash[offset + 2] & 0xff) << 8) //Math.
| (hash[offset + 3] & 0xff); //Math.
int password = binary % (int)Math.Pow(10, length); //Math.*/
int offset = hash[hash.Length - 1] & 0x0f; //Math.
byte[] totp = { hash[offset + 3], hash[offset + 2], hash[offset + 1], hash[offset] };
return totp;
/*
return password.ToString(new string('0', length)); //Math.*/
}
/// <summary>
/// Generate a TOTP using provided binary data.
@@ -241,15 +219,37 @@ namespace KeeTrayTOTP.Libraries
public string GenerateByByte(byte[] key)
{
HMACSHA1 hmac = new HMACSHA1(key, true); //Instanciates a new hash provider with a key.
byte[] codeInterval = BitConverter.GetBytes((ulong)Counter);
byte[] pbText = BitConverter.GetBytes((ulong)Counter);
if (BitConverter.IsLittleEndian)
Array.Reverse(codeInterval);
Array.Reverse(pbText);
byte[] hash = hmac.ComputeHash(codeInterval); //Generates hash from key using counter.
hmac.Clear(); //Clear hash instance securing the key.
byte[] hash;
if (HashAlgorithm == "HMAC-SHA-256")
{
using (HMACSHA256 h = new HMACSHA256(key))
{
hash = h.ComputeHash(pbText);
}
}
else if (HashAlgorithm == "HMAC-SHA-512")
{
using (HMACSHA512 h = new HMACSHA512(key))
{
hash = h.ComputeHash(pbText);
}
}
else
{
if (HashAlgorithm != "HMAC-SHA-1")
{
Kp2aLog.Log("Unexpected hash algorithm " + HashAlgorithm);
}
using (HMACSHA1 h = new HMACSHA1(key))
{
hash = h.ComputeHash(pbText);
}
}
int start = hash[hash.Length - 1] & 0xf;
byte[] totp = new byte[4];

View File

@@ -1,28 +1,31 @@
using System.Collections.Generic;
using KeeTrayTOTP.Libraries;
namespace PluginTOTP
{
struct TotpData
public class TotpData
{
public TotpData()
{
HashAlgorithm = "HMAC-SHA-1";
}
public const string EncoderSteam = "steam";
public const string EncoderRfc6238 = "rfc6238";
public bool IsTotpEnry { get; set; }
public string TotpSeed { get; set; }
public bool IsTotpEntry { get; set; }
public byte[] TotpSecret { get; set; }
public string TotpSeed
{
set { TotpSecret = Base32.Decode(value.Trim()); }
}
public string Duration { get; set; }
public string Encoder { get; set; }
public string Length { get; set; }
public string Url { get; set; }
public string[] Settings
{
get
{
List<string> settings = new List<string>() { Duration, Encoder == EncoderSteam ? "S" : Length};
if (Url != null)
settings.Add(Url);
return settings.ToArray();
}
}
}
public string HashAlgorithm { get; set; }
}
}

View File

@@ -135,7 +135,7 @@ namespace PluginTOTP
string InvalidCharacters;
if (SeedValidate(entryFields, out InvalidCharacters))
{
res.IsTotpEnry = true;
res.IsTotpEntry = true;
res.TotpSeed = SeedGet(entryFields).ExtWithoutSpaces();

View File

@@ -33,11 +33,11 @@ namespace PluginTOTP
Dictionary<string, string> entryFields = App.Kp2a.LastOpenedEntry.OutputStrings.ToDictionary(pair => StrUtil.SafeXmlString(pair.Key), pair => pair.Value.ReadString());
//mute warnings to avoid repeated display of the toasts
TotpData totpData = _adapter.GetTotpData(entryFields, _context, true /*mute warnings*/);
if (totpData.IsTotpEnry)
if (totpData.IsTotpEntry)
{
//generate a new totp
TOTPProvider prov = new TOTPProvider(totpData.Settings);
string totp = prov.Generate(totpData.TotpSeed);
TOTPProvider prov = new TOTPProvider(totpData);
string totp = prov.GenerateByByte(totpData.TotpSecret);
//update entry and keyboard
UpdateEntryData(totp);
//broadcast new field value (update EntryActivity). this might result in another keyboard

View File

@@ -230,6 +230,7 @@
<Compile Include="Totp\Base32.cs" />
<Compile Include="Totp\ITotpPluginAdapter.cs" />
<Compile Include="Totp\KeeOtpPluginAdapter.cs" />
<Compile Include="Totp\Keepass2TotpPluginAdapter.cs" />
<Compile Include="Totp\KeeWebOtpPluginAdapter.cs" />
<Compile Include="Totp\Kp2aTotp.cs" />
<Compile Include="Totp\TimeCorrectionProvider.cs" />