/// <summary> /// Remove spaces and in case of BASE32 correct some characters that are misread often /// </summary> /// <param name="seed"></param> /// <param name="encoding"></param> /// <returns></returns> private ProtectedString SanitizeSeed(ProtectedString seed, KPOTPEncoding encoding) { Dictionary <byte, byte> dReplace = new Dictionary <byte, byte>(); dReplace[(byte)'0'] = (byte)'O'; dReplace[(byte)'1'] = (byte)'L'; dReplace[(byte)'8'] = (byte)'B'; try { ProtectedString work = ProtectedString.EmptyEx; foreach (byte b in seed.ReadUtf8()) { if ((char)b == ' ') { SanitizeChanged = true; //Remember we changed something, important for .Equals continue; } if ((encoding == KPOTPEncoding.BASE32) && dReplace.ContainsKey(b)) { SanitizeChanged = true; //Remember we changed something, important for .Equals work += new ProtectedString(true, new byte[] { dReplace[b] }); } else { work += new ProtectedString(true, new byte[] { b }); } } return(work); } catch { return(ProtectedString.EmptyEx); } }
private void MigrateToKeePassOTP_Totp(bool bRemove, out int EntriesOverall, out int EntriesMigrated) { EntriesOverall = EntriesMigrated = 0; Dictionary <PwEntry, KPOTPEncoding> dEntries = new Dictionary <PwEntry, KPOTPEncoding>(); foreach (KeyValuePair <KPOTPEncoding, string> kvp in m_dTotpStrings) { List <PwEntry> lHelp = m_db.RootGroup.GetEntries(true).Where(x => x.Strings.Exists(kvp.Value)).ToList(); foreach (PwEntry pe in lHelp) { if (!dEntries.ContainsKey(pe)) { dEntries[pe] = kvp.Key; } } } EntriesOverall = dEntries.Count; if (dEntries.Count == 0) { return; } if (!OTPDAO.EnsureOTPSetupPossible(dEntries.Keys.First())) { return; } OTPDAO.OTPHandler_Base handler = OTPDAO.GetOTPHandler(dEntries.Keys.First()); InitLogger("KeePass -> KeePassOTP (TOTP)", dEntries.Count); try { foreach (KeyValuePair <PwEntry, KPOTPEncoding> kvp in dEntries) { IncreaseLogger(); KPOTPEncoding enc = kvp.Value; PwEntry pe = kvp.Key; var otp = OTPDAO.GetOTP(pe); otp.Encoding = enc; otp.OTPSeed = new ProtectedString(true, MigrateString(pe.Strings.ReadSafe(m_dTotpStrings[enc]))); string hash = pe.Strings.ReadSafe(TOTPHASH).ToLowerInvariant(); if (hash.Contains("sha-512")) { otp.Hash = KPOTPHash.SHA512; } else if (hash.Contains("sha-256")) { otp.Hash = KPOTPHash.SHA256; } else { otp.Hash = KPOTPHash.SHA1; } otp.Length = MigrateInt(pe.Strings.ReadSafe(TOTPLENGTH), 6); otp.TOTPTimestep = MigrateInt(pe.Strings.ReadSafe(TOTPPERIOD), 30); otp.HOTPCounter = MigrateInt(pe.Strings.ReadSafe(HOTP_COUNTER), 0); if (otp.Valid) { EntriesMigrated++; try { handler.IgnoreBuffer = true; OTPDAO.SaveOTP(otp, pe); } finally { handler.IgnoreBuffer = false; } if (bRemove) { pe.Strings.Remove(m_dTotpStrings[enc]); pe.Strings.Remove(TOTPHASH); pe.Strings.Remove(TOTPLENGTH); pe.Strings.Remove(TOTPPERIOD); } } else { PluginDebug.AddError("Migration of entry failed", "Uuid: " + pe.Uuid.ToHexString(), "OTP data: " + m_dHotpStrings[enc]); } } } finally { EndLogger(); } MigratePlaceholder(PLACEHOLDER_TOTP, Config.Placeholder); }
private void SetOTPAuthString(ProtectedString value) { m_key = null; m_seed = ProtectedString.EmptyEx; Issuer = PluginTranslation.PluginTranslate.PluginName; Label = string.Empty; //otpauth strings contain all parameters and the seed //we do NOT want to remove protection from the seed int idx = 0; int i = 0; int secretstart = -1; char[] c = value.ReadChars(); try { List <char> lSecret1 = new List <char>(("?secret=").ToCharArray()); List <char> lSecret2 = new List <char>(("&secret=").ToCharArray()); //Search for parameter 'secret' for (i = 0; i < c.Length; i++) { char check = char.ToLower(c[i]); if ((check != lSecret1[idx]) && (check != lSecret2[idx])) { idx = 0; continue; } idx++; if (idx == lSecret1.Count) { secretstart = i + 1; break; } } if (secretstart == -1) { return; } idx = 0; for (i = secretstart; i < c.Length; i++) { if (c[i] == '&') { break; } idx++; } } finally { MemUtil.ZeroArray(c); } ProtectedString seed = value.Remove(secretstart + idx, value.Length - secretstart - idx); seed = seed.Remove(0, secretstart); string s = value.Remove(secretstart, idx).ReadString(); Uri u = new Uri(s); var parameters = System.Web.HttpUtility.ParseQueryString(u.Query); if (parameters.Count < 1) { return; } List <string> lKeys = parameters.AllKeys.ToList(); string sLabelIssuer = u.AbsolutePath; if (!string.IsNullOrEmpty(sLabelIssuer)) { List <string> lIssuer = sLabelIssuer.Split(new string[] { ":", "%3a", "%3A" }, StringSplitOptions.None).ToList(); if (lIssuer.Count > 1) { Label = Decode(lIssuer[1]).Replace("/", string.Empty); Issuer = Decode(lIssuer[0]).Replace("/", string.Empty); } else if (lIssuer.Count == 1) { Label = Decode(lIssuer[0]).Replace("/", string.Empty); } } Type = u.Host.ToLowerInvariant() == "hotp" ? KPOTPType.HOTP : KPOTPType.TOTP; if (Type == KPOTPType.TOTP) { TOTPTimestep = MigrateInt(parameters.Get("period"), 30); } else { HOTPCounter = MigrateInt(parameters.Get("counter"), 0); } string hash = parameters.Get("algorithm"); if (!string.IsNullOrEmpty(hash)) { hash = hash.ToLowerInvariant(); } Hash = KPOTPHash.SHA1; if (hash == "sha256") { Hash = KPOTPHash.SHA256; } else if (hash == "sha512") { Hash = KPOTPHash.SHA512; } Length = MigrateInt(parameters.Get("digits"), 6); string encoding = parameters.Get("encoding"); if (!string.IsNullOrEmpty(encoding)) { encoding = encoding.ToLowerInvariant(); } Encoding = KPOTPEncoding.BASE32; if (encoding == "base64") { Encoding = KPOTPEncoding.BASE64; } else if (encoding == "hex") { Encoding = KPOTPEncoding.HEX; } else if (encoding == "utf8") { Encoding = KPOTPEncoding.UTF8; } string encoder = parameters.Get("encoder"); KPOTPType tType = Type; if (Enum.TryParse(encoder, true, out tType)) { Type = tType; } string sIssuerParameter = parameters.Get("issuer"); if (!string.IsNullOrEmpty(sIssuerParameter)) { Issuer = Decode(sIssuerParameter); } //Remove %3d / %3D at the end of the seed c = seed.ReadChars(); idx = c.Length - 3; i = 0; while (idx > 0) { if ((c[idx] == '%') && (c[idx + 1] == '3') && (char.ToLowerInvariant(c[idx + 2]) == 'd')) { i++; idx -= 3; } else { break; } } if (i > 0) { seed = seed.Remove(seed.Length - (i * 3), i * 3); } SetSeed(seed); }
private void MigrateToKeePassOTP_Hotp(bool bRemove, out int EntriesOverall, out int EntriesMigrated) { EntriesOverall = EntriesMigrated = 0; List <PwEntry> lEntries = m_db.RootGroup.GetEntries(true).Where(x => x.Strings.Exists(HOTP_COUNTER)).ToList(); EntriesOverall = lEntries.Count; if (lEntries.Count == 0) { return; } if (!OTPDAO.EnsureOTPSetupPossible(lEntries[0])) { return; } OTPDAO.OTPHandler_Base handler = OTPDAO.GetOTPHandler(lEntries[0]); InitLogger("KeePass -> KeePassOTP (HOTP)", lEntries.Count); try { foreach (PwEntry pe in lEntries) { IncreaseLogger(); KPOTPEncoding enc = KPOTPEncoding.BASE32; bool bFound = false; string seed = null; foreach (KeyValuePair <KPOTPEncoding, string> kvp in m_dHotpStrings) { if (pe.Strings.Exists(kvp.Value)) { enc = kvp.Key; seed = pe.Strings.ReadSafe(kvp.Value); bFound = true; break; } } if (!bFound) { PluginDebug.AddError("Migration of entry failed", "Uuid: " + pe.Uuid.ToHexString(), "OTP data: not defined"); continue; } var otp = OTPDAO.GetOTP(pe); otp.Type = KPOTPType.HOTP; otp.Encoding = enc; otp.OTPSeed = new ProtectedString(true, MigrateString(seed)); otp.HOTPCounter = MigrateInt(pe.Strings.ReadSafe(HOTP_COUNTER), 0); if (otp.Valid) { EntriesMigrated++; try { handler.IgnoreBuffer = true; OTPDAO.SaveOTP(otp, pe); } finally { handler.IgnoreBuffer = false; } if (bRemove) { pe.Strings.Remove(m_dHotpStrings[enc]); pe.Strings.Remove(HOTP_COUNTER); } } else { PluginDebug.AddError("Migration of entry failed", "Uuid: " + pe.Uuid.ToHexString(), "OTP data: " + m_dHotpStrings[enc]); } } } finally { EndLogger(); } MigratePlaceholder(PLACEHOLDER_HOTP, Config.Placeholder); }