Skip to content

Commit

Permalink
XChaCha20.cs: Check for counter overflows.
Browse files Browse the repository at this point in the history
To be consistent with ChaCha20.
  • Loading branch information
samuel-lucas6 committed Aug 13, 2023
1 parent 444f792 commit f975167
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 17 deletions.
40 changes: 26 additions & 14 deletions src/Geralt.Tests/XChaCha20Tests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Geralt.Tests;
Expand Down Expand Up @@ -36,26 +37,27 @@ public static IEnumerable<object[]> DraftXChaChaEncryptTestVectors()
"5468652064686f6c65202870726f6e6f756e6365642022646f6c65222920697320616c736f206b6e6f776e2061732074686520417369617469632077696c6420646f672c2072656420646f672c20616e642077686973746c696e6720646f672e2049742069732061626f7574207468652073697a65206f662061204765726d616e20736865706865726420627574206c6f6f6b73206d6f7265206c696b652061206c6f6e672d6c656767656420666f782e205468697320686967686c7920656c757369766520616e6420736b696c6c6564206a756d70657220697320636c6173736966696564207769746820776f6c7665732c20636f796f7465732c206a61636b616c732c20616e6420666f78657320696e20746865207461786f6e6f6d69632066616d696c792043616e696461652e",
"404142434445464748494a4b4c4d4e4f5051525354555658",
"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
(uint)0
(ulong)0
};
yield return new object[]
{
"7d0a2e6b7f7c65a236542630294e063b7ab9b555a5d5149aa21e4ae1e4fbce87ecc8e08a8b5e350abe622b2ffa617b202cfad72032a3037e76ffdcdc4376ee053a190d7e46ca1de04144850381b9cb29f051915386b8a710b8ac4d027b8b050f7cba5854e028d564e453b8a968824173fc16488b8970cac828f11ae53cabd20112f87107df24ee6183d2274fe4c8b1485534ef2c5fbc1ec24bfc3663efaa08bc047d29d25043532db8391a8a3d776bf4372a6955827ccb0cdd4af403a7ce4c63d595c75a43e045f0cce1f29c8b93bd65afc5974922f214a40b7c402cdb91ae73c0b63615cdad0480680f16515a7ace9d39236464328a37743ffc28f4ddb324f4d0f5bbdc270c65b1749a6efff1fbaa09536175ccd29fb9e6057b307320d316838a9c71f70b5b5907a66f7ea49aadc409",
"5468652064686f6c65202870726f6e6f756e6365642022646f6c65222920697320616c736f206b6e6f776e2061732074686520417369617469632077696c6420646f672c2072656420646f672c20616e642077686973746c696e6720646f672e2049742069732061626f7574207468652073697a65206f662061204765726d616e20736865706865726420627574206c6f6f6b73206d6f7265206c696b652061206c6f6e672d6c656767656420666f782e205468697320686967686c7920656c757369766520616e6420736b696c6c6564206a756d70657220697320636c6173736966696564207769746820776f6c7665732c20636f796f7465732c206a61636b616c732c20616e6420666f78657320696e20746865207461786f6e6f6d69632066616d696c792043616e696461652e",
"404142434445464748494a4b4c4d4e4f5051525354555658",
"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
(uint)1
(ulong)1
};
}

