Skip to content

Commit

Permalink
Implement Yandex support
Browse files Browse the repository at this point in the history
Closes #5
  • Loading branch information
Rookiestyle committed Jul 29, 2021
1 parent 5bc6297 commit 729b687
Show file tree
Hide file tree
Showing 18 changed files with 782 additions and 42 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Functionality provided
- Configurable hotkey to invoke Auto-Type OTP
- Column to display OTP or to indicate possible 2FA usage
- Compatibility with Google Authenticator, andOTP etc.
- Support TOTP and HOTP as well as Steam OTP
- Support TOTP and HOTP as well as Steam OTP and Yandex (Yandex.Key)
- Secure storage of OTP secrets
- Auto-Type or Copy OTP using the KeePass tray icon

Expand Down
4 changes: 2 additions & 2 deletions Translations/KeePassOTP.de.language.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
Increment the TranslationVersion every time the translation file is updated
Also update the version.info file
-->
<TranslationVersion>16</TranslationVersion>
<TranslationVersion>17</TranslationVersion>
<item>
<key>OTPCopyTrayNoEntries</key>
<value>KPOTP - Keine Einträge vorhanden</value>
Expand Down Expand Up @@ -83,7 +83,7 @@
</item>
<item>
<key>TimeCorrection</key>
<value>Zeitversatz ausgleichen - Nur für TOTP &amp;&amp; Steam</value>
<value>Zeitversatz ausgleichen - Nicht für HOTP</value>
</item>
<item>
<key>URL</key>
Expand Down
4 changes: 2 additions & 2 deletions Translations/KeePassOTP.fr.language.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
Increment the TranslationVersion every time the translation file is updated
Also update the version.info file
-->
<TranslationVersion>6</TranslationVersion>
<TranslationVersion>7</TranslationVersion>
<item>
<key>OTPCopyTrayNoEntries</key>
<value>KPOTP - Aucune entrée disponible</value>
Expand Down Expand Up @@ -83,7 +83,7 @@
</item>
<item>
<key>TimeCorrection</key>
<value>Correction de temps - seulement pour TOTP et Steam</value>
<value>Correction de temps - Pas pour HOTP</value>
</item>
<item>
<key>URL</key>
Expand Down
4 changes: 2 additions & 2 deletions Translations/KeePassOTP.pt.language.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
Increment the TranslationVersion every time the translation file is updated
Also update the version.info file
-->
<TranslationVersion>5</TranslationVersion>
<TranslationVersion>6</TranslationVersion>
<item>
<key>OTPCopyTrayNoEntries</key>
<value>KPOTP - Sem entradas disponíveis</value>
Expand Down Expand Up @@ -83,7 +83,7 @@
</item>
<item>
<key>TimeCorrection</key>
<value>Correção de tempo - TOTP &amp;&amp; Steam apenas</value>
<value>Correção de tempo - Não aplicável para HOTP</value>
</item>
<item>
<key>URL</key>
Expand Down
2 changes: 1 addition & 1 deletion Translations/KeePassOTP.template.language.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
</item>
<item>
<key>TimeCorrection</key>
<value>Time correction - TOTP &amp;&amp; Steam only</value>
<value>Time correction - not applicable for HOTP</value>
</item>
<item>
<key>URL</key>
Expand Down
4 changes: 2 additions & 2 deletions Translations/KeePassOTP.zh.language.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
Increment the TranslationVersion every time the translation file is updated
Also update the version.info file
-->
<TranslationVersion>3</TranslationVersion>
<TranslationVersion>4</TranslationVersion>
<item>
<key>OTPCopyTrayNoEntries</key>
<value>KPOTP - 没有可用的条目</value>
Expand Down Expand Up @@ -83,7 +83,7 @@
</item>
<item>
<key>TimeCorrection</key>
<value>时间校正 - TOTP &amp;&amp; 仅 Steam</value>
<value>时间校正 - 不适用于 HOTP</value>
</item>
<item>
<key>URL</key>
Expand Down
88 changes: 82 additions & 6 deletions src/KeePassOTP.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ public enum KPOTPType : int
{
HOTP = 0,
TOTP,
STEAM
STEAM,
YANDEX
}

public enum KPOTPHash : int
Expand Down Expand Up @@ -54,6 +55,13 @@ public KPOTPType Type

public KPOTPEncoding Encoding = KPOTPEncoding.BASE32;

private string m_YandexPin = string.Empty;
public string YandexPin
{
get { return m_YandexPin; }
set { m_YandexPin = value; }
}

public int m_length = 6;
public int Length
{
Expand Down Expand Up @@ -104,7 +112,20 @@ public ProtectedString OTPAuthString
set { SetOTPAuthString(value); }
}

public bool Valid { get { return (m_key != null) && !m_seed.IsEmpty; } }
public bool Valid
{
get
{
if (Type == KPOTPType.YANDEX)
{
if (string.IsNullOrEmpty(YandexPin)) return false;
int l;
if (!KeePassOTP.YandexPin.Verify(YandexPin, out l)) return false;
return !m_seed.IsEmpty && !string.IsNullOrEmpty(YandexPin);
}
else return (m_key != null) && !m_seed.IsEmpty;
}
}

public TimeSpan OTPTimeCorrection = TimeSpan.Zero;
private DateTime UtcNow { get { return DateTime.UtcNow - OTPTimeCorrection; } }
Expand Down Expand Up @@ -147,7 +168,18 @@ public string GetOTP(bool ShowNext, bool CheckTime)
//List of time correction data is filled asynchronously
//Call it synchronously as it is required now!
if (CheckTime && Type != KPOTPType.HOTP) GetTimeCorrection(m_url);
byte[] data = (Type == KPOTPType.HOTP) ? GetHOTPData(ShowNext) : GetTOTPData(ShowNext);

