From ebe779a7834f8a3b0060d46bde16d972bf97e8eb Mon Sep 17 00:00:00 2001 From: Slavasil Date: Thu, 26 Mar 2026 14:38:54 +0300 Subject: [PATCH] add TOTP parameters to the file format --- .../PasswordStore/FileFormatConstants.cs | 5 ++ src/KeyKeeper/PasswordStore/PassStoreEntry.cs | 24 +++++---- .../PasswordStore/PassStoreEntryPassword.cs | 51 ++++++++++++++++++- 3 files changed, 69 insertions(+), 11 deletions(-) diff --git a/src/KeyKeeper/PasswordStore/FileFormatConstants.cs b/src/KeyKeeper/PasswordStore/FileFormatConstants.cs index 994cfb8..fc0dc28 100644 --- a/src/KeyKeeper/PasswordStore/FileFormatConstants.cs +++ b/src/KeyKeeper/PasswordStore/FileFormatConstants.cs @@ -25,6 +25,11 @@ static class FileFormatConstants public const byte LOGIN_FIELD_ACCOUNT_NUMBER_ID = 0x03; public const byte LOGIN_FIELD_NOTES_ID = 0x04; public const byte LOGIN_FIELD_CUSTOM_ID = 0xff; // пока не используется + public const byte TOTP_ABSENT = 0x00; + public const byte TOTP_PRESENT = 0x01; + public const byte TOTP_ALGO_SHA1 = 0x00; + public const byte TOTP_ALGO_SHA256 = 0x01; + public const byte TOTP_ALGO_SHA512 = 0x02; public const byte GROUP_TYPE_ROOT = 0x00; public const byte GROUP_TYPE_DEFAULT = 0x01; public const byte GROUP_TYPE_FAVOURITES = 0x02; diff --git a/src/KeyKeeper/PasswordStore/PassStoreEntry.cs b/src/KeyKeeper/PasswordStore/PassStoreEntry.cs index a67c5f7..a9e6a79 100644 --- a/src/KeyKeeper/PasswordStore/PassStoreEntry.cs +++ b/src/KeyKeeper/PasswordStore/PassStoreEntry.cs @@ -46,31 +46,37 @@ public abstract class PassStoreEntry BinaryReader rd = new(str); try { - rd.Read7BitEncodedInt(); + int entryLength = rd.Read7BitEncodedInt(); + byte[] entryData = rd.ReadBytes(entryLength); + if (entryData.Length < entryLength) + throw PassStoreFileException.UnexpectedEndOfFile; + + MemoryStream entryStream = new(entryData); + BinaryReader entryRd = new(entryStream); byte[] uuidBuffer = new byte[16]; - if (rd.Read(uuidBuffer) < 16) + if (entryRd.Read(uuidBuffer) < 16) throw PassStoreFileException.UnexpectedEndOfFile; Guid id = new Guid(uuidBuffer); - ulong timestamp = FileFormatUtil.ReadVarUint16(str); + ulong timestamp = FileFormatUtil.ReadVarUint16(entryStream); DateTime createdAt = DateTimeOffset.FromUnixTimeSeconds((long)timestamp).UtcDateTime; - timestamp = FileFormatUtil.ReadVarUint16(str); + timestamp = FileFormatUtil.ReadVarUint16(entryStream); DateTime modifiedAt = DateTimeOffset.FromUnixTimeSeconds((long)timestamp).UtcDateTime; - if (rd.Read(uuidBuffer) < 16) + if (entryRd.Read(uuidBuffer) < 16) throw PassStoreFileException.UnexpectedEndOfFile; Guid iconType = new Guid(uuidBuffer); - string name = FileFormatUtil.ReadU16TaggedString(str); + string name = FileFormatUtil.ReadU16TaggedString(entryStream); - byte entryType = rd.ReadByte(); + byte entryType = entryRd.ReadByte(); if (entryType == ENTRY_GROUP_ID) { - return PassStoreEntryGroup.ReadFromStream(str, id, createdAt, modifiedAt, iconType, name); + return PassStoreEntryGroup.ReadFromStream(entryStream, id, createdAt, modifiedAt, iconType, name); } else if (entryType == ENTRY_PASS_ID) { - return PassStoreEntryPassword.ReadFromStream(str, id, createdAt, modifiedAt, iconType, name); + return PassStoreEntryPassword.ReadFromStream(entryStream, id, createdAt, modifiedAt, iconType, name); } else { throw PassStoreFileException.InvalidPassStoreEntry; diff --git a/src/KeyKeeper/PasswordStore/PassStoreEntryPassword.cs b/src/KeyKeeper/PasswordStore/PassStoreEntryPassword.cs index 09cece9..529d919 100644 --- a/src/KeyKeeper/PasswordStore/PassStoreEntryPassword.cs +++ b/src/KeyKeeper/PasswordStore/PassStoreEntryPassword.cs @@ -10,8 +10,11 @@ public class PassStoreEntryPassword : PassStoreEntry public LoginField Username { get; set; } public LoginField Password { get; set; } public List ExtraFields { get; set; } + public TotpParameters? Totp { get; set; } - public PassStoreEntryPassword(Guid id, DateTime createdAt, DateTime modifiedAt, Guid iconType, string name, LoginField username, LoginField password, List? extras = null) + public PassStoreEntryPassword(Guid id, DateTime createdAt, DateTime modifiedAt, Guid iconType, string name, + LoginField username, LoginField password, + List? extras = null, TotpParameters? totp = null) { Id = id; CreationDate = createdAt; @@ -21,6 +24,7 @@ public class PassStoreEntryPassword : PassStoreEntry Username = username; Password = password; ExtraFields = extras ?? new(); + Totp = totp; } public static PassStoreEntry ReadFromStream(Stream str, Guid id, DateTime createdAt, DateTime modifiedAt, Guid iconType, string name) @@ -34,6 +38,27 @@ public class PassStoreEntryPassword : PassStoreEntry int extraFieldCount = rd.Read7BitEncodedInt(); for (; extraFieldCount > 0; extraFieldCount--) entry.ExtraFields.Add(ReadField(str)); + + int totpPresence = str.ReadByte(); + if (totpPresence == TOTP_PRESENT) + { + TotpAlgorithm algo = rd.ReadByte() switch + { + TOTP_ALGO_SHA256 => TotpAlgorithm.SHA256, + TOTP_ALGO_SHA512 => TotpAlgorithm.SHA512, + _ => TotpAlgorithm.SHA1, + }; + byte digits = rd.ReadByte(); + int period = rd.Read7BitEncodedInt(); + string secret = FileFormatUtil.ReadU16TaggedString(str); + string issuer = FileFormatUtil.ReadU16TaggedString(str); + string accountName = FileFormatUtil.ReadU16TaggedString(str); + entry.Totp = new TotpParameters( + secret, algo, digits, period, + issuer.Length > 0 ? issuer : null, + accountName.Length > 0 ? accountName : null); + } + return entry; } catch (EndOfStreamException) { @@ -58,6 +83,28 @@ public class PassStoreEntryPassword : PassStoreEntry wr.Write7BitEncodedInt(ExtraFields.Count); foreach (LoginField field in ExtraFields) WriteField(str, field); + + if (Totp is TotpParameters totp) + { + str.WriteByte(TOTP_PRESENT); + byte algoByte = totp.Algorithm switch + { + TotpAlgorithm.SHA256 => TOTP_ALGO_SHA256, + TotpAlgorithm.SHA512 => TOTP_ALGO_SHA512, + _ => TOTP_ALGO_SHA1, + }; + str.WriteByte(algoByte); + str.WriteByte((byte)totp.Digits); + wr.Write7BitEncodedInt(totp.Period); + FileFormatUtil.WriteU16TaggedString(str, totp.Secret); + FileFormatUtil.WriteU16TaggedString(str, totp.Issuer ?? ""); + FileFormatUtil.WriteU16TaggedString(str, totp.AccountName ?? ""); + } + else + { + str.WriteByte(TOTP_ABSENT); + } + return str.ToArray(); } @@ -86,4 +133,4 @@ public class PassStoreEntryPassword : PassStoreEntry field.Value = FileFormatUtil.ReadU16TaggedString(str); return field; } -} \ No newline at end of file +}