public static IEnumerable<object[]> EncryptInvalidParameterSizes()
{
yield return new object[] { XChaCha20.BlockSize, XChaCha20.BlockSize + 1, XChaCha20.NonceSize, XChaCha20.KeySize, (uint)0 };
yield return new object[] { XChaCha20.BlockSize, XChaCha20.BlockSize - 1, XChaCha20.NonceSize, XChaCha20.KeySize, (uint)0 };
yield return new object[] { XChaCha20.BlockSize, XChaCha20.BlockSize, XChaCha20.NonceSize + 1, XChaCha20.KeySize, (uint)0 };
yield return new object[] { XChaCha20.BlockSize, XChaCha20.BlockSize, XChaCha20.NonceSize - 1, XChaCha20.KeySize, (uint)0 };
yield return new object[] { XChaCha20.BlockSize, XChaCha20.BlockSize, XChaCha20.NonceSize, XChaCha20.KeySize + 1, (uint)0 };
yield return new object[] { XChaCha20.BlockSize, XChaCha20.BlockSize, XChaCha20.NonceSize, XChaCha20.KeySize - 1, (uint)0 };
yield return new object[] { XChaCha20.BlockSize, XChaCha20.BlockSize + 1, XChaCha20.NonceSize, XChaCha20.KeySize, (ulong)0 };
yield return new object[] { XChaCha20.BlockSize, XChaCha20.BlockSize - 1, XChaCha20.NonceSize, XChaCha20.KeySize, (ulong)0 };
yield return new object[] { XChaCha20.BlockSize, XChaCha20.BlockSize, XChaCha20.NonceSize + 1, XChaCha20.KeySize, (ulong)0 };
yield return new object[] { XChaCha20.BlockSize, XChaCha20.BlockSize, XChaCha20.NonceSize - 1, XChaCha20.KeySize, (ulong)0 };
yield return new object[] { XChaCha20.BlockSize, XChaCha20.BlockSize, XChaCha20.NonceSize, XChaCha20.KeySize + 1, (ulong)0 };
yield return new object[] { XChaCha20.BlockSize, XChaCha20.BlockSize, XChaCha20.NonceSize, XChaCha20.KeySize - 1, (ulong)0 };
yield return new object[] { XChaCha20.BlockSize, XChaCha20.BlockSize, XChaCha20.NonceSize, XChaCha20.KeySize, ulong.MaxValue };
}

[TestMethod]
Expand Down Expand Up @@ -92,7 +94,7 @@ public void Fill_Invalid(int bufferSize, int nonceSize, int keySize)

