// 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; } } }