Implement most of libsodium's helpers.

This commit is contained in:
Pieter-Jan Briers
2022-04-09 18:03:18 +02:00
parent 10f6adb297
commit 6429621f04
2 changed files with 318 additions and 0 deletions

View File

@@ -0,0 +1,122 @@
using System;
using System.Text;
using NUnit.Framework;
namespace SpaceWizards.Sodium.Tests;
[TestFixture]
[Parallelizable(ParallelScope.All)]
[TestOf(typeof(SodiumHelpers))]
public class HelpersTest
{
[Test]
[TestCase("foobar", "foobar", ExpectedResult = true)]
[TestCase("foobar", "foobaz", ExpectedResult = false)]
public bool TestMemoryCompare(string b1Str, string b2Str)
{
var b1 = Encoding.UTF8.GetBytes(b1Str);
var b2 = Encoding.UTF8.GetBytes(b2Str);
return SodiumHelpers.MemoryCompare(b1, b2);
}
[Test]
[TestCase("DEADBEEF", "deadbeef")]
public void TestBin2Hex(string binHex, string expectHex)
{
var bin = Convert.FromHexString(binHex);
var hex = SodiumHelpers.Bin2Hex(bin);
Assert.That(hex, Is.EqualTo(expectHex).IgnoreCase);
}
[Test]
[TestCase("F9FD8217F8F00EE5", SodiumBase64Variant.Original, ExpectedResult = "+f2CF/jwDuU=")]
[TestCase("F9FD8217F8F00EE5", SodiumBase64Variant.OriginalNoPadding, ExpectedResult = "+f2CF/jwDuU")]
[TestCase("F9FD8217F8F00EE5", SodiumBase64Variant.OriginalUrlSafe, ExpectedResult = "-f2CF_jwDuU=")]
[TestCase("F9FD8217F8F00EE5", SodiumBase64Variant.OriginalUrlSafeNoPadding, ExpectedResult = "-f2CF_jwDuU")]
public string TestBin2Base64(string binHex, SodiumBase64Variant variant)
{
var bin = Convert.FromHexString(binHex);
return SodiumHelpers.Bin2Base64(bin, variant);
}
[Test]
[TestCase("00000000000000000000000000000000", ExpectedResult = "01000000000000000000000000000000")]
[TestCase("FF000000000000000000000000000000", ExpectedResult = "00010000000000000000000000000000")]
[TestCase("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00", ExpectedResult = "00000000000000000000000000000001")]
public string TestIncrement(string nHex)
{
var n = Convert.FromHexString(nHex);
SodiumHelpers.Increment(n);
return Convert.ToHexString(n);
}
[Test]
// @formatter:off
[TestCase("00000000000000000000000000000000", "00000000000000000000000000000000", ExpectedResult = "00000000000000000000000000000000")]
[TestCase("FF000000000000000000000000000000", "FF000000000000000000000000000000", ExpectedResult = "FE010000000000000000000000000000")]
[TestCase("576F809E99346A475F20727603A1D41C", "6AE9809052ACEA8F7535E6ADE2613B6F", ExpectedResult = "C158012FECE054D7D4555824E602108C")]
// @formatter:on
public string TestAdd(string aHex, string bHex)
{
var a = Convert.FromHexString(aHex);
var b = Convert.FromHexString(bHex);
SodiumHelpers.Add(a, b);
return Convert.ToHexString(a);
}
[Test]
// @formatter:off
[TestCase("00000000000000000000000000000000", "00000000000000000000000000000000", ExpectedResult = "00000000000000000000000000000000")]
[TestCase("FE010000000000000000000000000000", "FF000000000000000000000000000000", ExpectedResult = "FF000000000000000000000000000000")]
[TestCase("6AE9809052ACEA8F7535E6ADE2613B6F", "576F809E99346A475F20727603A1D41C", ExpectedResult = "137A00F2B877804816157437DFC06652")]
// @formatter:on
public string TestSub(string aHex, string bHex)
{
var a = Convert.FromHexString(aHex);
var b = Convert.FromHexString(bHex);
SodiumHelpers.Sub(a, b);
return Convert.ToHexString(a);
}
[Test]
[TestCase("00000000000000000000000000000000", "00000000000000000000000000000000", ExpectedResult = 0)]
[TestCase("FE010000000000000000000000000000", "FF000000000000000000000000000000", ExpectedResult = 1)]
[TestCase("576F809E99346A475F20727603A1D41C", "6AE9809052ACEA8F7535E6ADE2613B6F", ExpectedResult = -1)]
public int TestCompare(string aHex, string bHex)
{
var a = Convert.FromHexString(aHex);
var b = Convert.FromHexString(bHex);
return SodiumHelpers.Compare(a, b);
}
[Test]
[TestCase("00000000000000000000000000000000", ExpectedResult = true)]
[TestCase("FE010000000000000000000000000000", ExpectedResult = false)]
public bool TestIsZero(string nHex)
{
var n = Convert.FromHexString(nHex);
return SodiumHelpers.IsZero(n);
}
[Test]
public void StackZero()
{
// I mean there's not a whole lot I can verify here.
// I guess just test that it doesn't crash?
SodiumHelpers.StackZero(32);
}
}

View File