[TestMethod]
[DynamicData(nameof(DraftXChaChaEncryptTestVectors), DynamicDataSourceType.Method)]
public void Encrypt_Valid(string ciphertext, string plaintext, string nonce, string key, uint counter)
public void Encrypt_Valid(string ciphertext, string plaintext, string nonce, string key, ulong counter)
{
Span<byte> c = stackalloc byte[ciphertext.Length / 2];
Span<byte> p = Convert.FromHexString(plaintext);
Expand All @@ -106,19 +108,24 @@ public void Encrypt_Valid(string ciphertext, string plaintext, string nonce, str

[TestMethod]
[DynamicData(nameof(EncryptInvalidParameterSizes), DynamicDataSourceType.Method)]
public void Encrypt_Invalid(int ciphertextSize, int plaintextSize, int nonceSize, int keySize, uint counter)
public void Encrypt_Invalid(int ciphertextSize, int plaintextSize, int nonceSize, int keySize, ulong counter)
{
var c = new byte[ciphertextSize];
var p = new byte[plaintextSize];
var n = new byte[nonceSize];
var k = new byte[keySize];

Assert.ThrowsException<ArgumentOutOfRangeException>(() => XChaCha20.Encrypt(c, p, n, k, counter));
if (counter < ulong.MaxValue) {
Assert.ThrowsException<ArgumentOutOfRangeException>(() => XChaCha20.Encrypt(c, p, n, k, counter));
}
else {
Assert.ThrowsException<CryptographicException>(() => XChaCha20.Encrypt(c, p, n, k, counter));
}
}

[TestMethod]
[DynamicData(nameof(DraftXChaChaEncryptTestVectors), DynamicDataSourceType.Method)]
public void Decrypt_Valid(string ciphertext, string plaintext, string nonce, string key, uint counter)
public void Decrypt_Valid(string ciphertext, string plaintext, string nonce, string key, ulong counter)
{
Span<byte> p = stackalloc byte[plaintext.Length / 2];
Span<byte> c = Convert.FromHexString(ciphertext);
Expand All @@ -132,13 +139,18 @@ public void Decrypt_Valid(string ciphertext, string plaintext, string nonce, str

[TestMethod]
[DynamicData(nameof(EncryptInvalidParameterSizes), DynamicDataSourceType.Method)]
public void Decrypt_Invalid(int ciphertextSize, int plaintextSize, int nonceSize, int keySize, uint counter)
public void Decrypt_Invalid(int ciphertextSize, int plaintextSize, int nonceSize, int keySize, ulong counter)
{
var p = new byte[plaintextSize];
var c = new byte[ciphertextSize];
var n = new byte[nonceSize];
var k = new byte[keySize];

Assert.ThrowsException<ArgumentOutOfRangeException>(() => XChaCha20.Decrypt(p, c, n, k, counter));
if (counter < ulong.MaxValue) {
Assert.ThrowsException<ArgumentOutOfRangeException>(() => XChaCha20.Decrypt(p, c, n, k, counter));
}
else {
Assert.ThrowsException<CryptographicException>(() => XChaCha20.Decrypt(p, c, n, k, counter));
}
}
}
6 changes: 3 additions & 3 deletions src/Geralt/Crypto/ChaCha20.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public static unsafe void Encrypt(Span<byte> ciphertext, ReadOnlySpan<byte> plai
Validation.EqualToSize(nameof(ciphertext), ciphertext.Length, plaintext.Length);
Validation.EqualToSize(nameof(nonce), nonce.Length, NonceSize);
Validation.EqualToSize(nameof(key), key.Length, KeySize);
CounterOverflow(plaintext.Length, counter);
ThrowIfCounterOverflow(plaintext.Length, counter);
Sodium.Initialize();
fixed (byte* c = ciphertext, p = plaintext, n = nonce, k = key)
{
Expand All @@ -41,7 +41,7 @@ public static unsafe void Decrypt(Span<byte> plaintext, ReadOnlySpan<byte> ciphe
Validation.EqualToSize(nameof(plaintext), plaintext.Length, ciphertext.Length);
Validation.EqualToSize(nameof(nonce), nonce.Length, NonceSize);
Validation.EqualToSize(nameof(key), key.Length, KeySize);
CounterOverflow(ciphertext.Length, counter);
ThrowIfCounterOverflow(ciphertext.Length, counter);
Sodium.Initialize();
fixed (byte* p = plaintext, c = ciphertext, n = nonce, k = key)
{
Expand All @@ -50,7 +50,7 @@ public static unsafe void Decrypt(Span<byte> plaintext, ReadOnlySpan<byte> ciphe
}
}

private static void CounterOverflow(int messageSize, uint counter)
private static void ThrowIfCounterOverflow(int messageSize, uint counter)
{
long blockCount = (-1L + messageSize + BlockSize) / BlockSize;
if (counter + blockCount > uint.MaxValue)
Expand Down
9 changes: 9 additions & 0 deletions src/Geralt/Crypto/XChaCha20.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public static unsafe void Encrypt(Span<byte> ciphertext, ReadOnlySpan<byte> plai
Validation.EqualToSize(nameof(ciphertext), ciphertext.Length, plaintext.Length);
Validation.EqualToSize(nameof(nonce), nonce.Length, NonceSize);
Validation.EqualToSize(nameof(key), key.Length, KeySize);
ThrowIfCounterOverflow(plaintext.Length, counter);
Sodium.Initialize();
fixed (byte* c = ciphertext, p = plaintext, n = nonce, k = key)
{
Expand All @@ -40,11 +41,19 @@ public static unsafe void Decrypt(Span<byte> plaintext, ReadOnlySpan<byte> ciphe
Validation.EqualToSize(nameof(plaintext), plaintext.Length, ciphertext.Length);
Validation.EqualToSize(nameof(nonce), nonce.Length, NonceSize);
Validation.EqualToSize(nameof(key), key.Length, KeySize);
ThrowIfCounterOverflow(ciphertext.Length, counter);
Sodium.Initialize();
fixed (byte* p = plaintext, c = ciphertext, n = nonce, k = key)
{
int ret = crypto_stream_xchacha20_xor_ic(p, c, (ulong)ciphertext.Length, n, counter, k);
if (ret != 0) { throw new CryptographicException("Error decrypting ciphertext."); }
}
}

private static void ThrowIfCounterOverflow(int messageSize, ulong counter)
{
long blockCount = (-1L + messageSize + BlockSize) / BlockSize;
if (ulong.MaxValue - (ulong)blockCount < counter)
throw new CryptographicException("Counter overflow prevented.");
}
}

0 comments on commit f975167

Please sign in to comment.