File: System\Security\Cryptography\AesImplementation.OpenSsl.cs
Web Access
Project: src\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 System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
 
namespace System.Security.Cryptography
{
    internal sealed partial class AesImplementation
    {
        private static UniversalCryptoTransform CreateTransformCore(
            CipherMode cipherMode,
            PaddingMode paddingMode,
            ReadOnlySpan<byte> key,
            byte[]? iv,
            int blockSize,
            int paddingSize,
            int feedback,
            bool encrypting)
        {
            // The algorithm pointer is a static pointer, so not having any cleanup code is correct.
            IntPtr algorithm = GetAlgorithm(key.Length * 8, feedback * 8, cipherMode);
 
            BasicSymmetricCipher cipher = new OpenSslCipher(algorithm, cipherMode, blockSize, paddingSize, key, iv, encrypting);
            return UniversalCryptoTransform.Create(paddingMode, cipher, encrypting);
        }
 
        private static OpenSslCipherLite CreateLiteCipher(
            CipherMode cipherMode,
            ReadOnlySpan<byte> key,
            ReadOnlySpan<byte> iv,
            int blockSize,
            int paddingSize,
            int feedback,
            bool encrypting)
        {
            IntPtr algorithm = GetAlgorithm(key.Length * 8, feedback * 8, cipherMode);
            return new OpenSslCipherLite(algorithm, blockSize, paddingSize, key, iv, encrypting);
        }
 
        protected override void EncryptKeyWrapPaddedCore(ReadOnlySpan<byte> source, Span<byte> destination)
        {
            int written = KeyWrap(source, destination, enc: 1);
            Debug.Assert(written == destination.Length);
        }
 
        protected override int DecryptKeyWrapPaddedCore(ReadOnlySpan<byte> source, Span<byte> destination)
        {
            return KeyWrap(source, destination, enc: 0);
        }
 
        private int KeyWrap(ReadOnlySpan<byte> source, Span<byte> destination, int enc)
        {
            Debug.Assert(enc is 0 or 1);
 
            SafeEvpCipherCtxHandle ctx = GetKey().UseKey(
                state: enc,
                static (enc, key) =>
                {
                    int keySizeInBits = key.Length * 8;
 
                    IntPtr algorithm = GetKeyWrapAlgorithm(keySizeInBits);
 
                    SafeEvpCipherCtxHandle ctx = Interop.Crypto.EvpCipherCreate(
                        algorithm,
                        ref MemoryMarshal.GetReference(key),
                        key.Length * 8,
                        ref MemoryMarshal.GetReference(ReadOnlySpan<byte>.Empty),
                        enc);
 
                    if (ctx.IsInvalid)
                    {
                        ctx.Dispose();
                        throw Interop.Crypto.CreateOpenSslCryptographicException();
                    }
 
                    return ctx;
                });
 
            int written;
 
            using (ctx)
            {
                // OpenSSL AES-KWP requires that the destination be at least as large as the source length plus the block size.
                const int AesBlockSizeBytes = 16;
                using (CryptoPoolLease lease = CryptoPoolLease.RentConditionally(
                    checked(source.Length + AesBlockSizeBytes),
                    destination,
                    skipClearIfNotRented: true))
                {
                    bool ret = Interop.Crypto.EvpCipherUpdate(
                        ctx,
                        lease.Span,
                        out written,
                        source);
 
                    if (!ret)
                    {
                        throw Interop.Crypto.CreateOpenSslCryptographicException();
                    }
 
                    if (lease.IsRented)
                    {
                        if (written > destination.Length)
                        {
                            Debug.Fail("Wrote more bytes than expected");
                            throw new CryptographicException();
                        }
 
                        lease.Span.Slice(0, written).CopyTo(destination);
                    }
 
                    Debug.Assert(written > 0);
                }
 
                // Experimentation and code inspection show that EVP_CipherFinal_ex is not needed here,
                // the work is done in EVP_CipherUpdate.
                // Since AES-KW(P) involves multiple passes over the data, where the end of each pass
                // stores a tag/checksum back in the beginning of the buffer, it makes sense that only
                // one of Update or Final could write data, and they chose to go with Update.
                //
                // As the call to Final does not yield more data, and we're about to dispose the context,
                // don't bother making the call.
            }
 
            return written;
        }
 
        private static IntPtr GetAlgorithm(int keySize, int feedback, CipherMode cipherMode) =>
            (keySize, cipherMode) switch
            {
                // Neither OpenSSL nor Cng Aes support CTS mode.
 
                (128, CipherMode.CBC) => Interop.Crypto.EvpAes128Cbc(),
                (128, CipherMode.ECB) => Interop.Crypto.EvpAes128Ecb(),
                (128, CipherMode.CFB) when feedback == 8 => Interop.Crypto.EvpAes128Cfb8(),
                (128, CipherMode.CFB) when feedback == 128 => Interop.Crypto.EvpAes128Cfb128(),
 
                (192, CipherMode.CBC) => Interop.Crypto.EvpAes192Cbc(),
                (192, CipherMode.ECB) => Interop.Crypto.EvpAes192Ecb(),
                (192, CipherMode.CFB) when feedback == 8 => Interop.Crypto.EvpAes192Cfb8(),
                (192, CipherMode.CFB) when feedback == 128 => Interop.Crypto.EvpAes192Cfb128(),
 
                (256, CipherMode.CBC) => Interop.Crypto.EvpAes256Cbc(),
                (256, CipherMode.ECB) => Interop.Crypto.EvpAes256Ecb(),
                (256, CipherMode.CFB) when feedback == 8 => Interop.Crypto.EvpAes256Cfb8(),
                (256, CipherMode.CFB) when feedback == 128 => Interop.Crypto.EvpAes256Cfb128(),
 
                _ => throw (keySize == 128 || keySize == 192 || keySize == 256 ? (Exception)
                        new NotSupportedException() :
                        new CryptographicException(SR.Cryptography_InvalidKeySize)),
            };
 
        private static IntPtr GetKeyWrapAlgorithm(int keySize) =>
            keySize switch
            {
                128 => Interop.Crypto.EvpAes128WrapPad(),
                192 => Interop.Crypto.EvpAes192WrapPad(),
                256 => Interop.Crypto.EvpAes256WrapPad(),
                _ => throw new CryptographicException(SR.Cryptography_InvalidKeySize),
            };
    }
}