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");