+ OtpKeyProvider plugin (unmodified, not yet compiling)

This commit is contained in:
Philipp Crocoll 2013-11-15 06:31:45 +01:00
parent b68ef33515
commit 59eace5834
4 changed files with 811 additions and 0 deletions

View File

@ -0,0 +1,189 @@
/*
OtpKeyProv Plugin
Copyright (C) 2011-2012 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using KeePassLib.Utility;
namespace OtpKeyProv
{
public static class EncodingUtil
{
private const string FmtHex = "Hex";
private const string FmtBase64 = "Base64";
private const string FmtBase32 = "Base32";
private const string FmtUtf8 = "UTF-8";
private const string FmtDec = "Dec";
public static readonly string[] Formats = new string[]{
FmtHex, FmtBase64, FmtBase32, FmtUtf8, FmtDec
};
public static OtpDataFmt? GetOtpDataFormat(ComboBox cmb)
{
string strFmt = (cmb.SelectedItem as string);
if(strFmt == null) return null; // No assert
if(strFmt == FmtHex) return OtpDataFmt.Hex;
if(strFmt == FmtBase64) return OtpDataFmt.Base64;
if(strFmt == FmtBase32) return OtpDataFmt.Base32;
if(strFmt == FmtUtf8) return OtpDataFmt.Utf8;
if(strFmt == FmtDec) return OtpDataFmt.Dec;
return null;
}
public static byte[] ParseKey(string strKey, OtpDataFmt fmt)
{
if(strKey == null) { Debug.Assert(false); return null; }
strKey = strKey.Trim();
if(strKey.Length == 0) return null; // No assert
if(fmt == OtpDataFmt.Hex)
{
strKey = strKey.Replace(" ", string.Empty);
strKey = strKey.Replace("\t", string.Empty);
strKey = strKey.Replace("\r", string.Empty);
strKey = strKey.Replace("\n", string.Empty);
if((strKey.Length % 2) == 1) strKey = "0" + strKey;
return MemUtil.HexStringToByteArray(strKey);
}
else if(fmt == OtpDataFmt.Base64)
{
try { return Convert.FromBase64String(strKey); }
catch(Exception) { }
}
else if(fmt == OtpDataFmt.Base32)
return ParseBase32(strKey);
else if(fmt == OtpDataFmt.Utf8)
{
try { return StrUtil.Utf8.GetBytes(strKey); }
catch(Exception) { }
}
else if(fmt == OtpDataFmt.Dec)
{
ulong u;
if(ulong.TryParse(strKey, out u))
{
byte[] pb = MemUtil.UInt64ToBytes(u);
Array.Reverse(pb); // Little endian -> big endian
return pb;
}
}
return null;
}
public static ulong? ParseCounter(string strCounter, OtpDataFmt fmt)
{
byte[] pb = ParseKey(strCounter, fmt);
if(pb == null) return null;
if(pb.Length > 8) return null;
Array.Reverse(pb); // Big endian -> little endian
byte[] pb8 = new byte[8];
Array.Copy(pb, 0, pb8, 0, pb.Length);
return MemUtil.BytesToUInt64(pb8); // Little endian
}
private const string Base32Alph = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
/// <summary>
/// Decode base32 strings according to RFC 4648.
/// </summary>
private static byte[] ParseBase32(string str)
{
if((str == null) || ((str.Length % 8) != 0)) return null;
List<byte> l = new List<byte>();
for(int i = 0; i < str.Length; i += 8)
{
ulong u = 0;
int nBits = 0;
for(int j = 0; j < 8; ++j)
{
char ch = char.ToUpper(str[i + j]);
if(ch == '=') break;
int iValue = Base32Alph.IndexOf(ch);
if(iValue < 0) return null;
u <<= 5;
u += (ulong)iValue;
nBits += 5;
}
int nBitsTooMany = (nBits % 8);
u >>= nBitsTooMany;
nBits -= nBitsTooMany;
Debug.Assert((nBits % 8) == 0);
int idxNewBytes = l.Count;
while(nBits > 0)
{
l.Add((byte)(u & 0xFF));
u >>= 8;
nBits -= 8;
}
l.Reverse(idxNewBytes, l.Count - idxNewBytes);
}
return l.ToArray();
}
internal static void SelfTest()
{
#if DEBUG
byte[] pbRes = ParseBase32("MY======");
byte[] pbExp = Encoding.ASCII.GetBytes("f");
if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-1");
pbRes = ParseBase32("MZXQ====");
pbExp = Encoding.ASCII.GetBytes("fo");
if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-2");
pbRes = ParseBase32("MZXW6===");
pbExp = Encoding.ASCII.GetBytes("foo");
if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-3");
pbRes = ParseBase32("MZXW6YQ=");
pbExp = Encoding.ASCII.GetBytes("foob");
if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-4");
pbRes = ParseBase32("MZXW6YTB");
pbExp = Encoding.ASCII.GetBytes("fooba");
if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-5");
pbRes = ParseBase32("MZXW6YTBOI======");
pbExp = Encoding.ASCII.GetBytes("foobar");
if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-6");
pbRes = ParseBase32("JNSXSIDQOJXXM2LEMVZCAYTBONSWIIDPNYQG63TFFV2GS3LFEBYGC43TO5XXEZDTFY======");
pbExp = Encoding.ASCII.GetBytes("Key provider based on one-time passwords.");
if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-7");
#endif
}
}
}

View File

@ -0,0 +1,132 @@
/*
OtpKeyProv Plugin
Copyright (C) 2011-2012 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using OtpKeyProv.Forms;
using KeePass.UI;
using KeePassLib.Keys;
using KeePassLib.Serialization;
using KeePassLib.Utility;
namespace OtpKeyProv
{
public sealed class OathHotpKeyProv : KeyProvider
{
private const string AuxFileExt = ".otp.xml";
private const string ProvType = "OATH HOTP / RFC 4226";
private const string ProvVersion = "2.0"; // File version, not OtpKeyProv version
public override string Name
{
get { return "One-Time Passwords (OATH HOTP)"; }
}
public override bool SecureDesktopCompatible
{
get { return true; }
}
public override byte[] GetKey(KeyProviderQueryContext ctx)
{
try
{
if(ctx.CreatingNewKey) return Create(ctx);
return Open(ctx);
}
catch(Exception ex) { MessageService.ShowWarning(ex.Message); }
return null;
}
private static IOConnectionInfo GetAuxFileIoc(KeyProviderQueryContext ctx)
{
IOConnectionInfo ioc = ctx.DatabaseIOInfo.CloneDeep();
ioc.Path = UrlUtil.StripExtension(ioc.Path) + AuxFileExt;
return ioc;
}
private static byte[] Create(KeyProviderQueryContext ctx)
{
IOConnectionInfo iocPrev = GetAuxFileIoc(ctx);
OtpInfo otpInfo = OtpInfo.Load(iocPrev);
if(otpInfo == null) otpInfo = new OtpInfo();
OtpKeyCreationForm dlg = new OtpKeyCreationForm();
dlg.InitEx(otpInfo, ctx);
if(UIUtil.ShowDialogAndDestroy(dlg) != DialogResult.OK)
return null;
if(!CreateAuxFile(otpInfo, ctx)) return null;
return otpInfo.Secret;
}
private static byte[] Open(KeyProviderQueryContext ctx)
{
IOConnectionInfo ioc = GetAuxFileIoc(ctx);
OtpInfo otpInfo = OtpInfo.Load(ioc);
if(otpInfo == null)
{
MessageService.ShowWarning("Failed to load auxiliary OTP info file:",
ioc.GetDisplayName());
return null;
}
if(otpInfo.Type != ProvType)
{
MessageService.ShowWarning("Unknown OTP generator type!");
return null;
}
OtpKeyPromptForm dlg = new OtpKeyPromptForm();
dlg.InitEx(otpInfo, ctx);
if(UIUtil.ShowDialogAndDestroy(dlg) != DialogResult.OK)
return null;
if(!CreateAuxFile(otpInfo, ctx)) return null;
return otpInfo.Secret;
}
private static bool CreateAuxFile(OtpInfo otpInfo,
KeyProviderQueryContext ctx)
{
otpInfo.Type = ProvType;
otpInfo.Version = ProvVersion;
otpInfo.Generator = OtpKeyProvExt.ProductName;
otpInfo.EncryptSecret();
IOConnectionInfo ioc = GetAuxFileIoc(ctx);
if(!OtpInfo.Save(ioc, otpInfo))
{
MessageService.ShowWarning("Failed to save auxiliary OTP info file:",
ioc.GetDisplayName());
return false;
}
return true;
}
}
}

View File

@ -0,0 +1,317 @@
/*
OtpKeyProv Plugin
Copyright (C) 2011-2012 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.ComponentModel;
using System.Security.Cryptography;
using System.Diagnostics;
using KeePassLib.Cryptography;
using KeePassLib.Keys;
using KeePassLib.Serialization;
using KeePassLib.Utility;
namespace OtpKeyProv
{
public sealed class OtpInfo
{
private string m_strType = string.Empty;
public string Type
{
get { return m_strType; }
set
{
if(value == null) throw new ArgumentNullException("value");
m_strType = value;
}
}
private string m_strVersion = string.Empty;
public string Version
{
get { return m_strVersion; }
set
{
if(value == null) throw new ArgumentNullException("value");
m_strVersion = value;
}
}
private string m_strGen = string.Empty;
public string Generator
{
get { return m_strGen; }
set
{
if(value == null) throw new ArgumentNullException("value");
m_strGen = value;
}
}
private byte[] m_pbSecret = null;
[XmlIgnore]
public byte[] Secret
{
get { return m_pbSecret; }
set { m_pbSecret = value; }
}
private string m_strEncSecret = string.Empty;
[DefaultValue("")]
public string EncryptedSecret // Deprecated, < v2.0
{
get { return m_strEncSecret; }
set
{
if(value == null) throw new ArgumentNullException("value");
m_strEncSecret = value;
}
}
private List<OtpEncryptedData> m_lSecrets = new List<OtpEncryptedData>();
[XmlArrayItem("EncryptedData")]
public List<OtpEncryptedData> EncryptedSecrets
{
get { return m_lSecrets; }
set
{
if(value == null) throw new ArgumentNullException("value");
m_lSecrets = value;
}
}
private string m_strEncIV = string.Empty;
[DefaultValue("")]
public string EncryptionIV // Deprecated, < v2.0
{
get { return m_strEncIV; }
set
{
if(value == null) throw new ArgumentNullException("value");
m_strEncIV = value;
}
}
private string m_strTrfKey = string.Empty;
[DefaultValue("")]
public string TransformationKey // Deprecated, < v2.0
{
get { return m_strTrfKey; }
set
{
if(value == null) throw new ArgumentNullException("value");
m_strTrfKey = value;
}
}
private const ulong DefaultTrfRounds = 12000;
private ulong m_uTrfRounds = DefaultTrfRounds;
[DefaultValue(typeof(ulong), "12000")]
public ulong TransformationRounds // Deprecated, < v2.0
{
get { return m_uTrfRounds; }
set { m_uTrfRounds = value; }
}
private ulong m_uCounter = 0;
public ulong Counter
{
get { return m_uCounter; }
set { m_uCounter = value; }
}
private uint m_uOtpLength = 8;
public uint OtpLength
{
get { return m_uOtpLength; }
set { m_uOtpLength = value; }
}
private uint m_uOtpsReq = 4;
public uint OtpsRequired
{
get { return m_uOtpsReq; }
set { m_uOtpsReq = value; }
}
private uint m_uLookAhead = 0;
public uint LookAheadCount
{
get { return m_uLookAhead; }
set { m_uLookAhead = value; }
}
public static OtpInfo Load(IOConnectionInfo ioc)
{
Stream sIn = null;
try
{
sIn = IOConnection.OpenRead(ioc);
XmlSerializer xs = new XmlSerializer(typeof(OtpInfo));
return (OtpInfo)xs.Deserialize(sIn);
}
catch(Exception) { }
finally
{
if(sIn != null) sIn.Close();
}
return null;
}
public static bool Save(IOConnectionInfo ioc, OtpInfo otpInfo)
{
Stream sOut = null;
try
{
sOut = IOConnection.OpenWrite(ioc);
XmlWriterSettings xws = new XmlWriterSettings();
xws.CloseOutput = true;
xws.Encoding = StrUtil.Utf8;
xws.Indent = true;
xws.IndentChars = "\t";
XmlWriter xw = XmlWriter.Create(sOut, xws);
XmlSerializer xs = new XmlSerializer(typeof(OtpInfo));
xs.Serialize(xw, otpInfo);
xw.Close();
return true;
}
catch(Exception) { Debug.Assert(false); }
finally
{
if(sOut != null) sOut.Close();
}
return false;
}
public void EncryptSecret()
{
if(m_pbSecret == null) throw new InvalidOperationException();
string[] vOtps = new string[m_uOtpsReq + m_uLookAhead];
ulong uCounter = m_uCounter;
for(int i = 0; i < vOtps.Length; ++i)
{
vOtps[i] = HmacOtp.Generate(m_pbSecret, uCounter,
m_uOtpLength, false, -1);
++uCounter;
}
m_strEncSecret = string.Empty;
m_strEncIV = string.Empty;
m_strTrfKey = string.Empty;
m_uTrfRounds = DefaultTrfRounds;
m_lSecrets.Clear();
for(int i = 0; i <= (int)m_uLookAhead; ++i)
m_lSecrets.Add(OtpUtil.EncryptSecret(m_pbSecret, vOtps, i,
(int)m_uOtpsReq));
}
}
public sealed class OtpEncryptedData
{
private string m_strCipherText = string.Empty;
[DefaultValue("")]
public string CipherText
{
get { return m_strCipherText; }
set
{
if(value == null) throw new ArgumentNullException("value");
m_strCipherText = value;
}
}
private string m_strIV = string.Empty;
[DefaultValue("")]
public string IV
{
get { return m_strIV; }
set
{
if(value == null) throw new ArgumentNullException("value");
m_strIV = value;
}
}
private string m_strTrfKey = string.Empty;
[DefaultValue("")]
public string TransformationKey
{
get { return m_strTrfKey; }
set
{
if(value == null) throw new ArgumentNullException("value");
m_strTrfKey = value;
}
}
private ulong m_uTrfRounds = 10000;
public ulong TransformationRounds
{
get { return m_uTrfRounds; }
set { m_uTrfRounds = value; }
}
private string m_strPlainHash = string.Empty;
[DefaultValue("")]
public string PlainTextHash
{
get { return m_strPlainHash; }
set
{
if(value == null) throw new ArgumentNullException("value");
m_strPlainHash = value;
}
}
private string m_strPlainHashTrfKey = string.Empty;
[DefaultValue("")]
public string PlainTextHashTransformationKey
{
get { return m_strPlainHashTrfKey; }
set
{
if(value == null) throw new ArgumentNullException("value");
m_strPlainHashTrfKey = value;
}
}
private ulong m_uHashTrfRounds = 10000;
public ulong PlainTextHashTransformationRounds
{
get { return m_uHashTrfRounds; }
set { m_uHashTrfRounds = value; }
}
}
}

View File

@ -0,0 +1,173 @@
/*
OtpKeyProv Plugin
Copyright (C) 2011-2012 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Security.Cryptography;
using System.Diagnostics;
using KeePassLib.Cryptography;
using KeePassLib.Cryptography.Cipher;
using KeePassLib.Keys;
using KeePassLib.Utility;
namespace OtpKeyProv
{
public enum OtpDataFmt
{
Hex = 0,
Base64 = 1,
Base32 = 4,
Utf8 = 2,
Dec = 3
}
public static class OtpUtil
{
public static byte[] KeyFromOtps(string[] vOtps, int iOtpsOffset,
int iOtpsCount, byte[] pbTrfKey32, ulong uTrfRounds)
{
StringBuilder sb = new StringBuilder();
for(int i = iOtpsOffset; i < (iOtpsOffset + iOtpsCount); ++i)
{
if(sb.Length > 0) sb.Append(':');
sb.Append(vOtps[i].Trim());
}
string strKey = sb.ToString();
byte[] pb = StrUtil.Utf8.GetBytes(strKey);
if(pb.Length == 0) return null;
byte[] pbKey = HashAndTransform(pb, pbTrfKey32, uTrfRounds);
if(pbKey == null) throw new InvalidOperationException();
return pbKey;
}
public static string EncryptData(byte[] pbData, byte[] pbKey32,
byte[] pbIV16)
{
byte[] pbIV8 = new byte[8];
Array.Copy(pbIV16, 0, pbIV8, 0, 8);
byte[] pbEnc = new byte[pbData.Length];
Array.Copy(pbData, pbEnc, pbData.Length);
Salsa20Cipher enc = new Salsa20Cipher(pbKey32, pbIV8);
enc.Encrypt(pbEnc, pbEnc.Length, true);
return ("s20://" + Convert.ToBase64String(pbEnc,
Base64FormattingOptions.None));
}
public static byte[] DecryptData(string strData, byte[] pbKey32,
byte[] pbIV16)
{
if(!strData.StartsWith("s20://")) return null;
string strEnc = strData.Substring(6);
byte[] pb = Convert.FromBase64String(strEnc);
byte[] pbIV8 = new byte[8];
Array.Copy(pbIV16, 0, pbIV8, 0, 8);
Salsa20Cipher dec = new Salsa20Cipher(pbKey32, pbIV8);
dec.Encrypt(pb, pb.Length, true);
return pb;
}
private static byte[] HashAndTransform(byte[] pbData, byte[] pbTrfKey32,
ulong uTrfRounds)
{
SHA256Managed sha256 = new SHA256Managed();
byte[] pbHash = sha256.ComputeHash(pbData);
sha256.Clear();
if(!CompositeKey.TransformKeyManaged(pbHash, pbTrfKey32, uTrfRounds))
return null;
sha256 = new SHA256Managed();
pbHash = sha256.ComputeHash(pbHash);
sha256.Clear();
return pbHash;
}
public static OtpEncryptedData EncryptSecret(byte[] pbSecret, string[] vOtps,
int iOtpsOffset, int iOtpsCount)
{
OtpEncryptedData d = new OtpEncryptedData();
CryptoRandom r = CryptoRandom.Instance;
byte[] pbIV16 = r.GetRandomBytes(16);
d.IV = Convert.ToBase64String(pbIV16, Base64FormattingOptions.None);
byte[] pbTrfKey32 = r.GetRandomBytes(32);
d.TransformationKey = Convert.ToBase64String(pbTrfKey32, Base64FormattingOptions.None);
byte[] pbKey32 = OtpUtil.KeyFromOtps(vOtps, iOtpsOffset, iOtpsCount,
pbTrfKey32, d.TransformationRounds);
d.CipherText = OtpUtil.EncryptData(pbSecret, pbKey32, pbIV16);
byte[] pbHashTrfKey32 = r.GetRandomBytes(32);
d.PlainTextHashTransformationKey = Convert.ToBase64String(pbHashTrfKey32,
Base64FormattingOptions.None);
byte[] pbHash = HashAndTransform(pbSecret, pbHashTrfKey32,
d.PlainTextHashTransformationRounds);
d.PlainTextHash = Convert.ToBase64String(pbHash, Base64FormattingOptions.None);
return d;
}
public static byte[] DecryptSecret(OtpEncryptedData d, string[] vOtps,
int iOtpsOffset, int iOtpsCount)
{
try { return DecryptSecretPriv(d, vOtps, iOtpsOffset, iOtpsCount); }
catch(Exception) { Debug.Assert(false); }
return null;
}
private static byte[] DecryptSecretPriv(OtpEncryptedData d, string[] vOtps,
int iOtpsOffset, int iOtpsCount)
{
if(d == null) throw new ArgumentNullException("d");
byte[] pbTrfKey32 = Convert.FromBase64String(d.TransformationKey);
byte[] pbKey32 = OtpUtil.KeyFromOtps(vOtps, iOtpsOffset, iOtpsCount,
pbTrfKey32, d.TransformationRounds);
byte[] pbIV = Convert.FromBase64String(d.IV);
byte[] pbSecret = OtpUtil.DecryptData(d.CipherText, pbKey32, pbIV);
byte[] pbHashTrfKey32 = Convert.FromBase64String(d.PlainTextHashTransformationKey);
byte[] pbHash = HashAndTransform(pbSecret, pbHashTrfKey32,
d.PlainTextHashTransformationRounds);
if(!MemUtil.ArraysEqual(pbHash, Convert.FromBase64String(d.PlainTextHash)))
return null;
return pbSecret;
}
}
}