File: System\Security\Cryptography\BasicSymmetricCipherCsp.cs
Web Access
Project: src\src\runtime\src\libraries\System.Security.Cryptography\src\System.Security.Cryptography.csproj (System.Security.Cryptography)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using Internal.Cryptography;
using static System.Security.Cryptography.CapiHelper;

namespace System.Security.Cryptography
{
    internal sealed class BasicSymmetricCipherCsp : BasicSymmetricCipher
    {
        private readonly bool _encrypting;
        private SafeProvHandle _hProvider;
        private SafeCapiKeyHandle _hKey;

        public BasicSymmetricCipherCsp(int algId, CipherMode cipherMode, int blockSizeInBytes, byte[] key, bool addNoSaltFlag, byte[]? iv, bool encrypting, int feedbackSize, int paddingSizeInBytes)
            : base(cipherMode.GetCipherIv(iv), blockSizeInBytes, paddingSizeInBytes)
        {
            _encrypting = encrypting;

            _hProvider = AcquireSafeProviderHandle();
            _hKey = ImportCspBlob(_hProvider, algId, key, addNoSaltFlag);

            SetKeyParameter(_hKey, CryptGetKeyParamQueryType.KP_MODE, (int)cipherMode);
            if (cipherMode == CipherMode.CFB)
            {
                SetKeyParameter(_hKey, CryptGetKeyParamQueryType.KP_MODE_BITS, feedbackSize);
            }

            byte[]? currentIv = cipherMode.GetCipherIv(iv);
            if (currentIv != null)
            {
                SetKeyParameter(_hKey, CryptGetKeyParamQueryType.KP_IV, currentIv);
            }

            if (algId == CapiHelper.CALG_RC2)
            {
                SetKeyParameter(_hKey, CryptGetKeyParamQueryType.KP_EFFECTIVE_KEYLEN, key.Length * 8);
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                SafeCapiKeyHandle hKey = _hKey;
                if (hKey != null)
                {
                    _hKey = null!;
                    hKey.Dispose();
                }

                SafeProvHandle hProvider = _hProvider;
                if (hProvider != null)
                {
                    _hProvider = null!;
                    hProvider.Dispose();
                }
            }

            base.Dispose(disposing);
        }

        public override int Transform(ReadOnlySpan<byte> input, Span<byte> output)
        {
            return Transform(input, output, false);
        }

        public override int TransformFinal(ReadOnlySpan<byte> input, Span<byte> output)
        {
            Debug.Assert((input.Length % PaddingSizeInBytes) == 0);

            int numBytesWritten = 0;

            if (input.Length != 0)
            {
                numBytesWritten = Transform(input, output, true);
                Debug.Assert(numBytesWritten == input.Length);  // Our implementation of Transform() guarantees this.
            }

            Reset();

            return numBytesWritten;
        }

        private void Reset()
        {
            // Ensure we've called CryptEncrypt with the final=true flag so the handle is reset property
            EncryptData(_hKey, default, default, true);
        }

        private int Transform(ReadOnlySpan<byte> input, Span<byte> output, bool isFinal)
        {
            Debug.Assert(input.Length > 0);
            Debug.Assert((input.Length % PaddingSizeInBytes) == 0);

            int numBytesWritten;
            if (_encrypting)
            {
                numBytesWritten = EncryptData(_hKey, input, output, isFinal);
            }
            else
            {
                numBytesWritten = DecryptData(_hKey, input, output);
            }

            return numBytesWritten;
        }

        private static SafeCapiKeyHandle ImportCspBlob(SafeProvHandle safeProvHandle, int algId, byte[] rawKey, bool addNoSaltFlag)
        {
            SafeCapiKeyHandle safeKeyHandle;
            byte[] keyBlob = ToPlainTextKeyBlob(algId, rawKey);
            ImportKeyBlob(safeProvHandle, (CspProviderFlags)0, addNoSaltFlag, keyBlob, out safeKeyHandle);
            // Note if plain text import fails, .NET Framework falls back to "ExponentOfOneImport" which is not handled here
            return safeKeyHandle;
        }

        private static SafeProvHandle AcquireSafeProviderHandle()
        {
            SafeProvHandle safeProvHandle;
            var cspParams = new CspParameters((int)ProviderType.PROV_RSA_FULL);
            CapiHelper.AcquireCsp(cspParams, out safeProvHandle);
            return safeProvHandle;
        }
    }
}