// 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 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 /// /// /// Cipher creation flags /// The cipher specification version public sealed class NoscryptCipher(NCContext ctx, NoscryptCipherVersion version, NoscryptCipherFlags flags) : VnDisposeable { private IMemoryHandle? _ivBuffer; private readonly nint _cipher = NCUtilCipher.Alloc(ctx, (uint)version, (uint)flags); /// /// The cipher standard version used by this instance /// public NoscryptCipherVersion Version => version; /// /// Gets the flags set for the cipher instance /// public uint GetFlags() => NCUtilCipher.GetFlags(ctx, _cipher); /// /// Gets the cipher's initilaization vector size (or nonce) /// /// The size of the IV in bytes public int GetIvSize() => NCUtilCipher.GetIvSize(ctx, _cipher); /// /// 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) { NCUtilCipher.InitCipher(ctx, _cipher, inputPtr, inputSize); NCUtilCipher.Update(ctx, _cipher, 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)NCUtilCipher.GetOutputSize(ctx, _cipher)); /// /// 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)NCUtilCipher.ReadOutput(ctx, _cipher, 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(ctx.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. */ NCUtilCipher.SetProperty( ctx, _cipher, NC_ENC_SET_IV, ref buffer.GetReference(), (uint)buffer.Length ); } catch { buffer.Dispose(); throw; } return buffer; } /// protected override void Free() { NCUtilCipher.Free(ctx, _cipher); _ivBuffer?.Dispose(); } } }