Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Fix AesStream Invalid Password exception in corrupted files #2344

Merged
merged 1 commit into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 82 additions & 1 deletion LiteDB.Tests/Internals/Aes_Tests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System.IO;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using FluentAssertions;
using LiteDB.Engine;
using Xunit;
Expand Down Expand Up @@ -61,5 +64,83 @@ public void Encrypt_Decrypt_Stream()
output2.All(x => x == 102).Should().BeTrue();
}
}

/// <summary>
/// Test whether AesStream can handle stream that has invalid page size.
/// </summary>
[Fact]
public void AesStream_Invalid_Page_Size()
{
var fakeContent = new byte[] {
1,22,222,184,3,227,126,129,205,182,182,143,201,181,242,107,36,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
20,88,18,70,65,77,202,50,184,177,167,59,80,255,67,66,20,
88,18,70,65,77,202,50,184,177,167,59,80,255,67,66
};

// stream of 64bytes (invalid page size)
using (var memoryStream = new MemoryStream())
{
memoryStream.Write(fakeContent, 0, fakeContent.Length);
memoryStream.Position = 0;

using (var crypto = new AesStream("password", memoryStream))
{
// 1st page is hidden, so AesStream.Length returns (stream.Length - PAGE_SIZE)
Assert.Equal(0, crypto.Length);

// AesStream should add padding to the underlying stream to make its size equivalent to PAGE_SIZE
Assert.Equal(8192, memoryStream.Length);
}
}
}

/// <summary>
/// Test whether AesStream can handle stream where bytes 32-64 are empty.
/// </summary>
[Fact]
public void AesStream_Invalid_Password()
{
// stream of 8192 bytes where bytes 32 to 64 is empty.
using (var memoryStream = new MemoryStream())
{
// 1st byte to indicate the stream is encrypted
memoryStream.WriteByte(1);

// next 16 bytes contain salt
var salt = new byte[16];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(salt);
}
memoryStream.Write(salt, 0, salt.Length);

// remaining (8192 - 17) bytes are empty
var emptyContent = new byte[8175];
memoryStream.Write(emptyContent, 0, emptyContent.Length);

// reset the stream position to 0
memoryStream.Position = 0;

using (var crypto = new AesStream("password", memoryStream))
{
// 1st page is hidden, so AesStream.Length returns (stream.Length - PAGE_SIZE)
Assert.Equal(0, crypto.Length);

// AesStream should add padding to the underlying stream to make its size equivalent to PAGE_SIZE
Assert.Equal(8192, memoryStream.Length);

// AesStream should fill bytes 32-64 with encrypted 1s
var checkBytes = new byte[32];
var cryptoReader = typeof(AesStream)
.GetField("_reader", BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(crypto) as CryptoStream;

memoryStream.Position = 32;
cryptoReader.Read(checkBytes, 0, checkBytes.Length);
Assert.All(checkBytes, b => Assert.Equal(1, b));
}
}
}
}
}
14 changes: 11 additions & 3 deletions LiteDB/Engine/Disk/Streams/AesStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,6 @@ public AesStream(string password, Stream stream)
// first byte =1 means this datafile is encrypted
_stream.WriteByte(1);
_stream.Write(this.Salt, 0, ENCRYPTION_SALT_SIZE);

// fill with 0 full PAGE_SIZE
_stream.Write(_emptyContent, 0, _emptyContent.Length);
}
else
{
Expand Down Expand Up @@ -109,6 +106,17 @@ public AesStream(string password, Stream stream)

var checkBuffer = new byte[32];

if (!isNew)
{
// check whether bytes 32 to 64 is empty. This indicates LiteDb was unable to write encrypted 1s during last attempt.
_stream.Read(checkBuffer, 0, checkBuffer.Length);
isNew = checkBuffer.All(x => x == 0);

// reset checkBuffer and stream position
Array.Clear(checkBuffer, 0, checkBuffer.Length);
_stream.Position = 32;
}

// fill checkBuffer with encrypted 1 to check when open
if (isNew)
{
Expand Down