add compatibility to Keepass 2.47 TimeOTP-Secret values (but not supporting {TIMEOTP} placeholder)
This commit is contained in:
@@ -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,
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
||||
}
|
||||
|
@@ -46,7 +46,7 @@ namespace keepass2android
|
||||
return res;
|
||||
}
|
||||
|
||||
res.IsTotpEnry = true;
|
||||
res.IsTotpEntry = true;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
83
src/keepass2android/Totp/Keepass2TotpPluginAdapter.cs
Normal file
83
src/keepass2android/Totp/Keepass2TotpPluginAdapter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
@@ -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];
|
||||
|
||||
|
@@ -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; }
|
||||
}
|
||||
}
|
@@ -135,7 +135,7 @@ namespace PluginTOTP
|
||||
string InvalidCharacters;
|
||||
if (SeedValidate(entryFields, out InvalidCharacters))
|
||||
{
|
||||
res.IsTotpEnry = true;
|
||||
res.IsTotpEntry = true;
|
||||
res.TotpSeed = SeedGet(entryFields).ExtWithoutSpaces();
|
||||
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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" />
|
||||
|
Reference in New Issue
Block a user