Skip to content

Commit

Permalink
src: Check for disposed in all incremental classes.
Browse files Browse the repository at this point in the history
This stuff isn't thread safe, but official .NET IDisposable APIs don't seem to be either. I might change that in the future.

The other thing to note is that it's unnecessary for the tests to have DynamicData. The same is true for InvalidOperation tests. It just inflates the number of tests, so I might change this.
  • Loading branch information
samuel-lucas6 committed Dec 14, 2024
1 parent 91874d6 commit 9c4ea6b
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 30 deletions.
22 changes: 22 additions & 0 deletions src/Geralt.Tests/BLAKE2bTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -505,4 +505,26 @@ public void Incremental_Verify_InvalidOperation(string hash, string message, str
Assert.ThrowsException<InvalidOperationException>(() => blake2b.CacheState());
Assert.ThrowsException<InvalidOperationException>(() => blake2b.RestoreCachedState());
}

[TestMethod]
[DynamicData(nameof(UnkeyedTestVectors), DynamicDataSourceType.Method)]
[DynamicData(nameof(KeyedTestVectors), DynamicDataSourceType.Method)]
public void Incremental_Disposed(string hash, string message, string? key = null)
{
var h = new byte[hash.Length / 2];
var m = Convert.FromHexString(message);
var k = key != null ? Convert.FromHexString(key) : Array.Empty<byte>();

var blake2b = new IncrementalBLAKE2b(h.Length, k);

blake2b.Dispose();

Assert.ThrowsException<ObjectDisposedException>(() => blake2b.Reinitialize(h.Length, k));
Assert.ThrowsException<ObjectDisposedException>(() => blake2b.Update(m));
Assert.ThrowsException<ObjectDisposedException>(() => blake2b.Finalize(h));
Assert.ThrowsException<ObjectDisposedException>(() => blake2b.FinalizeAndVerify(h));
Assert.ThrowsException<ObjectDisposedException>(() => blake2b.CacheState());
Assert.ThrowsException<ObjectDisposedException>(() => blake2b.RestoreCachedState());
Assert.ThrowsException<ObjectDisposedException>(() => blake2b.Dispose());
}
}
20 changes: 20 additions & 0 deletions src/Geralt.Tests/Ed25519Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -458,4 +458,24 @@ public void Incremental_Verify_InvalidOperation(string signature, string message
Assert.ThrowsException<InvalidOperationException>(() => ed25519ph.Finalize(s, sk));
Assert.ThrowsException<InvalidOperationException>(() => ed25519ph.FinalizeAndVerify(s, pk));
}

[TestMethod]
[DynamicData(nameof(Rfc8032Ed25519phTestVectors), DynamicDataSourceType.Method)]
public void Incremental_Disposed(string signature, string message, string privateKey)
{
var s = Convert.FromHexString(signature);
var m = Convert.FromHexString(message);
var sk = Convert.FromHexString(privateKey);
var pk = sk[^Ed25519.PublicKeySize..];

var ed25519ph = new IncrementalEd25519ph();

ed25519ph.Dispose();

Assert.ThrowsException<ObjectDisposedException>(() => ed25519ph.Reinitialize());
Assert.ThrowsException<ObjectDisposedException>(() => ed25519ph.Update(m));
Assert.ThrowsException<ObjectDisposedException>(() => ed25519ph.Finalize(s, sk));
Assert.ThrowsException<ObjectDisposedException>(() => ed25519ph.FinalizeAndVerify(s, pk));
Assert.ThrowsException<ObjectDisposedException>(() => ed25519ph.Dispose());
}
}
19 changes: 19 additions & 0 deletions src/Geralt.Tests/Poly1305Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -257,4 +257,23 @@ public void Incremental_Verify_InvalidOperation(string tag, string message, stri
Assert.ThrowsException<InvalidOperationException>(() => poly1305.Finalize(t));
Assert.ThrowsException<InvalidOperationException>(() => poly1305.FinalizeAndVerify(t));
}

[TestMethod]
[DynamicData(nameof(Rfc8439TestVectors), DynamicDataSourceType.Method)]
public void Incremental_Disposed(string tag, string message, string oneTimeKey)
{
var t = Convert.FromHexString(tag);
var m = Convert.FromHexString(message);
var k = Convert.FromHexString(oneTimeKey);

var poly1305 = new IncrementalPoly1305(k);

poly1305.Dispose();

Assert.ThrowsException<ObjectDisposedException>(() => poly1305.Reinitialize(k));
Assert.ThrowsException<ObjectDisposedException>(() => poly1305.Update(m));
Assert.ThrowsException<ObjectDisposedException>(() => poly1305.Finalize(t));
Assert.ThrowsException<ObjectDisposedException>(() => poly1305.FinalizeAndVerify(t));
Assert.ThrowsException<ObjectDisposedException>(() => poly1305.Dispose());
}
}
77 changes: 48 additions & 29 deletions src/Geralt.Tests/XChaCha20Poly1305Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,42 @@ public void Incremental_Reinitialize_Valid(string key, string plaintext, string
Assert.AreEqual(plaintext, Convert.ToHexString(p).ToLower());
}