if (Type == KPOTPType.YANDEX)
{
return GetYandexData(ShowNext);
}
byte[] data;
switch (Type)
{
case KPOTPType.HOTP: data = GetHOTPData(ShowNext); break;
default: data = GetTOTPData(ShowNext); break;
}
if (data == null) return string.Empty;
byte[] hash = ComputeHash(data);
MemUtil.ZeroByteArray(data);
int offset = hash[hash.Length - 1] & 0xF;
Expand All @@ -158,8 +190,11 @@ public string GetOTP(bool ShowNext, bool CheckTime)
(hash[offset + 3] & 0xFF);

string result = string.Empty;
if (Type != KPOTPType.STEAM) result = (binary % (int)Math.Pow(10, Length)).ToString().PadLeft(Length, '0');
else result = GetSteamData(binary);
switch (Type)
{
case KPOTPType.STEAM: result = GetSteamString(binary); break;
default: result = (binary % (int)Math.Pow(10, Length)).ToString().PadLeft(Length, '0'); break;
}

return result + (string.IsNullOrEmpty(m_url) || m_timeCorrectionUrls.ContainsKey(m_url) ? string.Empty : "*");
}
Expand All @@ -174,6 +209,26 @@ public string ReadableOTP(string otp)
if (!bFinal) otp += "*";
return otp;
}
private string GetYandexData(bool showNext)
{
return GetYandexData(showNext, Ticks);
}
private string GetYandexData(bool showNext, long ticks)
{
YandexSecret ys;
YandexPin yp;
if (!YandexSecret.TryCreate(OTPSeed, out ys)) return null;
if (!KeePassOTP.YandexPin.TryCreate(YandexPin, out yp)) return null;
try
{
YaOtp yo = new YaOtp(ys, yp, () => showNext ? DateTime.UtcNow.AddSeconds(30) : DateTime.UtcNow);
return yo.ComputeOtp();
}
catch
{
return null;
}
}

private byte[] GetHOTPData(bool showNext)
{
Expand All @@ -191,7 +246,19 @@ private byte[] GetTOTPData(bool showNext)
private static readonly char[] aSteamChars = new char[] { '2', '3', '4', '5',
'6', '7', '8', '9', 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P',
'Q', 'R', 'T', 'V', 'W', 'X', 'Y' };
private string GetSteamData(int binary)
private string GetSteamString(int binary)
{
string result = string.Empty;
for (int i = 0; i < Length; i++)
{
result += aSteamChars[binary % aSteamChars.Length];
binary /= aSteamChars.Length;
}

return result;
}

private string GetYandexString(int binary)
{
string result = string.Empty;
for (int i = 0; i < Length; i++)
Expand Down Expand Up @@ -252,6 +319,8 @@ private ProtectedString GetOTPAuthString()
otpSuffix += "&issuer=" + Encode(Issuer, false);
if (Type == KPOTPType.STEAM)
otpSuffix += "&encoder=steam";
else if (Type == KPOTPType.YANDEX)
otpSuffix += "&yandexpin=" + Encode(YandexPin,false) + "&encoder=yandex";
return new ProtectedString(true, otpPrefix) + m_seed + new ProtectedString(true, otpSuffix);
}

Expand Down Expand Up @@ -348,6 +417,11 @@ private void SetOTPAuthString(ProtectedString value)
string sIssuerParameter = parameters.Get("issuer");
if (!string.IsNullOrEmpty(sIssuerParameter)) Issuer = Decode(sIssuerParameter);

if (Type == KPOTPType.YANDEX)
{
YandexPin = parameters.Get("yandexpin");
}

//Remove %3d / %3D at the end of the seed
c = seed.ReadChars();
idx = c.Length - 3;
Expand Down Expand Up @@ -440,6 +514,7 @@ private void SetSeed(ProtectedString value)
{
ProtectedString work = SanitizeSeed(value, Encoding);
m_seed = work;
if (Type == KPOTPType.YANDEX) return;
try
{
if (Encoding == KPOTPEncoding.BASE32)
Expand Down Expand Up @@ -611,6 +686,7 @@ public static bool Equals(KPOTP otp1, KPOTP otp2, string url, out bool OnlyCount
if (otp1.Length != otp2.Length) return false;
if ((otp1.Type == KPOTPType.TOTP) && (otp1.TOTPTimestep != otp2.TOTPTimestep)) return false;
if ((otp1.Type == KPOTPType.TOTP) && (otp1.TimeCorrectionUrlOwn != otp2.TimeCorrectionUrlOwn)) return false;
if (otp1.Type == KPOTPType.YANDEX && otp1.YandexPin != otp2.YandexPin) return false;
if (otp1.TimeCorrectionUrlOwn && (otp1.Type == KPOTPType.TOTP))
{
if (otp1.TimeCorrectionUrl != otp2.TimeCorrectionUrl) return false;
Expand Down
4 changes: 4 additions & 0 deletions src/KeePassOTP.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@
<Compile Include="Utilities\Tools_Main.cs" />
<Compile Include="Utilities\Tools_Options.cs" />
<Compile Include="Utilities\Tools_UI.cs" />
<Compile Include="Yandex\Base26Encoder.cs" />
<Compile Include="Yandex\YandexPin.cs" />
<Compile Include="Yandex\YandexSecret.cs" />
<Compile Include="Yandex\YaOtp.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\Resources.resx">
Expand Down
26 changes: 25 additions & 1 deletion src/KeePassOTPSetup.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 729b687

Please sign in to comment.