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; toRead -= n;
chunkPosition += n; chunkPosition += n;
position += 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; return read;
} }

View File

@@ -30,5 +30,5 @@ static class FileFormatConstants
public const byte GROUP_TYPE_FAVOURITES = 0x02; public const byte GROUP_TYPE_FAVOURITES = 0x02;
public const byte GROUP_TYPE_SIMPLE = 0x03; public const byte GROUP_TYPE_SIMPLE = 0x03;
public const byte GROUP_TYPE_CUSTOM = 0xff; // пока не используется 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Security.Cryptography; using System.Security.Cryptography;
using KeyKeeper.PasswordStore.Crypto; using KeyKeeper.PasswordStore.Crypto;
using KeyKeeper.PasswordStore.Crypto.KeyDerivation; using KeyKeeper.PasswordStore.Crypto.KeyDerivation;
@@ -36,11 +37,13 @@ public class PassStoreFileAccessor : IPassStore
public bool Locked public bool Locked
{ {
get { return key != null; } get { return key == null; }
} }
public IPassStoreDirectory GetRootDirectory() public IPassStoreDirectory GetRootDirectory()
{ {
if (Locked)
throw new InvalidOperationException();
return root!; return root!;
} }
@@ -52,6 +55,10 @@ public class PassStoreFileAccessor : IPassStore
public void Unlock(CompositeKey key) public void Unlock(CompositeKey key)
{ {
if (!Locked) return; 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() public void Lock()
@@ -74,7 +81,7 @@ public class PassStoreFileAccessor : IPassStore
{ {
throw PassStoreFileException.UnexpectedEndOfFile; throw PassStoreFileException.UnexpectedEndOfFile;
} }
if (magic != FORMAT_MAGIC) if (!magic.SequenceEqual(FORMAT_MAGIC))
{ {
throw PassStoreFileException.IncorrectMagicNumber; throw PassStoreFileException.IncorrectMagicNumber;
} }
@@ -125,9 +132,9 @@ public class PassStoreFileAccessor : IPassStore
file.Write(randomPadding); file.Write(randomPadding);
} }
byte[] masterKey = newHeader.KdfInfo.GetKdf().Derive(options.Key, 32); key = newHeader.KdfInfo.GetKdf().Derive(options.Key, 32);
// пока предполагаем что везде используется AES // пока предполагаем что везде используется AES
OuterEncryptionWriter cryptoWriter = new(file, masterKey, ((OuterAesHeader)newHeader.OuterCryptoHeader).InitVector); OuterEncryptionWriter cryptoWriter = new(file, key, ((OuterAesHeader)newHeader.OuterCryptoHeader).InitVector);
BinaryWriter wr = new(cryptoWriter); BinaryWriter wr = new(cryptoWriter);
@@ -234,6 +241,70 @@ public class PassStoreFileAccessor : IPassStore
} }
return written; 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 {} record OuterEncryptionHeader {}

View File

@@ -1,13 +1,25 @@
using System;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using KeyKeeper.PasswordStore;
using KeyKeeper.PasswordStore.Crypto;
namespace KeyKeeper; namespace KeyKeeper;
public partial class RepositoryWindow: Window public partial class RepositoryWindow: Window
{ {
public IPassStore? PassStore { private get; init; }
public RepositoryWindow() public RepositoryWindow()
{ {
InitializeComponent(); 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.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using KeyKeeper.PasswordStore;
using KeyKeeper.ViewModels; using KeyKeeper.ViewModels;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -39,7 +40,11 @@ namespace KeyKeeper.Views
if (file.TryGetLocalPath() is string path) if (file.TryGetLocalPath() is string path)
{ {
(DataContext as MainWindowViewModel)!.CreateVault(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) if (file.TryGetLocalPath() is string path)
{ {
(DataContext as MainWindowViewModel)!.OpenVault(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() var repositoryWindow = new RepositoryWindow()
{ {
DataContext = this.DataContext, DataContext = this.DataContext,
PassStore = store,
WindowStartupLocation = WindowStartupLocation.CenterScreen WindowStartupLocation = WindowStartupLocation.CenterScreen
}; };
repositoryWindow.Closed += (s, e) => this.Show(); repositoryWindow.Closed += (s, e) => this.Show();