[TestMethod]
[DynamicData(nameof(IncrementalDecryptTestVectors), DynamicDataSourceType.Method)]
public void Incremental_Tampered(string header, string key, string plaintext, string ciphertext, string associatedData)
{
var p = new byte[plaintext.Length / 2];
var parameters = new List<byte[]>
{
Convert.FromHexString(header),
Convert.FromHexString(key),
Convert.FromHexString(ciphertext),
Convert.FromHexString(associatedData)
};

foreach (var param in parameters) {
param[0]++;
using var decryptor = new IncrementalXChaCha20Poly1305(decryption: true, parameters[0], parameters[1]);
Assert.ThrowsException<CryptographicException>(() => decryptor.Pull(p, parameters[2], parameters[3]));
param[0]--;
}
Assert.IsTrue(p.SequenceEqual(new byte[p.Length]));
}

[TestMethod]
[DataRow("3677e196fb57f611fe71cf25cbd892481f7a7179c2827102", "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f", "4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e", "cc55781c4cb5443c19b856b695a2a6fc801935832c9c8278da67de429c3760231c846838d5a58a89815a99d572bfcf6ec14ed725931c6ac1fb7445eb25cad39773502f9550670fd918638c89d83ddfda7297b59281b6012f780c0b3f0cb6309345573c7967fbcfb8843ce04db7912754fe861f5963c83dc6ad066c0d04b3235ade74ca", "")]
public void Incremental_MissingRekey(string header, string key, string plaintext, string ciphertext, string associatedData)
{
var h = Convert.FromHexString(header);
var k = Convert.FromHexString(key);
var p = new byte[plaintext.Length / 2];
var c = Convert.FromHexString(ciphertext);

using var decryptor = new IncrementalXChaCha20Poly1305(decryption: true, h, k);
// Should rekey here
Assert.ThrowsException<CryptographicException>(() => decryptor.Pull(p, c));
}

[TestMethod]
[DynamicData(nameof(IncrementalInvalidParameterSizes), DynamicDataSourceType.Method)]
public void Incremental_Invalid(int headerSize, int keySize, int ciphertextSize, int plaintextSize)
Expand Down Expand Up @@ -294,38 +330,21 @@ public void Incremental_InvalidOperation()
}

[TestMethod]
[DynamicData(nameof(IncrementalDecryptTestVectors), DynamicDataSourceType.Method)]
public void Incremental_Tampered(string header, string key, string plaintext, string ciphertext, string associatedData)
public void Incremental_Disposed()
{
var p = new byte[plaintext.Length / 2];
var parameters = new List<byte[]>
{
Convert.FromHexString(header),
Convert.FromHexString(key),
Convert.FromHexString(ciphertext),
Convert.FromHexString(associatedData)
};
var h = new byte[IncrementalXChaCha20Poly1305.HeaderSize];
var k = new byte[IncrementalXChaCha20Poly1305.KeySize];
var p = new byte[h.Length];
var c = new byte[p.Length + IncrementalXChaCha20Poly1305.TagSize];

foreach (var param in parameters) {
param[0]++;
using var decryptor = new IncrementalXChaCha20Poly1305(decryption: true, parameters[0], parameters[1]);
Assert.ThrowsException<CryptographicException>(() => decryptor.Pull(p, parameters[2], parameters[3]));
param[0]--;
}
Assert.IsTrue(p.SequenceEqual(new byte[p.Length]));
}
var encryptor = new IncrementalXChaCha20Poly1305(decryption: false, h, k);

[TestMethod]
[DataRow("3677e196fb57f611fe71cf25cbd892481f7a7179c2827102", "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f", "4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e", "cc55781c4cb5443c19b856b695a2a6fc801935832c9c8278da67de429c3760231c846838d5a58a89815a99d572bfcf6ec14ed725931c6ac1fb7445eb25cad39773502f9550670fd918638c89d83ddfda7297b59281b6012f780c0b3f0cb6309345573c7967fbcfb8843ce04db7912754fe861f5963c83dc6ad066c0d04b3235ade74ca", "")]
public void Incremental_MissingRekey(string header, string key, string plaintext, string ciphertext, string associatedData)
{
var h = Convert.FromHexString(header);
var k = Convert.FromHexString(key);
var p = new byte[plaintext.Length / 2];
var c = Convert.FromHexString(ciphertext);
encryptor.Dispose();

using var decryptor = new IncrementalXChaCha20Poly1305(decryption: true, h, k);
// Should rekey here
Assert.ThrowsException<CryptographicException>(() => decryptor.Pull(p, c));
Assert.ThrowsException<ObjectDisposedException>(() => encryptor.Reinitialize(decryption: false, h, k));
Assert.ThrowsException<ObjectDisposedException>(() => encryptor.Push(c, p, IncrementalXChaCha20Poly1305.ChunkFlag.Final));
Assert.ThrowsException<ObjectDisposedException>(() => encryptor.Pull(p, c));
Assert.ThrowsException<ObjectDisposedException>(() => encryptor.Rekey());
Assert.ThrowsException<ObjectDisposedException>(() => encryptor.Dispose());
}
}
1 change: 0 additions & 1 deletion src/Geralt/Crypto/GuardedHeapAllocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ public GuardedHeapAllocation(int size)
_pointer = sodium_malloc((nuint)size);
if (_pointer == IntPtr.Zero) { throw new OutOfMemoryException("Unable to allocate memory."); }
_size = size;
_disposed = false;
}

