fix bugs and add some placeholders

+ fix file magic number check
+ change marker size 7 -> 8
+ set key field when creating-and-unlocking, so Locked becomes false
+ throw InvalidOperationException in GetRootDirectory when the store is not unlocked
+ invert Locked property (hahaha)
+ fix debug logging in OuterEncryptionReader
+ implement reading the file header from a Stream
+ PassStoreFileAccessor.Unlock reads and prints out the file header (placeholder for future implementation)
+ the Create Store button creates an empty file at the selected location
+ the Open Store button checks the file and calls Unlock
This commit is contained in:
2025-12-04 00:32:15 +03:00
parent 8fe565ca82
commit 000722e5a6
5 changed files with 98 additions and 9 deletions

View File

@@ -139,7 +139,7 @@ public class OuterEncryptionReader : Stream
toRead -= n;
chunkPosition += n;
position += n;
Console.WriteLine(string.Format("read={} toread={} pos={}", read, toRead, chunkPosition));
Console.WriteLine(string.Format("read={0} toread={1} pos={2}", read, toRead, chunkPosition));
}
return read;
}

View File

@@ -30,5 +30,5 @@ static class FileFormatConstants
public const byte GROUP_TYPE_FAVOURITES = 0x02;
public const byte GROUP_TYPE_SIMPLE = 0x03;
public const byte GROUP_TYPE_CUSTOM = 0xff; // пока не используется
public static readonly byte[] BEGIN_MARKER = [0x5f, 0x4f, 0xcf, 0x67, 0xc0, 0x90, 0xd0];
public static readonly byte[] BEGIN_MARKER = [0x5f, 0x4f, 0xcf, 0x67, 0xc0, 0x90, 0xd0, 0xe5];
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using KeyKeeper.PasswordStore.Crypto;
using KeyKeeper.PasswordStore.Crypto.KeyDerivation;
@@ -36,11 +37,13 @@ public class PassStoreFileAccessor : IPassStore
public bool Locked
{
get { return key != null; }
get { return key == null; }
}
public IPassStoreDirectory GetRootDirectory()
{
if (Locked)
throw new InvalidOperationException();
return root!;
}
@@ -52,6 +55,10 @@ public class PassStoreFileAccessor : IPassStore
public void Unlock(CompositeKey key)
{
if (!Locked) return;
using FileStream file = new(filename, FileMode.Open, FileAccess.Read, FileShare.None);
FileHeader hdr = FileHeader.ReadFrom(file);
Console.WriteLine(hdr); // debug
}
public void Lock()
@@ -74,7 +81,7 @@ public class PassStoreFileAccessor : IPassStore
{
throw PassStoreFileException.UnexpectedEndOfFile;
}
if (magic != FORMAT_MAGIC)
if (!magic.SequenceEqual(FORMAT_MAGIC))
{
throw PassStoreFileException.IncorrectMagicNumber;
}
@@ -125,9 +132,9 @@ public class PassStoreFileAccessor : IPassStore
file.Write(randomPadding);
}
byte[] masterKey = newHeader.KdfInfo.GetKdf().Derive(options.Key, 32);
key = newHeader.KdfInfo.GetKdf().Derive(options.Key, 32);
// пока предполагаем что везде используется AES
OuterEncryptionWriter cryptoWriter = new(file, masterKey, ((OuterAesHeader)newHeader.OuterCryptoHeader).InitVector);
OuterEncryptionWriter cryptoWriter = new(file, key, ((OuterAesHeader)newHeader.OuterCryptoHeader).InitVector);
BinaryWriter wr = new(cryptoWriter);
@@ -234,6 +241,70 @@ public class PassStoreFileAccessor : IPassStore
}
return written;
}
public static FileHeader ReadFrom(Stream s)
{
BinaryReader rd = new(s);
{
byte[] magic = new byte[8];
if (rd.Read(magic, 0, 8) < 8)
throw PassStoreFileException.UnexpectedEndOfFile;
if (!magic.SequenceEqual(FORMAT_MAGIC))
throw PassStoreFileException.IncorrectMagicNumber;
}
try
{
ushort major, minor;
major = rd.ReadUInt16();
minor = rd.ReadUInt16();
if (major != FORMAT_VERSION_MAJOR || minor != FORMAT_VERSION_MINOR)
throw PassStoreFileException.UnsupportedVersion;
byte saltLen = rd.ReadByte();
if (saltLen < MIN_MASTER_SALT_LEN || saltLen > MAX_MASTER_SALT_LEN)
throw PassStoreFileException.InvalidCryptoHeader;
byte[] salt = new byte[saltLen];
if (rd.Read(salt) < saltLen)
throw PassStoreFileException.UnexpectedEndOfFile;
byte typeDiscrim = rd.ReadByte();
OuterEncryptionHeader outerEncrHdr;
if (typeDiscrim == ENCRYPT_ALGO_AES)
{
byte[] iv = new byte[16];
if (rd.Read(iv) < 16)
throw PassStoreFileException.UnexpectedEndOfFile;
outerEncrHdr = new OuterAesHeader(iv);
} else
{
throw PassStoreFileException.InvalidCryptoHeader;
}
typeDiscrim = rd.ReadByte();
KdfHeader kdfHdr;
if (typeDiscrim == KDF_TYPE_AESKDF)
{
int rounds = rd.Read7BitEncodedInt();
byte[] seed = new byte[32];
if (rd.Read(seed) < 32)
throw PassStoreFileException.UnexpectedEndOfFile;
kdfHdr = new AesKdfHeader(rounds, seed);
} else
{
throw PassStoreFileException.InvalidCryptoHeader;
}
return new FileHeader(
major, minor, salt,
outerEncrHdr, kdfHdr
);
}
catch (EndOfStreamException)
{
throw PassStoreFileException.UnexpectedEndOfFile;
}
}
};
record OuterEncryptionHeader {}

View File

@@ -1,13 +1,25 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using KeyKeeper.PasswordStore;
using KeyKeeper.PasswordStore.Crypto;
namespace KeyKeeper;
public partial class RepositoryWindow: Window
{
public IPassStore? PassStore { private get; init; }
public RepositoryWindow()
{
InitializeComponent();
}
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
if (PassStore!.Locked)
PassStore.Unlock(new CompositeKey("blablabla", null));
}
}

View File

@@ -3,6 +3,7 @@ using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using KeyKeeper.PasswordStore;
using KeyKeeper.ViewModels;
using System;
using System.Collections.Generic;
@@ -39,7 +40,11 @@ namespace KeyKeeper.Views
if (file.TryGetLocalPath() is string path)
{
(DataContext as MainWindowViewModel)!.CreateVault(path);
OpenRepositoryWindow();
OpenRepositoryWindow(new PassStoreFileAccessor(path, true, new StoreCreationOptions()
{
Key = new PasswordStore.Crypto.CompositeKey("blablabla", null),
LockTimeoutSeconds = 800,
}));
}
}
}
@@ -70,16 +75,17 @@ namespace KeyKeeper.Views
if (file.TryGetLocalPath() is string path)
{
(DataContext as MainWindowViewModel)!.OpenVault(path);
OpenRepositoryWindow();
OpenRepositoryWindow(new PassStoreFileAccessor(path, false, null));
}
}
}
private void OpenRepositoryWindow()
private void OpenRepositoryWindow(IPassStore store)
{
var repositoryWindow = new RepositoryWindow()
{
DataContext = this.DataContext,
PassStore = store,
WindowStartupLocation = WindowStartupLocation.CenterScreen
};
repositoryWindow.Closed += (s, e) => this.Show();