mirror of
https://github.com/KeyKeeperApp/KeyKeeper.git
synced 2026-05-10 18:36:30 +03:00
add class representing a content chunk of the password store
This commit is contained in:
107
src/KeyKeeper/PasswordStore/Crypto/PassStoreContentChunk.cs
Normal file
107
src/KeyKeeper/PasswordStore/Crypto/PassStoreContentChunk.cs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
using System;
|
||||||
|
using System.Buffers.Binary;
|
||||||
|
using System.IO;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using KeyKeeper.PasswordStore;
|
||||||
|
using static KeyKeeper.PasswordStore.FileFormatConstants;
|
||||||
|
|
||||||
|
namespace KeyKeeper.Crypto;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Класс, представляющий собой обертку над content chunkом, считанным из файла
|
||||||
|
/// хранилища. Не расшифровывает содержимое, но проверяет целостность и
|
||||||
|
/// подлинность при создании объекта.
|
||||||
|
/// </summary>
|
||||||
|
public class PassStoreContentChunk
|
||||||
|
{
|
||||||
|
private byte[] chunk;
|
||||||
|
private int chunkLen;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Создаёт объект content chunk, считывая массив байт. Бросает исключение
|
||||||
|
/// в случае, если массив не содержит корректный content chunk или не
|
||||||
|
/// совпадает HMAC
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="chunk">Массив байт, содержащий весь content chunk, включая
|
||||||
|
/// длину и HMAC</param>
|
||||||
|
/// <param name="key">Ключ от хранилища. Используется только для
|
||||||
|
/// проверки HMAC и не хранится в объекте</param>
|
||||||
|
/// <param name="chunkOrdinal">Порядковый номер content chunk'а, начиная
|
||||||
|
/// с 0</param>
|
||||||
|
public PassStoreContentChunk(byte[] chunk, byte[] key, int chunkOrdinal)
|
||||||
|
{
|
||||||
|
this.chunk = chunk;
|
||||||
|
|
||||||
|
MemoryStream str = new(chunk);
|
||||||
|
BinaryReader rd = new(str);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
chunkLen = rd.ReadUInt16();
|
||||||
|
chunkLen = (chunkLen << 8) | rd.ReadByte();
|
||||||
|
}
|
||||||
|
catch (EndOfStreamException)
|
||||||
|
{
|
||||||
|
throw PassStoreFileException.UnexpectedEndOfFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunk.Length != chunkLen + 3 + HMAC_SIZE)
|
||||||
|
{
|
||||||
|
throw PassStoreFileException.UnexpectedEndOfFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] storedHmac = new byte[HMAC_SIZE];
|
||||||
|
str.Read(storedHmac, 0, HMAC_SIZE);
|
||||||
|
|
||||||
|
HMACSHA3_512 hmac = new(key);
|
||||||
|
hmac.TransformBlock(chunk, (int)str.Position, Math.Min(chunkLen, chunk.Length - (int)str.Position), null, 0);
|
||||||
|
|
||||||
|
byte[] encodedOrdinal = new byte[sizeof(int)];
|
||||||
|
BinaryPrimitives.WriteInt32LittleEndian(new Span<byte>(encodedOrdinal), chunkOrdinal);
|
||||||
|
hmac.TransformBlock(encodedOrdinal, 0, encodedOrdinal.Length, null, 0);
|
||||||
|
|
||||||
|
byte[] actualHmac = hmac.Hash!;
|
||||||
|
|
||||||
|
if (!storedHmac.Equals(actualHmac))
|
||||||
|
{
|
||||||
|
throw PassStoreFileException.ContentHMACMismatch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Создаёт объект content chunk, считывая байты из потока. Бросает
|
||||||
|
/// исключение в случае, если массив не содержит корректный content chunk
|
||||||
|
/// или не совпадает HMAC
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="chunk">Массив байт, содержащий весь content chunk, включая
|
||||||
|
/// длину и HMAC</param>
|
||||||
|
/// <param name="key">Ключ от хранилища. Используется только для
|
||||||
|
/// проверки HMAC и не хранится в объекте</param>
|
||||||
|
/// <param name="chunkOrdinal">Порядковый номер content chunk'а, начиная
|
||||||
|
/// с 0</param>
|
||||||
|
public static PassStoreContentChunk GetFromStream(Stream s, byte[] key, int chunkOrdinal)
|
||||||
|
{
|
||||||
|
BinaryReader rd = new(s);
|
||||||
|
int chunkLen;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
chunkLen = rd.ReadUInt16();
|
||||||
|
chunkLen = (chunkLen << 8) | rd.ReadByte();
|
||||||
|
}
|
||||||
|
catch (EndOfStreamException)
|
||||||
|
{
|
||||||
|
throw PassStoreFileException.UnexpectedEndOfFile;
|
||||||
|
}
|
||||||
|
byte[] chunk = new byte[3 + HMAC_SIZE + chunkLen];
|
||||||
|
if (s.Read(chunk) < chunk.Length)
|
||||||
|
{
|
||||||
|
throw PassStoreFileException.UnexpectedEndOfFile;
|
||||||
|
}
|
||||||
|
return new PassStoreContentChunk(chunk, key, chunkOrdinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlySpan<byte> GetContent()
|
||||||
|
{
|
||||||
|
return new ReadOnlySpan<byte>(chunk, 3 + HMAC_SIZE, chunkLen);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user