diff --git a/src/keepass2android/ChallengeInfo.cs b/src/keepass2android/ChallengeInfo.cs
index f3cfa601..2fdb1408 100644
--- a/src/keepass2android/ChallengeInfo.cs
+++ b/src/keepass2android/ChallengeInfo.cs
@@ -31,6 +31,8 @@ namespace KeeChallenge
{
public class ChallengeInfo
{
+ private bool m_LT64;
+
public byte[] EncryptedSecret {
get;
private set;
@@ -51,16 +53,24 @@ namespace KeeChallenge
private set;
}
- private ChallengeInfo()
+ public bool LT64
{
+ get { return m_LT64; }
+ private set { m_LT64 = value; }
}
- public ChallengeInfo(byte[] encryptedSecret, byte[] iv, byte[] challenge, byte[] verification)
+ private ChallengeInfo()
+ {
+ LT64 = false;
+ }
+
+ public ChallengeInfo(byte[] encryptedSecret, byte[] iv, byte[] challenge, byte[] verification, bool lt64)
{
EncryptedSecret = encryptedSecret;
IV = iv;
Challenge = challenge;
Verification = verification;
+ LT64 = lt64;
}
public static ChallengeInfo Load(IOConnectionInfo ioc)
@@ -125,6 +135,10 @@ namespace KeeChallenge
xml.Read();
Verification = Convert.FromBase64String(xml.Value.Trim());
break;
+ case "lt64":
+ xml.Read();
+ if (!bool.TryParse(xml.Value.Trim(), out m_LT64)) throw new Exception("Unable to parse LT64 flag");
+ break;
}
}
}
@@ -184,6 +198,7 @@ namespace KeeChallenge
xml.WriteElementString("challenge", Convert.ToBase64String(Challenge));
xml.WriteElementString("verification", Convert.ToBase64String(Verification));
+ xml.WriteElementString("lt64", LT64.ToString());
xml.WriteEndElement();
xml.WriteEndDocument();
diff --git a/src/keepass2android/KeeChallenge.cs b/src/keepass2android/KeeChallenge.cs
index 8d90626c..381c068b 100644
--- a/src/keepass2android/KeeChallenge.cs
+++ b/src/keepass2android/KeeChallenge.cs
@@ -46,19 +46,40 @@ namespace KeeChallenge
public const int responseLenBytes = 20;
public const int secretLenBytes = 20;
- private KeeChallengeProv()
+ //If variable length challenges are enabled, a 63 byte challenge is sent instead.
+ //See GenerateChallenge() and http://forum.yubico.com/viewtopic.php?f=16&t=1078
+ //This field is automatically set by calling GetSecret(). However, when creating
+ //a new database it will need to be set manually based on the user's yubikey settings
+ public bool LT64
{
- }
-
- private static byte[] GenerateChallenge()
- {
- CryptoRandom rand = CryptoRandom.Instance;
- return CryptoRandom.Instance.GetRandomBytes(challengeLenBytes);
+ get;
+ set;
}
- private static byte[] GenerateResponse(byte[] challenge, byte[] key)
+ private KeeChallengeProv()
+ {
+ LT64 = false;
+ }
+
+ private byte[] GenerateChallenge()
+ {
+ CryptoRandom rand = CryptoRandom.Instance;
+ byte[] chal = CryptoRandom.Instance.GetRandomBytes(challengeLenBytes);
+ if (LT64)
+ {
+ chal[challengeLenBytes - 2] = (byte)~chal[challengeLenBytes - 1];
+ }
+
+ return chal;
+ }
+
+ private byte[] GenerateResponse(byte[] challenge, byte[] key)
{
HMACSHA1 hmac = new HMACSHA1(key);
+
+ if (LT64)
+ challenge = challenge.Take(challengeLenBytes - 1).ToArray();
+
byte[] resp = hmac.ComputeHash(challenge);
hmac.Clear();
return resp;
@@ -71,7 +92,7 @@ namespace KeeChallenge
///
/// The un-encrypted secret
/// A fully populated ChallengeInfo object ready to be saved
- public static ChallengeInfo Encrypt(byte[] secret)
+ public ChallengeInfo Encrypt(byte[] secret)
{
//generate a random challenge for use next time
byte[] challenge = GenerateChallenge();
@@ -101,14 +122,14 @@ namespace KeeChallenge
msEncrypt.Close();
}
- ChallengeInfo inf = new ChallengeInfo (encrypted, IV, challenge, secretHash);
+ ChallengeInfo inf = new ChallengeInfo (encrypted, IV, challenge, secretHash, LT64);
sha.Clear();
return inf;
}
- private static bool DecryptSecret(byte[] yubiResp, ChallengeInfo inf, out byte[] secret)
+ private bool DecryptSecret(byte[] yubiResp, ChallengeInfo inf, out byte[] secret)
{
secret = new byte[keyLenBytes];
@@ -156,7 +177,7 @@ namespace KeeChallenge
/// This should be populated from the database.xml auxilliary file
/// The Yubikey's response to the issued challenge
/// The common secret, used as a composite key to encrypt a Keepass database
- public static byte[] GetSecret(ChallengeInfo inf, byte[] resp)
+ public byte[] GetSecret(ChallengeInfo inf, byte[] resp)
{
if (resp.Length != responseLenBytes)
return null;
@@ -165,6 +186,8 @@ namespace KeeChallenge
if (inf.Challenge == null ||
inf.Verification == null)
return null;
+
+ LT64 = inf.LT64;
byte[] secret;
diff --git a/src/keepass2android/PasswordActivity.cs b/src/keepass2android/PasswordActivity.cs
index f293ad9b..468847c1 100644
--- a/src/keepass2android/PasswordActivity.cs
+++ b/src/keepass2android/PasswordActivity.cs
@@ -129,6 +129,7 @@ namespace keepass2android
private OtpInfo _otpInfo;
private ChallengeInfo _chalInfo;
private byte[] _challengeSecret;
+ private KeeChallengeProv _challengeProv;
private readonly int[] _otpTextViewIds = new[] {Resource.Id.otp1, Resource.Id.otp2, Resource.Id.otp3, Resource.Id.otp4, Resource.Id.otp5, Resource.Id.otp6};
private const string OtpInfoKey = "OtpInfoKey";
private const string EnteredOtpsKey = "EnteredOtpsKey";
@@ -325,8 +326,9 @@ namespace keepass2android
{
try
{
+ _challengeProv = new KeeChallengeProv();
byte[] challengeResponse = data.GetByteArrayExtra("response");
- _challengeSecret = KeeChallengeProv.GetSecret(_chalInfo, challengeResponse);
+ _challengeSecret = _challengeProv.GetSecret(_chalInfo, challengeResponse);
Array.Clear(challengeResponse, 0, challengeResponse.Length);
}
catch (Exception e)
@@ -348,7 +350,7 @@ namespace keepass2android
//save aux file
try
{
- ChallengeInfo temp = KeeChallengeProv.Encrypt(_challengeSecret);
+ ChallengeInfo temp = _challengeProv.Encrypt(_challengeSecret);
IFileStorage fileStorage = App.Kp2a.GetOtpAuxFileStorage(_ioConnection);
IOConnectionInfo iocAux = fileStorage.GetFilePath(fileStorage.GetParentPath(_ioConnection),
fileStorage.GetFilenameWithoutPathAndExt(_ioConnection) + ".xml");