// 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.Threading;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using VNLib.Utils.Memory;
using VNLib.Utils.Cryptography.Noscrypt.Random;
using static VNLib.Utils.Cryptography.Noscrypt.NoscryptLibrary;
namespace VNLib.Utils.Cryptography.Noscrypt.Encryption
{
///
/// The noscrypt util cipher wapper
///
/// A reference to the library context object
/// A pointer to an existing cipher context that this instance owns
/// The cipher specification version
public class NoscryptMessageCipher : SafeHandleMinusOneIsInvalid
{
private readonly NCContext _context;
private readonly NoscryptCipherVersion _version;
private IMemoryHandle? _ivBuffer;
private NoscryptMessageCipher(NCContext ctx, nint cipher, NoscryptCipherVersion version)
: base(ownsHandle: true)
{
SetHandle(cipher);
_context = ctx;
_version = version;
}
///
/// Allocates and initializes a new cipher instance using the specified
///
/// A reference to the noscrypt library context
/// The encryption standard to use
/// The raw cipher flags to the pass to the cipher initialization function
/// A new instance
public static NoscryptMessageCipher Create(NCContext context, NoscryptCipherVersion version, NoscryptCipherFlags flags)
{
return new(
context,
NCCipherUtil.Alloc(context, (uint)version, (uint)flags),
version
);
}
///
/// The cipher standard version used by this instance
///
public NoscryptCipherVersion Version => _version;
///
/// Gets the flags set for the cipher instance
///
public uint GetFlags() => NCCipherUtil.GetFlags(_context, handle);
///
/// Gets the cipher's initilaization vector size (or nonce)
///
/// The size of the IV in bytes
public int GetIvSize() => NCCipherUtil.GetIvSize(_context, handle);
///
/// Gets the internal heap buffer that holds the cipher's initalization
/// vector.
///
/// The mutable span of the cipher's IV buffer
public Span IvBuffer
{
get => LazyInitializer.EnsureInitialized(ref _ivBuffer, AllocIvBuffer).Span;
}
///
/// Sets the cipher's initialization vector to a random value using
/// the specified random source
///
/// The random source
public void SetRandomIv(IRandomSource rng)
{
ArgumentNullException.ThrowIfNull(rng);
rng.GetRandomBytes(IvBuffer);
}
///
/// Performs the cipher operation on the input data using the specified
/// local and remote keys.
///
/// The secret key of the local user
/// The public key of the remote user
/// A pointer to the first byte in the buffer sequence
/// The size of the input buffer in bytes
///
///
/// If the flag is
/// set, this function may be considered independent and called repeatedly.
///
public unsafe void Update(
ref readonly NCSecretKey localKey,
ref readonly NCPublicKey remoteKey,
ref readonly byte inputData,
uint inputSize
)
{
if (Unsafe.IsNullRef(in localKey))
{
throw new ArgumentNullException(nameof(localKey));
}
if (Unsafe.IsNullRef(in remoteKey))
{
throw new ArgumentNullException(nameof(remoteKey));
}
if (Unsafe.IsNullRef(in inputData))
{
throw new ArgumentNullException(nameof(inputData));
}
/*
* Initializing the cipher requires the context holding a pointer
* to the input data, so it has to be pinned in a fixed statment
* for the duration of the update operation.
*
* So init and update must be done as an atomic operation.
*
* If ciphers have the Reusable flag set this function may be called
* repeatedly. The results of this operation can be considered
* independent.
*/
fixed (byte* inputPtr = &inputData)
{
NCCipherUtil.InitCipher(_context, handle, inputPtr, inputSize);
NCCipherUtil.Update(_context, handle, in localKey, in remoteKey);
}
}
///
/// Performs the cipher operation on the input data using the specified
/// local and remote keys.
///
/// The secret key of the local user
/// The public key of the remote user
/// The buffer sequence to read the input data from
///
public void Update(
ref readonly NCSecretKey localKey,
ref readonly NCPublicKey remoteKey,
ReadOnlySpan input
)
{
Update(
in localKey,
in remoteKey,
inputData: ref MemoryMarshal.GetReference(input), //If empty, null ref will throw
inputSize: (uint)input.Length
);
}
///
/// Gets the size of the output buffer required to read the cipher output
///
/// The size of the output in bytes
public int GetOutputSize() => checked((int)NCCipherUtil.GetOutputSize(_context, handle));
///
/// Reads the output data from the cipher into the specified buffer
///
/// A reference to the first byte in the buffer sequence
/// The size of the buffer sequence
/// The number of bytes written to the buffer
///
public int ReadOutput(ref byte outputData, int size)
{
ArgumentOutOfRangeException.ThrowIfLessThan(size, GetOutputSize());
return checked((int)NCCipherUtil.ReadOutput(_context, handle, ref outputData, (uint)size));
}
///
/// Reads the output data from the cipher into the specified buffer
///
/// The buffer sequence to write output data to
/// The number of bytes written to the buffer
///
public int ReadOutput(Span buffer)
{
return ReadOutput(
ref MemoryMarshal.GetReference(buffer),
buffer.Length
);
}
private IMemoryHandle AllocIvBuffer()
{
//Use the context heap to allocate the internal iv buffer
MemoryHandle buffer = MemoryUtil.SafeAlloc(_context.Heap, GetIvSize(), zero: true);
try
{
/*
* Assign the buffer reference to the cipher context
*
* NOTE: This pointer will be held as long as the cipher
* context is allocated. So the buffer must be held until
* the cipher is freed. Because of this an umnanaged heap
* buffer is required so we don't need to pin managed memory
* nor worry about the GC moving the buffer.
*/
NCCipherUtil.SetProperty(
_context,
DangerousGetHandle(),
NC_ENC_SET_IV,
ref buffer.GetReference(),
(uint)buffer.Length
);
}
catch
{
buffer.Dispose();
throw;
}
return buffer;
}
///
protected override bool ReleaseHandle()
{
NCCipherUtil.Free(_context, handle);
_ivBuffer?.Dispose();
return true;
}
}
}