@@ -0,0 +1,196 @@
using System.Buffers;
using System.Text;
namespace SpaceWizards.Sodium;
using static Interop.Libsodium;
public static class SodiumHelpers
{
static SodiumHelpers()
{
SodiumCore.EnsureInit();
}
// TODO: hex2bin and base642bin
/// <summary>
/// <c>sodium_memcmp</c>
/// </summary>
public static unsafe bool MemoryCompare(ReadOnlySpan<byte> b1, ReadOnlySpan<byte> b2)
{
if (b1.Length != b2.Length)
throw new ArgumentException("Both input spans must be the same length.");
fixed (byte* b1Ptr = b1)
fixed (byte* b2Ptr = b2)
{
return sodium_memcmp(b1Ptr, b2Ptr, (nuint)b1.Length) == 0;
}
}
/// <summary>
/// <c>sodium_bin2hex</c>
/// </summary>
/// <returns>The subsection of <paramref name="hex"/> that was filled, EXCLUDING null terminator.</returns>
/// <remarks>
/// If you need a version that gives back a string, just use the BCL <see cref="Convert.ToHexString(ReadOnlySpan{byte})"/> instead.
/// </remarks>
public static unsafe Span<byte> Bin2Hex(Span<byte> hex, ReadOnlySpan<byte> bin)
{
var needSize = (long)bin.Length * 2 + 1;
if (hex.Length < needSize)
throw new ArgumentException("Hex must be at least (bin.Length * 2) + 1 bytes.");
fixed (byte* hexPtr = hex)
fixed (byte* binPtr = bin)
{
sodium_bin2hex((sbyte*)hexPtr, (nuint)hex.Length, binPtr, (nuint)bin.Length);
}
return hex[..(int)(needSize - 1)];
}
/// <summary>
/// <c>sodium_bin2hex</c>, returns string.
/// </summary>
public static string Bin2Hex(ReadOnlySpan<byte> bin)
{
var buffer = ArrayPool<byte>.Shared.Rent(bin.Length * 2 + 1);
try
{
var data = Bin2Hex(buffer, bin);
return Encoding.ASCII.GetString(data);
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}
public static int Base64EncodedLength(int length, SodiumBase64Variant variant) =>
checked((int)sodium_base64_ENCODED_LEN((ulong)length, (ulong)variant));
/// <summary>
/// <c>sodium_bin2base64</c>
/// </summary>
/// <returns>The subsection of <paramref name="b64"/> that was filled, EXCLUDING null terminator.</returns>
public static unsafe Span<byte> Bin2Base64(Span<byte> b64, ReadOnlySpan<byte> bin, SodiumBase64Variant variant)
{
var needSize = sodium_base64_ENCODED_LEN((ulong)bin.Length, (ulong)variant);
if ((ulong)b64.Length < needSize)
throw new ArgumentException("B64 is too short for encoded data");
fixed (byte* b64Ptr = b64)
fixed (byte* binPtr = bin)
{
sodium_bin2base64((sbyte*)b64Ptr, (nuint)b64.Length, binPtr, (nuint)bin.Length, (int)variant);
}
return b64[..(int)(needSize - 1)];
}
/// <summary>
/// <c>sodium_bin2hex</c>, returns string.
/// </summary>
public static string Bin2Base64(ReadOnlySpan<byte> bin, SodiumBase64Variant variant)
{
var buffer = ArrayPool<byte>.Shared.Rent(Base64EncodedLength(bin.Length, variant));
try
{
var data = Bin2Base64(buffer, bin, variant);
return Encoding.ASCII.GetString(data);
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}
/// <summary>
/// <c>sodium_increment</c>
/// </summary>
public static unsafe void Increment(Span<byte> n)
{
fixed (byte* nPtr = n)
{
sodium_increment(nPtr, (nuint)n.Length);
}
}
/// <summary>
/// <c>sodium_add</c>
/// </summary>
public static unsafe void Add(Span<byte> a, ReadOnlySpan<byte> b)
{
if (a.Length != b.Length)
throw new ArgumentException("Both input numbers must be the same length.");
fixed (byte* aPtr = a)
fixed (byte* bPtr = b)
{
sodium_add(aPtr, bPtr, (nuint)a.Length);
}
}
/// <summary>
/// <c>sodium_sub</c>
/// </summary>
public static unsafe void Sub(Span<byte> a, ReadOnlySpan<byte> b)
{
if (a.Length != b.Length)
throw new ArgumentException("Both input numbers must be the same length.");
fixed (byte* aPtr = a)
fixed (byte* bPtr = b)
{
sodium_sub(aPtr, bPtr, (nuint)a.Length);
}
}
/// <summary>
/// <c>sodium_compare</c>
/// </summary>
public static unsafe int Compare(ReadOnlySpan<byte> b1, ReadOnlySpan<byte> b2)
{
if (b1.Length != b2.Length)
throw new ArgumentException("Both input numbers must be the same length.");
fixed (byte* b1Ptr = b1)
fixed (byte* b2Ptr = b2)
{
return sodium_compare(b1Ptr, b2Ptr, (nuint)b1.Length);
}
}
/// <summary>
/// <c>sodium_is_zero</c>
/// </summary>
public static unsafe bool IsZero(ReadOnlySpan<byte> n)
{
fixed (byte* nPtr = n)
{
return sodium_is_zero(nPtr, (nuint)n.Length) == 1;
}
}
/// <summary>
/// <c>sodium_stackzero</c>
/// </summary>
public static void StackZero(int len)
{
sodium_stackzero((nuint)len);
}
}
public enum SodiumBase64Variant
{
Original = sodium_base64_VARIANT_ORIGINAL,
OriginalNoPadding = sodium_base64_VARIANT_ORIGINAL_NO_PADDING,
OriginalUrlSafe = sodium_base64_VARIANT_URLSAFE,
OriginalUrlSafeNoPadding = sodium_base64_VARIANT_URLSAFE_NO_PADDING,
}