diff --git a/plgxcreate.cmd b/plgxcreate.cmd
index 67c256b..9e94d66 100644
--- a/plgxcreate.cmd
+++ b/plgxcreate.cmd
@@ -1,40 +1,11 @@
@echo off
-set plgxnet=%1
-set plgxkp=%2
-set plgxos=%3
-
-cls
cd %~dp0
for %%* in (.) do set CurrDirName=%%~nx*
echo Processing %CurrDirName%
-echo Deleting existing PlgX folder
-rmdir /s /q plgx
-
-echo Creating PlgX folder
-mkdir plgx
-
-echo Copying files
-xcopy src plgx /s /e /exclude:plgxexclude.txt > nul
-
-echo Compiling PlgX
-cd..
-cd _KeePass_Release
-KeePass.exe --plgx-create "%~dp0plgx" plgx-prereq-net:%plgxnet% -plgx-prereq-kp:%plgxkp% -plgx-prereq-os:%plgxos%
-cd ..
-cd %CurrDirName%
-
echo Copying PlgX to KeePass plugin folder
-copy plgx.plgx "..\_KeePass_Release\Plugins\%CurrDirName%.plgx"
+copy "src\bin\Release\%CurrDirName%.plgx" "..\_KeePass_Release\Plugins\%CurrDirName%.plgx"
echo Releasing PlgX
-move /y plgx.plgx "..\_Releases\%CurrDirName%.plgx"
-
-echo Cleaning up
-rmdir /s /q plgx
-
-echo Compiled with following minimum requirements:
-echo .NET = %plgxnet%
-echo KeePass = %plgxkp%
-echo OS = %plgxos%
\ No newline at end of file
+move /y "src\bin\Release\%CurrDirName%.plgx" "..\_Releases\%CurrDirName%.plgx"
diff --git a/proto/GoogleAuthenticator_Import.proto b/proto/GoogleAuthenticator_Import.proto
new file mode 100644
index 0000000..1a08100
--- /dev/null
+++ b/proto/GoogleAuthenticator_Import.proto
@@ -0,0 +1,36 @@
+syntax = "proto3";
+package KeePassOTP;
+
+message GoogleAuthenticatorImport {
+ enum Algorithm {
+ ALGORITHM_UNSPECIFIED = 0;
+ ALGORITHM_SHA1 = 1;
+ ALGORITHM_SHA256 = 2;
+ ALGORITHM_SHA512 = 3;
+ ALGORITHM_MD5 = 4;
+ }
+ enum DigitCount {
+ DIGIT_COUNT_UNSPECIFIED = 0;
+ DIGIT_COUNT_SIX = 1;
+ DIGIT_COUNT_EIGHT = 2;
+ }
+ enum OtpType {
+ OTP_TYPE_UNSPECIFIED = 0;
+ OTP_TYPE_HOTP = 1;
+ OTP_TYPE_TOTP = 2;
+ }
+ message OtpParameters {
+ bytes secret = 1;
+ string name = 2;
+ string issuer = 3;
+ Algorithm algorithm = 4;
+ DigitCount digits = 5;
+ OtpType type = 6;
+ int64 counter = 7;
+ }
+ repeated OtpParameters otp_parameters = 1;
+ int32 version = 2;
+ int32 batch_size = 3;
+ int32 batch_index = 4;
+ int32 batch_id = 5;
+}
\ No newline at end of file
diff --git a/src/GoogleAuthenticatorImport/GoogleAuthenticatorImport.cs b/src/GoogleAuthenticatorImport/GoogleAuthenticatorImport.cs
new file mode 100644
index 0000000..d38f9d7
--- /dev/null
+++ b/src/GoogleAuthenticatorImport/GoogleAuthenticatorImport.cs
@@ -0,0 +1,127 @@
+//
+// This file was generated by a tool; you should avoid making direct changes.
+// Consider using 'partial classes' to extend these types
+// Input: my.proto
+//
+
+#region Designer generated code
+#pragma warning disable 0612, 0618, 1591, 3021
+namespace KeePassOTP
+{
+
+ [global::ProtoBuf.ProtoContract()]
+ public partial class GoogleAuthenticatorImport : global::ProtoBuf.IExtensible
+ {
+ private global::ProtoBuf.IExtension __pbn__extensionData;
+ global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)
+ {
+ return global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing);
+ }
+ public GoogleAuthenticatorImport()
+ {
+ otp_parameters = new global::System.Collections.Generic.List();
+ OnConstructor();
+ }
+
+ partial void OnConstructor();
+
+ [global::ProtoBuf.ProtoMember(1)]
+ public global::System.Collections.Generic.List otp_parameters { get; set; }
+
+ [global::ProtoBuf.ProtoMember(2, Name = @"version")]
+ public int Version { get; set; }
+
+ [global::ProtoBuf.ProtoMember(3, Name = @"batch_size")]
+ public int BatchSize { get; set; }
+
+ [global::ProtoBuf.ProtoMember(4, Name = @"batch_index")]
+ public int BatchIndex { get; set; }
+
+ [global::ProtoBuf.ProtoMember(5, Name = @"batch_id")]
+ public int BatchId { get; set; }
+
+ [global::ProtoBuf.ProtoContract()]
+ public partial class OtpParameters : global::ProtoBuf.IExtensible
+ {
+ private global::ProtoBuf.IExtension __pbn__extensionData;
+ global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)
+ {
+ return global::ProtoBuf.Extensible.GetExtensionObject(ref __pbn__extensionData, createIfMissing);
+ }
+ public OtpParameters()
+ {
+ Name = "";
+ Issuer = "";
+ OnConstructor();
+ }
+
+ partial void OnConstructor();
+
+ [global::ProtoBuf.ProtoMember(1, Name = @"secret")]
+ public byte[] Secret { get; set; }
+
+ [global::ProtoBuf.ProtoMember(2, Name = @"name")]
+ [global::System.ComponentModel.DefaultValue("")]
+ public string Name { get; set; }
+
+ [global::ProtoBuf.ProtoMember(3, Name = @"issuer")]
+ [global::System.ComponentModel.DefaultValue("")]
+ public string Issuer { get; set; }
+
+ [global::ProtoBuf.ProtoMember(4, Name = @"algorithm")]
+ public GoogleAuthenticatorImport.Algorithm Algorithm { get; set; }
+
+ [global::ProtoBuf.ProtoMember(5, Name = @"digits")]
+ public GoogleAuthenticatorImport.DigitCount Digits { get; set; }
+
+ [global::ProtoBuf.ProtoMember(6, Name = @"type")]
+ public GoogleAuthenticatorImport.OtpType Type { get; set; }
+
+ [global::ProtoBuf.ProtoMember(7, Name = @"counter")]
+ public long Counter { get; set; }
+
+ }
+
+ [global::ProtoBuf.ProtoContract()]
+ public enum Algorithm
+ {
+ [global::ProtoBuf.ProtoEnum(Name = @"ALGORITHM_UNSPECIFIED")]
+ AlgorithmUnspecified = 0,
+ [global::ProtoBuf.ProtoEnum(Name = @"ALGORITHM_SHA1")]
+ AlgorithmSha1 = 1,
+ [global::ProtoBuf.ProtoEnum(Name = @"ALGORITHM_SHA256")]
+ AlgorithmSha256 = 2,
+ [global::ProtoBuf.ProtoEnum(Name = @"ALGORITHM_SHA512")]
+ AlgorithmSha512 = 3,
+ [global::ProtoBuf.ProtoEnum(Name = @"ALGORITHM_MD5")]
+ AlgorithmMd5 = 4,
+ }
+
+ [global::ProtoBuf.ProtoContract()]
+ public enum DigitCount
+ {
+ [global::ProtoBuf.ProtoEnum(Name = @"DIGIT_COUNT_UNSPECIFIED")]
+ DigitCountUnspecified = 0,
+ [global::ProtoBuf.ProtoEnum(Name = @"DIGIT_COUNT_SIX")]
+ DigitCountSix = 1,
+ [global::ProtoBuf.ProtoEnum(Name = @"DIGIT_COUNT_EIGHT")]
+ DigitCountEight = 2,
+ }
+
+ [global::ProtoBuf.ProtoContract()]
+ public enum OtpType
+ {
+ [global::ProtoBuf.ProtoEnum(Name = @"OTP_TYPE_UNSPECIFIED")]
+ OtpTypeUnspecified = 0,
+ [global::ProtoBuf.ProtoEnum(Name = @"OTP_TYPE_HOTP")]
+ OtpTypeHotp = 1,
+ [global::ProtoBuf.ProtoEnum(Name = @"OTP_TYPE_TOTP")]
+ OtpTypeTotp = 2,
+ }
+
+ }
+
+}
+
+#pragma warning restore 0612, 0618, 1591, 3021
+#endregion
diff --git a/src/KeePassOTP.csproj b/src/KeePassOTP.csproj
index 9144909..eb37b59 100644
--- a/src/KeePassOTP.csproj
+++ b/src/KeePassOTP.csproj
@@ -1,14 +1,9 @@
-
+
5
-
- 2.42
-
-
-
Debug
AnyCPU
@@ -19,7 +14,7 @@
Properties
KeePassOTP
KeePassOTP
- v4.5
+ v4.6.1
512
@@ -40,12 +35,21 @@
none
true
bin\Release\
-
-
prompt
4
false
+
+ true
+
+
+
+
+ 2.42
+ $(TargetFrameworkVersion.Replace('v', ''))
+
+
+
@@ -62,6 +66,7 @@
+
@@ -117,17 +122,47 @@
{10938016-dee2-4a25-9a5a-8fd3444379ca}
KeePass
+
+ bin\Release\protobuf-net.dll
+ bin\Release\protobuf-net.Core.dll
+ bin\Release\System.Buffers.dll
+ bin\Release\System.Collections.Immutable.dll
+ bin\Release\System.Memory.dll
+ bin\Release\System.Numerics.Vectors.dll
+ bin\Release\System.Runtime.CompilerServices.Unsafe.dll
+ bin\Release\System.ServiceModel.Primitives.dll
+
+
+ 1.0.0
+
+
+
+ 3.0.52
+
+
+
+
+
+
-
+
+
\ No newline at end of file
diff --git a/src/KeePassOTPSetup.cs b/src/KeePassOTPSetup.cs
index 13f1703..0706b09 100644
--- a/src/KeePassOTPSetup.cs
+++ b/src/KeePassOTPSetup.cs
@@ -3,6 +3,7 @@
using System.Windows.Forms;
using KeePassLib.Security;
using KeePassLib.Utility;
+using PluginTools;
using PluginTranslation;
namespace KeePassOTP
@@ -111,6 +112,24 @@ private void UpdatePreview()
OTP.OTPAuthString = new ProtectedString(true, tbOTPSeed.Text);
InitSettings(true);
}
+ else if (tbOTPSeed.Text.ToLowerInvariant().StartsWith("otpauth-migration://"))
+ {
+ int iCount;
+ ProtectedString psGoogleAuth = PSConvert.ParseGoogleAuthExport(tbOTPSeed.Text, out iCount);
+ if ((iCount == 1) && (psGoogleAuth.Length > 0))
+ {
+ //tbOTPSeed.Text = psGoogleAuth.ReadString();
+ OTP.OTPAuthString = psGoogleAuth;
+ InitSettings(true);
+ return;
+ }
+ tbOTPSeed.Text = string.Empty;
+
+ if (iCount > 1) Tools.ShowError(string.Format(PluginTranslate.ErrorGoogleAuthImportCount, iCount.ToString()));
+ else Tools.ShowError(PluginTranslate.ErrorGoogleAuthImport);
+ return;
+ }
+
else OTP.OTPSeed = new ProtectedString(true, tbOTPSeed.Text);
if (cbOTPFormat.SelectedIndex == 0) OTP.Encoding = KPOTPEncoding.BASE32;
@@ -254,7 +273,18 @@ private void pbQR_DragDrop(object sender, DragEventArgs e)
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
var f = e.Data.GetData(DataFormats.FileDrop) as string[];
- if (f != null) otp = ParseFromImageFile(f[0]);
+ if (f != null)
+ {
+ otp = ParseFromImageFile(f[0]);
+ if (otp.ReadString().ToLowerInvariant().StartsWith("otpauth-migration://"))
+ {
+ int iOTPCount = 0;
+ try { otp = PSConvert.ParseGoogleAuthExport(otp.ReadString(), out iOTPCount); }
+ catch { }
+ if (iOTPCount > 1) Tools.ShowError(string.Format(PluginTranslate.ErrorGoogleAuthImportCount, iOTPCount.ToString()));
+ else if (iOTPCount == 0) Tools.ShowError(PluginTranslate.ErrorGoogleAuthImport);
+ }
+ }
}
if (!IsValidOtpAuth(otp))
{
diff --git a/src/PluginTranslation.cs b/src/PluginTranslation.cs
index 374aa62..cece2d2 100644
--- a/src/PluginTranslation.cs
+++ b/src/PluginTranslation.cs
@@ -123,6 +123,11 @@ Open it anytime to check the previous content.
Restoring the data needs to be done manually.";
public static readonly string Placeholder = @"Placeholder:";
public static readonly string PlaceholderAutoSubmit = @"{0} + Enter";
+ public static readonly string ErrorGoogleAuthImport = @"Error parsing data";
+ public static readonly string ErrorGoogleAuthImportCount = @"Error parsing data
+
+Expected amount of OTP entries: 1
+Found amount of OTP entries: {0}";
#endregion
#region NO changes in this area
diff --git a/src/Properties/AssemblyInfo.cs b/src/Properties/AssemblyInfo.cs
index 86dbece..d492bf2 100644
--- a/src/Properties/AssemblyInfo.cs
+++ b/src/Properties/AssemblyInfo.cs
@@ -30,5 +30,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("0.16.1")]
-[assembly: AssemblyFileVersion("0.16.1")]
+[assembly: AssemblyVersion("0.17")]
+[assembly: AssemblyFileVersion("0.17")]
\ No newline at end of file
diff --git a/src/Resources/Resources.resx b/src/Resources/Resources.resx
index 18684d6..c3eb91e 100644
--- a/src/Resources/Resources.resx
+++ b/src/Resources/Resources.resx
@@ -112,12 +112,12 @@
2.0
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
+
KeePassOTP_setup.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
diff --git a/src/Util.cs b/src/Util.cs
index 1343dfe..8e8f579 100644
--- a/src/Util.cs
+++ b/src/Util.cs
@@ -266,6 +266,148 @@ internal static byte[] HexStringToByteArray(ProtectedString s)
MemUtil.ZeroArray(strHex);
return pb;
}
+
+ internal static ProtectedString ParseGoogleAuthExport(string s, out int iOTPCount)
+ {
+ iOTPCount = 0;
+ ProtectedString psResult = ProtectedString.Empty;
+ try
+ {
+ var u = new Uri(s);
+ var param = System.Web.HttpUtility.ParseQueryString(u.Query);
+ var b = Convert.FromBase64String(param["data"]);
+ GoogleAuthenticatorImport gi = DeserializeGoogleAuthMigrationData(b);
+
+ iOTPCount = gi.otp_parameters.Count;
+ if (iOTPCount != 1) throw new ArgumentException("Expected exactly one OTP object, found: " + iOTPCount.ToString());
+ var gAuthData = gi.otp_parameters[0];
+ KPOTP otp = new KPOTP();
+ switch (gAuthData.Algorithm)
+ {
+ case GoogleAuthenticatorImport.Algorithm.AlgorithmSha256:
+ otp.Hash = KPOTPHash.SHA256;
+ break;
+ case GoogleAuthenticatorImport.Algorithm.AlgorithmSha512:
+ otp.Hash = KPOTPHash.SHA512;
+ break;
+ default:
+ otp.Hash = KPOTPHash.SHA1;
+ break;
+ }
+
+ switch (gAuthData.Type)
+ {
+ case GoogleAuthenticatorImport.OtpType.OtpTypeHotp:
+ otp.Type = KPOTPType.HOTP;
+ otp.HOTPCounter = (int)gAuthData.Counter;
+ break;
+ default:
+ otp.Type = KPOTPType.TOTP;
+ break;
+ }
+
+ switch (gAuthData.Digits)
+ {
+ case GoogleAuthenticatorImport.DigitCount.DigitCountEight:
+ otp.Length = 8;
+ break;
+ default:
+ otp.Length = 6;
+ break;
+ }
+
+ otp.Issuer = gAuthData.Issuer;
+ otp.Label = string.IsNullOrEmpty(gAuthData.Issuer) ? gAuthData.Name : gAuthData.Name.Remove(0, gAuthData.Issuer.Length + 1);
+ otp.Encoding = KPOTPEncoding.BASE32;
+
+ byte[] bSeed = ConvertBase64ToBase32(gAuthData.Secret);
+ otp.OTPSeed = new ProtectedString(true, bSeed);
+ psResult = otp.OTPAuthString;
+ }
+ catch { }
+ return psResult;
+ }
+
+ private static GoogleAuthenticatorImport DeserializeGoogleAuthMigrationData(byte[] b)
+ {
+ GoogleAuthenticatorImport gi = new GoogleAuthenticatorImport();
+ using (var ms = new System.IO.MemoryStream())
+ {
+ ms.Write(b, 0, b.Length);
+ ms.Position = 0;
+ gi = ProtoBuf.Serializer.Deserialize(ms);
+ }
+ return gi;
+ }
+
+ private const string Base32Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+ private const int InByteSize = 8;
+ private const int OutByteSize = 5;
+ internal static byte[] ConvertBase64ToBase32(byte[] bytes)
+ {
+ List lBytes = new List();
+ if ((bytes == null) || (bytes.Length == 0)) return lBytes.ToArray();
+
+ int iCurrentBytePosition = 0;
+
+ // Offset inside a single byte that points to (from left to right)
+ // 0 - highest bit, 7 - lowest bit
+ int bytesSubPosition = 0;
+ //byte to look up in the base32 dictionary
+ byte outputBase32Byte = 0;
+ //The number of bits filled in the current output byte
+ int iCurrentOutputPosition = 0;
+
+ // Iterate through input buffer until we reach past the end of it
+ while (iCurrentBytePosition < bytes.Length)
+ {
+ // Calculate the number of bits we can extract out of current input byte to fill missing bits in the output byte
+ int bitsAvailableInByte = Math.Min(InByteSize - bytesSubPosition, OutByteSize - iCurrentOutputPosition);
+ // Make space in the output byte
+ outputBase32Byte <<= bitsAvailableInByte;
+ // Extract the part of the input byte and move it to the output byte
+ outputBase32Byte |= (byte)(bytes[iCurrentBytePosition] >> (InByteSize - (bytesSubPosition + bitsAvailableInByte)));
+ // Update current sub-byte position
+ bytesSubPosition += bitsAvailableInByte;
+
+ // Check overflow
+ if (bytesSubPosition >= InByteSize)
+ {
+ // Move to the next byte
+ iCurrentBytePosition++;
+ bytesSubPosition = 0;
+ }
+
+ // Update current base32 byte completion
+ iCurrentOutputPosition += bitsAvailableInByte;
+ // Check overflow or end of input array
+ if (iCurrentOutputPosition >= OutByteSize)
+ {
+ // Drop the overflow bits
+ outputBase32Byte &= 0x1F; // 0x1F = 00011111 in binary
+ // Add current Base32 byte and convert it to character
+ lBytes.Add((byte)Base32Alphabet[outputBase32Byte]);
+ // Move to the next byte
+ iCurrentOutputPosition = 0;
+ }
+ }
+
+ // Check if we have a remainder
+ if (iCurrentOutputPosition > 0)
+ {
+ // Move to the right bits
+ outputBase32Byte <<= (OutByteSize - iCurrentOutputPosition);
+
+ // Drop the overflow bits
+ outputBase32Byte &= 0x1F; // 0x1F = 00011111 in binary
+
+ // Add current Base32 byte and convert it to character
+ //builder.Append(Base32Alphabet[outputBase32Byte]);
+ lBytes.Add((byte)Base32Alphabet[outputBase32Byte]);
+ }
+
+ return lBytes.ToArray();
+ }
}
public static class EventHelper
diff --git a/version.info b/version.info
index 9f858f6..fc42afd 100644
--- a/version.info
+++ b/version.info
@@ -1,5 +1,5 @@
:
-KeePassOTP:0.16.1
+KeePassOTP:0.17
KeePassOTP!de:10
KeePassOTP!fr:3
: