334 lines
9.3 KiB
C#
334 lines
9.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Android.Content;
|
|
using Android.OS;
|
|
using Android.Preferences;
|
|
using Android.Widget;
|
|
using keepass2android;
|
|
using keepass2android;
|
|
|
|
namespace PluginTOTP
|
|
{
|
|
class TrayTotpPluginAdapter : ITotpPluginAdapter
|
|
{
|
|
public TotpData GetTotpData(IDictionary<string, string> entryFields, Context ctx, bool muteWarnings)
|
|
{
|
|
return new TrayTotpHandler(ctx, new Handler(Looper.MainLooper), muteWarnings).GetTotpData(entryFields);
|
|
}
|
|
|
|
private class TrayTotpHandler
|
|
{
|
|
private readonly Context _ctx;
|
|
private readonly Handler _uiThreadHandler;
|
|
private readonly bool _muteWarnings;
|
|
|
|
private string SeedFieldName { get { return PreferenceManager.GetDefaultSharedPreferences(_ctx).GetString(_ctx.GetString(Resource.String.TrayTotp_SeedField_key), "TOTP Seed"); } }
|
|
private string SettingsFieldName { get { return PreferenceManager.GetDefaultSharedPreferences(_ctx).GetString(_ctx.GetString(Resource.String.TrayTotp_SettingsField_key), "TOTP Settings"); } }
|
|
|
|
public TrayTotpHandler(Context ctx, Handler uiThreadHandler, bool muteWarnings)
|
|
{
|
|
_ctx = ctx;
|
|
_uiThreadHandler = uiThreadHandler;
|
|
_muteWarnings = muteWarnings;
|
|
}
|
|
|
|
|
|
internal bool HasSeed(IDictionary<string, string> entryFields)
|
|
{
|
|
string seed;
|
|
entryFields.TryGetValue(SeedFieldName, out seed);
|
|
return !String.IsNullOrEmpty(seed);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if specified Entry's Interval and Length are valid. All settings statuses are available as out booleans.
|
|
/// </summary>
|
|
/// <param name="pe">Password Entry.</param>
|
|
/// <param name="IsIntervalValid">Interval Validity.</param>
|
|
/// <param name="IsLengthValid">Length Validity.</param>
|
|
/// <param name="IsUrlValid">Url Validity.</param>
|
|
/// <returns>Error(s) while validating Interval or Length.</returns>
|
|
internal bool SettingsValidate(IDictionary<string, string> entryFields, out bool IsIntervalValid, out bool IsLengthValid, out bool IsUrlValid)
|
|
{
|
|
bool SettingsValid = true;
|
|
try
|
|
{
|
|
string[] Settings = SettingsGet(entryFields);
|
|
try
|
|
{
|
|
IsIntervalValid = (Convert.ToInt16(Settings[0]) > 0) && (Convert.ToInt16(Settings[0]) < 61); //Interval
|
|
}
|
|
catch (Exception)
|
|
{
|
|
IsIntervalValid = false;
|
|
SettingsValid = false;
|
|
}
|
|
try
|
|
{
|
|
IsLengthValid = (Settings[1] == "6") || (Settings[1] == "8") || (Settings[1] == "S"); //Length
|
|
}
|
|
catch (Exception)
|
|
{
|
|
IsLengthValid = false;
|
|
SettingsValid = false;
|
|
}
|
|
try
|
|
{
|
|
IsUrlValid = (Settings[2].StartsWith("http://")) || (Settings[2].StartsWith("https://")); //Url
|
|
}
|
|
catch (Exception)
|
|
{
|
|
IsUrlValid = false;
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
IsIntervalValid = false;
|
|
IsLengthValid = false;
|
|
IsUrlValid = false;
|
|
SettingsValid = false;
|
|
}
|
|
return SettingsValid;
|
|
}
|
|
|
|
private string[] SettingsGet(IDictionary<string, string> entryFields)
|
|
{
|
|
return entryFields.TryGetValue(SettingsFieldName, out var settings) ? settings.Split(';') : new[] { "30", "6" };
|
|
}
|
|
|
|
public TotpData GetTotpData(IDictionary<string, string> entryFields)
|
|
{
|
|
TotpData res = new TotpData();
|
|
|
|
if (HasSeed(entryFields))
|
|
{
|
|
bool ValidInterval; bool ValidLength; bool ValidUrl;
|
|
if (SettingsValidate(entryFields, out ValidInterval, out ValidLength, out ValidUrl))
|
|
{
|
|
bool NoTimeCorrection = false;
|
|
string[] Settings = SettingsGet(entryFields);
|
|
res.InternalFields.Add(SettingsFieldName);
|
|
res.InternalFields.Add(SeedFieldName);
|
|
res.Duration = Settings[0];
|
|
res.Length = Settings[1];
|
|
if (res.Length == "S")
|
|
res.Encoder = TotpData.EncoderSteam;
|
|
if (ValidUrl)
|
|
{
|
|
NoTimeCorrection = true;
|
|
res.TimeCorrectionUrl = Settings[2];
|
|
/*var CurrentTimeCorrection = TimeCorrections[Settings[2]];
|
|
if (CurrentTimeCorrection != null)
|
|
{
|
|
TotpGenerator.TimeCorrection = CurrentTimeCorrection.TimeCorrection;
|
|
}
|
|
else
|
|
{
|
|
TotpGenerator.TimeCorrection = TimeSpan.Zero;
|
|
NoTimeCorrection = true;
|
|
}*/
|
|
}
|
|
string InvalidCharacters;
|
|
if (SeedValidate(entryFields, out InvalidCharacters))
|
|
{
|
|
res.IsTotpEntry = true;
|
|
res.TotpSeed = SeedGet(entryFields).ExtWithoutSpaces();
|
|
|
|
|
|
}
|
|
else
|
|
{
|
|
ShowWarning("Bad seed!" + InvalidCharacters.ExtWithParenthesis().ExtWithSpaceBefore());
|
|
}
|
|
if (NoTimeCorrection)
|
|
ShowWarning("Warning: TOTP Time correction not implemented!");
|
|
}
|
|
else
|
|
{
|
|
ShowWarning("Bad settings!");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//no totp entry
|
|
}
|
|
return res;
|
|
}
|
|
|
|
private void ShowWarning(string warning)
|
|
{
|
|
if (_muteWarnings)
|
|
return;
|
|
try
|
|
{
|
|
_uiThreadHandler.Post(() => App.Kp2a.ShowMessage(_ctx, warning, MessageSeverity.Warning));
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Kp2aLog.LogUnexpectedError(e);
|
|
//ignore, it's only a warning
|
|
}
|
|
|
|
}
|
|
|
|
private bool SeedValidate(IDictionary<string, string> entryFields, out string invalidCharacters)
|
|
{
|
|
return SeedGet(entryFields).ExtWithoutSpaces().ExtIsBase32(out invalidCharacters);
|
|
}
|
|
internal string SeedGet(IDictionary<string, string> entryFields)
|
|
{
|
|
return entryFields[SeedFieldName];
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Class to support custom extensions.
|
|
/// </summary>
|
|
internal static class Extensions
|
|
{
|
|
/// <summary>
|
|
/// Concatenates a space in front of the current string.
|
|
/// </summary>
|
|
/// <param name="Extension">Current string.</param>
|
|
/// <returns></returns>
|
|
internal static string ExtWithSpaceBefore(this string Extension)
|
|
{
|
|
return " " + Extension;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Concatenates the current string with space to the end.
|
|
/// </summary>
|
|
/// <param name="Extension">Current string.</param>
|
|
/// <returns></returns>
|
|
internal static string ExtWithSpaceAfter(this string Extension)
|
|
{
|
|
return Extension + " ";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Concatenates the current string with a bracket in front and to the end.
|
|
/// </summary>
|
|
/// <param name="Extension">Current string.</param>
|
|
/// <returns></returns>
|
|
internal static string ExtWithBrackets(this string Extension)
|
|
{
|
|
return ExtWith(Extension, '{', '}');
|
|
}
|
|
|
|
/// <summary>
|
|
/// Concatenates the current string with a parenthesis in front and to the end.
|
|
/// </summary>
|
|
/// <param name="Extension">Current string.</param>
|
|
/// <returns></returns>
|
|
internal static string ExtWithParenthesis(this string Extension)
|
|
{
|
|
return ExtWith(Extension, '(', ')');
|
|
}
|
|
|
|
/// <summary>
|
|
/// Concatenates the current string with a charater in front and another character to the end.
|
|
/// </summary>
|
|
/// <param name="Extension">Current string.</param>
|
|
/// <param name="Left">Front character.</param>
|
|
/// <param name="Right">End charater.</param>
|
|
/// <returns></returns>
|
|
internal static string ExtWith(this string Extension, char Left, char Right)
|
|
{
|
|
return Left + Extension + Right;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove all spaces from the current string.
|
|
/// </summary>
|
|
/// <param name="Extension">Current string.</param>
|
|
/// <returns></returns>
|
|
internal static string ExtWithoutSpaces(this string Extension)
|
|
{
|
|
return Extension.ExtWithout(" ");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove all specified characters from the current string.
|
|
/// </summary>
|
|
/// <param name="Extension">Current string.</param>
|
|
/// <param name="Chars">Characters to remove.</param>
|
|
/// <returns></returns>
|
|
internal static string ExtWithout(this string Extension, string Chars)
|
|
{
|
|
foreach (var Char in Chars)
|
|
{
|
|
Extension = Extension.Replace(Char.ToString(), "");
|
|
}
|
|
return Extension;
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Splits the string and returns specified substring.
|
|
/// </summary>
|
|
/// <param name="Extension">Current string.</param>
|
|
/// <param name="Index">Split index.</param>
|
|
/// <param name="Seperator">Split seperators.</param>
|
|
/// <returns></returns>
|
|
internal static string ExtSplit(this string Extension, int Index, char Seperator = ';')
|
|
{
|
|
if (Extension != string.Empty)
|
|
{
|
|
try
|
|
{
|
|
var Text = Extension;
|
|
if (Text.Contains(Seperator.ToString()))
|
|
{
|
|
return Text.Split(Seperator)[Index];
|
|
}
|
|
return Text;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
}
|
|
return string.Empty;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Makes sure the string provided as a Seed is Base32. Invalid characters are available as out string.
|
|
/// </summary>
|
|
/// <param name="Extension">Current string.</param>
|
|
/// <param name="InvalidChars">Invalid characters.</param>
|
|
/// <returns>Validity of the string's characters for Base32 format.</returns>
|
|
internal static bool ExtIsBase32(this string Extension, out string InvalidChars)
|
|
{
|
|
InvalidChars = null;
|
|
try
|
|
{
|
|
foreach (var CurrentChar in Extension)
|
|
{
|
|
var CurrentCharValue = Char.GetNumericValue(CurrentChar);
|
|
if (Char.IsLetter(CurrentChar))
|
|
{
|
|
continue;
|
|
}
|
|
if (Char.IsDigit(CurrentChar))
|
|
{
|
|
if ((CurrentCharValue > 1) && (CurrentCharValue < 8))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
InvalidChars = (InvalidChars + CurrentCharValue.ToString().ExtWithSpaceBefore()).Trim();
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
InvalidChars = "(error)";
|
|
}
|
|
return InvalidChars == null;
|
|
}
|
|
}
|
|
} |