public unsafe Span<byte> AsSpan()
Expand Down
9 changes: 9 additions & 0 deletions src/Geralt/Crypto/IncrementalBLAKE2b.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public sealed class IncrementalBLAKE2b : IDisposable
private int _hashSize;
private bool _finalized;
private bool _cached;
private bool _disposed;

public IncrementalBLAKE2b(int hashSize, ReadOnlySpan<byte> key = default)
{
Expand All @@ -30,6 +31,7 @@ public IncrementalBLAKE2b(int hashSize, ReadOnlySpan<byte> key = default)

public void Reinitialize(int hashSize, ReadOnlySpan<byte> key = default)
{
if (_disposed) { throw new ObjectDisposedException(nameof(IncrementalBLAKE2b)); }
Validation.SizeBetween(nameof(hashSize), hashSize, MinHashSize, MaxHashSize);
if (key.Length != 0) { Validation.SizeBetween(nameof(key), key.Length, MinKeySize, MaxKeySize); }
_hashSize = hashSize;
Expand All @@ -40,13 +42,15 @@ public void Reinitialize(int hashSize, ReadOnlySpan<byte> key = default)

public void Update(ReadOnlySpan<byte> message)
{
if (_disposed) { throw new ObjectDisposedException(nameof(IncrementalBLAKE2b)); }
if (_finalized) { throw new InvalidOperationException("Cannot update after finalizing without reinitializing or restoring a cached state."); }
int ret = crypto_generichash_update(ref _state, message, (ulong)message.Length);
if (ret != 0) { throw new CryptographicException("Error updating hash function state."); }
}

public void Finalize(Span<byte> hash)
{
if (_disposed) { throw new ObjectDisposedException(nameof(IncrementalBLAKE2b)); }
if (_finalized) { throw new InvalidOperationException("Cannot finalize twice without reinitializing or restoring a cached state."); }
Validation.EqualToSize(nameof(hash), hash.Length, _hashSize);
_finalized = true;
Expand All @@ -56,6 +60,7 @@ public void Finalize(Span<byte> hash)

public bool FinalizeAndVerify(ReadOnlySpan<byte> hash)
{
if (_disposed) { throw new ObjectDisposedException(nameof(IncrementalBLAKE2b)); }
if (_finalized) { throw new InvalidOperationException("Cannot finalize twice without reinitializing or restoring a cached state."); }
Validation.EqualToSize(nameof(hash), hash.Length, _hashSize);
Span<byte> computedHash = stackalloc byte[_hashSize];
Expand All @@ -67,13 +72,15 @@ public bool FinalizeAndVerify(ReadOnlySpan<byte> hash)

public void CacheState()
{
if (_disposed) { throw new ObjectDisposedException(nameof(IncrementalBLAKE2b)); }
if (_finalized) { throw new InvalidOperationException("Cannot cache the state after finalizing without reinitializing."); }
_cachedState = _state;
_cached = true;
}

public void RestoreCachedState()
{
if (_disposed) { throw new ObjectDisposedException(nameof(IncrementalBLAKE2b)); }
if (!_cached) { throw new InvalidOperationException("Cannot restore the state when it has not been cached."); }
_state = _cachedState;
_finalized = false;
Expand All @@ -82,7 +89,9 @@ public void RestoreCachedState()
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
public void Dispose()
{
if (_disposed) { throw new ObjectDisposedException(nameof(IncrementalBLAKE2b)); }
_state = default;
_cachedState = default;
_disposed = true;
}
}
7 changes: 7 additions & 0 deletions src/Geralt/Crypto/IncrementalEd25519ph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public sealed class IncrementalEd25519ph : IDisposable

private crypto_sign_state _state;
private bool _finalized;
private bool _disposed;

public IncrementalEd25519ph()
{
Expand All @@ -21,20 +22,23 @@ public IncrementalEd25519ph()

public void Reinitialize()
{
if (_disposed) { throw new ObjectDisposedException(nameof(IncrementalEd25519ph)); }
_finalized = false;
int ret = crypto_sign_init(ref _state);
if (ret != 0) { throw new CryptographicException("Error initializing signature scheme state."); }
}

public void Update(ReadOnlySpan<byte> message)
{
if (_disposed) { throw new ObjectDisposedException(nameof(IncrementalEd25519ph)); }
if (_finalized) { throw new InvalidOperationException("Cannot update after finalizing without reinitializing."); }
int ret = crypto_sign_update(ref _state, message, (ulong)message.Length);
if (ret != 0) { throw new CryptographicException("Error updating signature scheme state."); }
}

public void Finalize(Span<byte> signature, ReadOnlySpan<byte> privateKey)
{
if (_disposed) { throw new ObjectDisposedException(nameof(IncrementalEd25519ph)); }
if (_finalized) { throw new InvalidOperationException("Cannot finalize twice without reinitializing."); }
Validation.EqualToSize(nameof(signature), signature.Length, SignatureSize);
Validation.EqualToSize(nameof(privateKey), privateKey.Length, PrivateKeySize);
Expand All @@ -45,6 +49,7 @@ public void Finalize(Span<byte> signature, ReadOnlySpan<byte> privateKey)

public bool FinalizeAndVerify(ReadOnlySpan<byte> signature, ReadOnlySpan<byte> publicKey)
{
if (_disposed) { throw new ObjectDisposedException(nameof(IncrementalEd25519ph)); }
if (_finalized) { throw new InvalidOperationException("Cannot finalize twice without reinitializing."); }
Validation.EqualToSize(nameof(signature), signature.Length, SignatureSize);
Validation.EqualToSize(nameof(publicKey), publicKey.Length, PublicKeySize);
Expand All @@ -55,6 +60,8 @@ public bool FinalizeAndVerify(ReadOnlySpan<byte> signature, ReadOnlySpan<byte> p
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
public void Dispose()
{
if (_disposed) { throw new ObjectDisposedException(nameof(IncrementalEd25519ph)); }
_state = default;
_disposed = true;
}
}
7 changes: 7 additions & 0 deletions src/Geralt/Crypto/IncrementalPoly1305.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public sealed class IncrementalPoly1305 : IDisposable

private crypto_onetimeauth_state _state;
private bool _finalized;
private bool _disposed;

public IncrementalPoly1305(ReadOnlySpan<byte> oneTimeKey)
{
Expand All @@ -20,6 +21,7 @@ public IncrementalPoly1305(ReadOnlySpan<byte> oneTimeKey)

public void Reinitialize(ReadOnlySpan<byte> oneTimeKey)
{
if (_disposed) { throw new ObjectDisposedException(nameof(IncrementalPoly1305)); }
Validation.EqualToSize(nameof(oneTimeKey), oneTimeKey.Length, KeySize);
_finalized = false;
int ret = crypto_onetimeauth_init(ref _state, oneTimeKey);
Expand All @@ -28,13 +30,15 @@ public void Reinitialize(ReadOnlySpan<byte> oneTimeKey)

public void Update(ReadOnlySpan<byte> message)
{
if (_disposed) { throw new ObjectDisposedException(nameof(IncrementalPoly1305)); }
if (_finalized) { throw new InvalidOperationException("Cannot update after finalizing without reinitializing."); }
int ret = crypto_onetimeauth_update(ref _state, message, (ulong)message.Length);
if (ret != 0) { throw new CryptographicException("Error updating message authentication code state."); }
}

public void Finalize(Span<byte> tag)
{
if (_disposed) { throw new ObjectDisposedException(nameof(IncrementalPoly1305)); }
if (_finalized) { throw new InvalidOperationException("Cannot finalize twice without reinitializing."); }
Validation.EqualToSize(nameof(tag), tag.Length, TagSize);
_finalized = true;
Expand All @@ -44,6 +48,7 @@ public void Finalize(Span<byte> tag)

public bool FinalizeAndVerify(ReadOnlySpan<byte> tag)
{
if (_disposed) { throw new ObjectDisposedException(nameof(IncrementalPoly1305)); }
if (_finalized) { throw new InvalidOperationException("Cannot finalize twice without reinitializing."); }
Validation.EqualToSize(nameof(tag), tag.Length, TagSize);
Span<byte> computedTag = stackalloc byte[TagSize];
Expand All @@ -56,6 +61,8 @@ public bool FinalizeAndVerify(ReadOnlySpan<byte> tag)
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
public void Dispose()
{
if (_disposed) { throw new ObjectDisposedException(nameof(IncrementalPoly1305)); }
_state = default;
_disposed = true;
}
}
Loading

0 comments on commit 9c4ea6b

Please sign in to comment.