// Copyright (C) 2024 Vaughn Nugent
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using VNLib.Utils.Memory;
using VNLib.Utils.Native;
using VNLib.Utils.Extensions;
using VNLib.Utils.Cryptography.Noscrypt.Random;
using VNLib.Utils.Cryptography.Noscrypt.@internal;
using NCResult = System.Int64;
namespace VNLib.Utils.Cryptography.Noscrypt
{
///
/// Initializes the native library and provides access to the native functions
///
/// An existing noscrypt library handle
/// A value that indicates if the instance owns the library handle
public unsafe sealed class NoscryptLibrary(SafeLibraryHandle Library, bool OwnsHandle) : VnDisposeable
{
public const string NoscryptDefaultLibraryName = "noscrypt";
public const string NoscryptDllPathEnvName = "NOSCRYPT_DLL_PATH";
//Constant values match the noscrypt.h header
public const int NC_SEC_KEY_SIZE = 0x20;
public const int NC_SEC_PUBKEY_SIZE = 0x20;
public const int NC_PUBKEY_SIZE = 0x20;
public const int NC_SIGNATURE_SIZE = 0x40;
public const int NC_CONV_KEY_SIZE = 0x20;
public const int NC_MESSAGE_KEY_SIZE = 0x20;
public const int NC_HMAC_KEY_SIZE = 0x20;
public const int NC_ENCRYPTION_MAC_SIZE = 0x20;
public const int NC_CONVERSATION_KEY_SIZE = 0x20;
public const int NC_CTX_ENTROPY_SIZE = 0x20;
public const int NC_SIG_ENTROPY_SIZE = 0x20;
public const uint NC_ENC_VERSION_NIP04 = 0x00000004u;
public const uint NC_ENC_VERSION_NIP44 = 0x00000002c;
public const uint NC_ENC_SET_VERSION = 0x01u;
public const uint NC_ENC_SET_IV = 0x02u;
public const uint NC_ENC_SET_NIP44_MAC_KEY = 0x03u;
public const uint NC_ENC_SET_NIP04_KEY = 0x04u;
public const NCResult NC_SUCCESS = 0x00;
public enum NCErrorCodes : long
{
NC_SUCCESS = 0,
//Generic argument related errors
E_NULL_PTR = 1,
E_INVALID_ARG = 2,
E_INVALID_CTX = 3,
E_ARGUMENT_OUT_OF_RANGE = 4,
E_OPERATION_FAILED = 5,
E_VERSION_NOT_SUPPORTED = 6,
//Cipher errors
E_CIPHER_INVALID_FORMAT = 11,
E_CIPHER_BAD_NONCE = 12,
E_CIPHER_MAC_INVALID = 13,
E_CIPHER_NO_OUTPUT = 14,
E_CIPHER_BAD_INPUT = 15,
E_CIPHER_BAD_INPUT_SIZE = 16
}
//Cipher flags
public const uint NC_UTIL_CIPHER_MODE = 0x01u;
private readonly FunctionTable _functions = FunctionTable.BuildFunctionTable(Library);
///
/// Gets a reference to the loaded function table for
/// the native library
///
internal ref readonly FunctionTable Functions
{
get
{
Check();
Library.ThrowIfClosed();
return ref _functions;
}
}
///
/// Gets a value that determines if the library has been released
///
internal bool IsClosed => Library.IsClosed || Library.IsInvalid;
///
/// Initialize a new NCContext for use. This may be done once at app startup
/// and is thread-safe for the rest of the application lifetime.
///
///
/// Initialization entropy buffer
/// The size of the buffer (must be 32 bytes)
/// The inialized context
///
///
///
public NCContext Initialize(IUnmangedHeap heap, ref readonly byte entropy32, int size)
{
ArgumentNullException.ThrowIfNull(heap);
//Entropy must be exactly 32 bytes
ArgumentOutOfRangeException.ThrowIfNotEqual(size, NC_CTX_ENTROPY_SIZE);
//Get struct size
nuint ctxSize = Functions.NCGetContextStructSize.Invoke();
//Allocate the context with the struct alignment on a heap
IntPtr ctx = heap.Alloc(1, ctxSize, true);
try
{
NCResult result;
fixed (byte* p = &entropy32)
{
result = Functions.NCInitContext.Invoke(ctx, p);
}
NCUtil.CheckResult(result, true);
Trace.WriteLine($"Initialzied noscrypt context 0x{ctx:x}");
return new NCContext(ctx, heap, this);
}
catch
{
heap.Free(ref ctx);
throw;
}
}
///
/// Initialize a new NCContext for use. This may be done once at app startup
/// and is thread-safe for the rest of the application lifetime.
///
///
/// The 32byte random seed/nonce for the noscrypt context
/// The inialized context
///
///
///
public NCContext Initialize(IUnmangedHeap heap, ReadOnlySpan enropy32)
{
return Initialize(
heap,
ref MemoryMarshal.GetReference(enropy32),
enropy32.Length
);
}
///
/// Initialize a new NCContext for use. This may be done once at app startup
/// and is thread-safe for the rest of the application lifetime.
///
///
/// The 32byte random seed/nonce for the noscrypt context
/// The inialized context
///
///
///
public NCContext Initialize(IUnmangedHeap heap, IRandomSource random)
{
ArgumentNullException.ThrowIfNull(random);
//Get random bytes for context entropy
Span entropy = stackalloc byte[NC_CTX_ENTROPY_SIZE];
random.GetRandomBytes(entropy);
return Initialize(heap, entropy);
}
///
protected override void Free()
{
if (OwnsHandle)
{
Library.Dispose();
Trace.WriteLine($"Disposed noscrypt library 0x{Library.DangerousGetHandle():x}");
}
}
///
/// Loads the native library from the specified path and initializes the
/// function table for use.
///
/// The native library path or name to load
/// The search path options
/// The loaded library instance
///
public static NoscryptLibrary Load(string path, DllImportSearchPath search)
{
//Load the native library
SafeLibraryHandle handle = SafeLibraryHandle.LoadLibrary(path, search);
Trace.WriteLine($"Loaded noscrypt library 0x{handle.DangerousGetHandle():x} from {path}");
//Create the wrapper
return new NoscryptLibrary(handle, true);
}
///
/// Loads the native library from the specified path and initializes the
/// function table for use.
///
/// The native library path or name to load
/// The loaded library instance
///
public static NoscryptLibrary Load(string path) => Load(path, DllImportSearchPath.SafeDirectories);
///
/// Attempts to load the default noscrypt library from the system search path
///
/// The loaded library instance
///
public static NoscryptLibrary LoadDefault()
{
string? libPath = Environment.GetEnvironmentVariable(NoscryptDllPathEnvName);
libPath ??= NoscryptDefaultLibraryName;
Console.WriteLine("Loading library {0}", libPath);
libPath = libPath.Replace("\"", "");
return Load(libPath);
}
}
}