Write out the high-level API I need.
This commit is contained in:
@@ -3,3 +3,12 @@
|
||||
A .NET [libsodium](https://libsodium.gitbook.io/doc/) binding that doesn't suck. Actually exposes the native API so you don't have to cry yourself to sleep in unnecessary allocations or OOP nonsense.
|
||||
|
||||
Currently depends on the ["libsodium" NuGet package](https://www.nuget.org/packages/libsodium/) provided by [Sodium.Core](https://github.com/ektrah/libsodium-core) for the native library. They say you shouldn't depend on it directly but I am too lazy to compile them myself so deal with it.
|
||||
|
||||
# API Shape
|
||||
|
||||
There is a low-level API in `SpaceWizards.Sodium.Interop`. This is a raw P/Invoke binding, hope you like pointers.
|
||||
|
||||
The high-level API in `SpaceWizards.Sodium` generally has two variants: span with return code, or `byte[]` with exceptions. Note that the former still throws exceptions if you pass spans that are too small, but otherwise failing return codes from libsodium are passed through so you can handle them. This is just what I decided to settle on to keep API size down.
|
||||
|
||||
|
||||
Also, if it wasn't obvious, the only API wrapped is the ones I needed at the moment. PRs welcome I guess.
|
||||
|
||||
112
SpaceWizards.Sodium/CryptoAeadXChaCha20Poly1305Ietf.cs
Normal file
112
SpaceWizards.Sodium/CryptoAeadXChaCha20Poly1305Ietf.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
namespace SpaceWizards.Sodium;
|
||||
using static Interop.Libsodium;
|
||||
|
||||
/// <summary>
|
||||
/// Wrappers around the <c>crypto_aead_xchacha20poly1305_ietf_</c> APIs.
|
||||
/// </summary>
|
||||
public static class CryptoAeadXChaCha20Poly1305Ietf
|
||||
{
|
||||
static CryptoAeadXChaCha20Poly1305Ietf()
|
||||
{
|
||||
SodiumCore.EnsureInit();
|
||||
}
|
||||
|
||||
public const int NoncePublicBytes = (int)crypto_aead_xchacha20poly1305_ietf_NPUBBYTES;
|
||||
public const int KeyBytes = (int)crypto_aead_xchacha20poly1305_ietf_KEYBYTES;
|
||||
public const int AddBytes = (int)crypto_aead_xchacha20poly1305_ietf_ABYTES;
|
||||
|
||||
public static unsafe void Keygen(Span<byte> key)
|
||||
{
|
||||
if (key.Length != KeyBytes)
|
||||
throw new ArgumentException($"Key must be {nameof(KeyBytes)} bytes");
|
||||
|
||||
fixed (byte* k = key)
|
||||
{
|
||||
crypto_aead_xchacha20poly1305_ietf_keygen(k);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] Keygen()
|
||||
{
|
||||
var key = new byte[KeyBytes];
|
||||
Keygen(key);
|
||||
return key;
|
||||
}
|
||||
|
||||
public static unsafe bool Encrypt(
|
||||
Span<byte> cipher,
|
||||
out int cipherLength,
|
||||
ReadOnlySpan<byte> message,
|
||||
ReadOnlySpan<byte> additionalData,
|
||||
ReadOnlySpan<byte> noncePublic,
|
||||
ReadOnlySpan<byte> key)
|
||||
{
|
||||
if (cipher.Length < checked(message.Length + AddBytes))
|
||||
throw new ArgumentException("Destination is too short");
|
||||
|
||||
if (key.Length != KeyBytes)
|
||||
throw new ArgumentException($"Key must be {nameof(KeyBytes)} bytes");
|
||||
|
||||
if (noncePublic.Length != NoncePublicBytes)
|
||||
throw new ArgumentException($"Nonce must be {nameof(NoncePublicBytes)} bytes");
|
||||
|
||||
fixed (byte* c = cipher)
|
||||
fixed (byte* m = message)
|
||||
fixed (byte* ad = additionalData)
|
||||
fixed (byte* npub = noncePublic)
|
||||
fixed (byte* k = key)
|
||||
{
|
||||
ulong clen;
|
||||
var ret = crypto_aead_xchacha20poly1305_ietf_encrypt(
|
||||
c, &clen,
|
||||
m, (ulong)message.Length,
|
||||
ad, (ulong)additionalData.Length,
|
||||
null,
|
||||
npub,
|
||||
k);
|
||||
|
||||
cipherLength = (int)clen;
|
||||
return ret == 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe bool Decrypt(
|
||||
Span<byte> message,
|
||||
out int messageLength,
|
||||
ReadOnlySpan<byte> cipher,
|
||||
ReadOnlySpan<byte> additionalData,
|
||||
ReadOnlySpan<byte> noncePublic,
|
||||
ReadOnlySpan<byte> key)
|
||||
{
|
||||
if (cipher.Length < AddBytes)
|
||||
throw new ArgumentException("Input is too short");
|
||||
|
||||
if (message.Length < cipher.Length - AddBytes)
|
||||
throw new ArgumentException("Output is too short");
|
||||
|
||||
if (key.Length != KeyBytes)
|
||||
throw new ArgumentException($"Key must be {nameof(KeyBytes)} bytes");
|
||||
|
||||
if (noncePublic.Length != NoncePublicBytes)
|
||||
throw new ArgumentException($"Nonce must be {nameof(NoncePublicBytes)} bytes");
|
||||
|
||||
fixed (byte* c = cipher)
|
||||
fixed (byte* m = message)
|
||||
fixed (byte* ad = additionalData)
|
||||
fixed (byte* npub = noncePublic)
|
||||
fixed (byte* k = key)
|
||||
{
|
||||
ulong mlen;
|
||||
var ret = crypto_aead_xchacha20poly1305_ietf_decrypt(
|
||||
m, &mlen,
|
||||
null,
|
||||
c, (ulong)cipher.Length,
|
||||
ad, (ulong)additionalData.Length,
|
||||
npub,
|
||||
k);
|
||||
|
||||
messageLength = (int)mlen;
|
||||
return ret == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
94
SpaceWizards.Sodium/CryptoBox.cs
Normal file
94
SpaceWizards.Sodium/CryptoBox.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
namespace SpaceWizards.Sodium;
|
||||
|
||||
using static Interop.Libsodium;
|
||||
|
||||
/// <summary>
|
||||
/// Wrappers around the <c>crypto_box</c> APIs.
|
||||
/// </summary>
|
||||
public static class CryptoBox
|
||||
{
|
||||
static CryptoBox()
|
||||
{
|
||||
SodiumCore.EnsureInit();
|
||||
}
|
||||
|
||||
public const int SealBytes = (int)crypto_box_SEALBYTES;
|
||||
public const int PublicKeyBytes = (int)crypto_box_PUBLICKEYBYTES;
|
||||
public const int SecretKeyBytes = (int)crypto_box_SECRETKEYBYTES;
|
||||
|
||||
public static unsafe bool KeyPair(Span<byte> publicKey, Span<byte> secretKey)
|
||||
{
|
||||
if (publicKey.Length != PublicKeyBytes)
|
||||
throw new ArgumentException($"Public key must be {nameof(PublicKeyBytes)} bytes.");
|
||||
|
||||
if (secretKey.Length != SecretKeyBytes)
|
||||
throw new ArgumentException($"Secret key must be {nameof(SecretKeyBytes)} bytes.");
|
||||
|
||||
fixed (byte* pk = publicKey)
|
||||
fixed (byte* sk = secretKey)
|
||||
{
|
||||
return crypto_box_keypair(pk, sk) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe bool Seal(Span<byte> cipher, ReadOnlySpan<byte> message, ReadOnlySpan<byte> publicKey)
|
||||
{
|
||||
if (cipher.Length < checked(message.Length + SealBytes))
|
||||
throw new ArgumentException("Destination is too short");
|
||||
|
||||
fixed (byte* c = cipher)
|
||||
fixed (byte* m = message)
|
||||
fixed (byte* pk = publicKey)
|
||||
{
|
||||
return crypto_box_seal(c, m, (ulong)message.Length, pk) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] Seal(ReadOnlySpan<byte> message, ReadOnlySpan<byte> publicKey)
|
||||
{
|
||||
var cipher = new byte[message.Length + SealBytes];
|
||||
if (!Seal(cipher, message, publicKey))
|
||||
throw new SodiumException("Seal failed");
|
||||
|
||||
return cipher;
|
||||
}
|
||||
|
||||
public static unsafe bool SealOpen(
|
||||
Span<byte> message,
|
||||
ReadOnlySpan<byte> cipher,
|
||||
ReadOnlySpan<byte> publicKey,
|
||||
ReadOnlySpan<byte> secretKey)
|
||||
{
|
||||
if (cipher.Length < SealBytes)
|
||||
throw new ArgumentException("Input is too short");
|
||||
|
||||
if (message.Length < (cipher.Length - SealBytes))
|
||||
throw new ArgumentException("Destination is too short");
|
||||
|
||||
if (publicKey.Length != PublicKeyBytes)
|
||||
throw new ArgumentException($"Public key must be {nameof(PublicKeyBytes)} bytes.");
|
||||
|
||||
if (secretKey.Length != SecretKeyBytes)
|
||||
throw new ArgumentException($"Secret key must be {nameof(SecretKeyBytes)} bytes.");
|
||||
|
||||
fixed (byte* m = message)
|
||||
fixed (byte* c = cipher)
|
||||
fixed (byte* pk = publicKey)
|
||||
fixed (byte* sk = secretKey)
|
||||
{
|
||||
return crypto_box_seal_open(m, c, (ulong)cipher.Length, pk, sk) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] SealOpen(ReadOnlySpan<byte> cipher, ReadOnlySpan<byte> publicKey, ReadOnlySpan<byte> secretKey)
|
||||
{
|
||||
if (cipher.Length < SealBytes)
|
||||
throw new ArgumentException("Input is too short");
|
||||
|
||||
var message = new byte[cipher.Length - SealBytes];
|
||||
if (!SealOpen(message, cipher, publicKey, secretKey))
|
||||
throw new SodiumException("SealOpen failed");
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
||||
26
SpaceWizards.Sodium/SodiumCore.cs
Normal file
26
SpaceWizards.Sodium/SodiumCore.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using static SpaceWizards.Sodium.Interop.Libsodium;
|
||||
|
||||
namespace SpaceWizards.Sodium;
|
||||
|
||||
public static class SodiumCore
|
||||
{
|
||||
/// <summary>
|
||||
/// Directly call <see cref="sodium_init"/>.
|
||||
/// </summary>
|
||||
/// <returns>0 on success, 1 if already initialized, -1 on initialize failure.</returns>
|
||||
/// <seealso cref="EnsureInit"/>
|
||||
public static int Init()
|
||||
{
|
||||
return sodium_init();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to ensure libsodium is initialized, throwing if it fails to initialize.
|
||||
/// </summary>
|
||||
/// <exception cref="SodiumInitException">Thrown if initialization of libsodium failed.</exception>
|
||||
public static void EnsureInit()
|
||||
{
|
||||
if (Init() == -1)
|
||||
throw new SodiumInitException("Failed to init libsodium!");
|
||||
}
|
||||
}
|
||||
16
SpaceWizards.Sodium/SodiumException.cs
Normal file
16
SpaceWizards.Sodium/SodiumException.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace SpaceWizards.Sodium;
|
||||
|
||||
public sealed class SodiumException : Exception
|
||||
{
|
||||
public SodiumException()
|
||||
{
|
||||
}
|
||||
|
||||
public SodiumException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public SodiumException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
}
|
||||
17
SpaceWizards.Sodium/SodiumInitException.cs
Normal file
17
SpaceWizards.Sodium/SodiumInitException.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace SpaceWizards.Sodium;
|
||||
|
||||
[Serializable]
|
||||
public sealed class SodiumInitException : Exception
|
||||
{
|
||||
public SodiumInitException()
|
||||
{
|
||||
}
|
||||
|
||||
public SodiumInitException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public SodiumInitException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,17 @@
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<PackageId>SpaceWizards.Sodium</PackageId>
|
||||
<Version>0.1.0</Version>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<Description>Simple API binding for libsodium.</Description>
|
||||
<NoWarn>CS1591</NoWarn>
|
||||
<PackageTags>libsodium</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SpaceWizards.Sodium.Interop\SpaceWizards.Sodium.Interop.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user