1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
4 using System.Diagnostics;
5 using Internal.Cryptography;
6 using Internal.NativeCrypto;
8 namespace System.Security.Cryptography
11 // Common infrastructure for crypto symmetric algorithms that use Cng.
13 internal struct CngSymmetricAlgorithmCore
16 /// Configures the core to use plaintext keys (to be auto-generated when first needed.)
18 public CngSymmetricAlgorithmCore(ICngSymmetricAlgorithm outer)
22 _keyName = null; // Setting _keyName to null signifies that this object is based on a plaintext key, not a stored CNG key.
24 _optionOptions = CngKeyOpenOptions.None;
28 /// Constructs the core to use a stored CNG key.
30 public CngSymmetricAlgorithmCore(ICngSymmetricAlgorithm outer, string keyName, CngProvider provider, CngKeyOpenOptions openOptions)
32 ArgumentNullException.ThrowIfNull(keyName);
33 ArgumentNullException.ThrowIfNull(provider);
39 _optionOptions = openOptions;
41 using (CngKey cngKey = ProduceCngKey())
43 CngAlgorithm actualAlgorithm = cngKey.Algorithm;
44 string algorithm = _outer.GetNCryptAlgorithmIdentifier();
46 if (algorithm != actualAlgorithm.Algorithm)
47 throw new CryptographicException(SR.Format(SR.Cryptography_CngKeyWrongAlgorithm, actualAlgorithm.Algorithm, algorithm));
49 _outer.BaseKeySize = cngKey.KeySize;
54 /// Note! This can and likely will throw if the algorithm was given a hardware-based key.
56 public byte[] GetKeyIfExportable()
60 return _outer.BaseKey;
64 using (CngKey cngKey = ProduceCngKey())
66 return cngKey.GetSymmetricKeyDataIfExportable(_outer.GetNCryptAlgorithmIdentifier());
71 public void SetKey(byte[] key)
74 _keyName = null; // Setting _keyName to null signifies that this object is now based on a plaintext key, not a stored CNG key.
77 public void SetKeySize(int keySize, ICngSymmetricAlgorithm outer)
79 // Warning: This gets invoked once before "this" is initialized, due to Aes(), DES(), etc., setting the KeySize property in their
80 // nullary constructor. That's why we require "outer" being passed as parameter.
81 Debug.Assert(_outer == null || _outer == outer);
83 outer.BaseKeySize = keySize;
84 _keyName = null; // Setting _keyName to null signifies that this object is now based on a plaintext key, not a stored CNG key.
87 public void GenerateKey()
89 byte[] key = RandomNumberGenerator.GetBytes(AsymmetricAlgorithmHelpers.BitsToBytes(_outer.BaseKeySize));
93 public void GenerateIV()
95 byte[] iv = RandomNumberGenerator.GetBytes(AsymmetricAlgorithmHelpers.BitsToBytes(_outer.BlockSize));
99 public ICryptoTransform CreateEncryptor()
101 return CreateCryptoTransform(encrypting: true);
104 public ICryptoTransform CreateDecryptor()
106 return CreateCryptoTransform(encrypting: false);
109 public ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[]? rgbIV)
111 return CreateCryptoTransform(rgbKey, rgbIV, encrypting: true, _outer.Padding, _outer.Mode, _outer.FeedbackSize);
114 public ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[]? rgbIV)
116 return CreateCryptoTransform(rgbKey, rgbIV, encrypting: false, _outer.Padding, _outer.Mode, _outer.FeedbackSize);
119 private ICryptoTransform CreateCryptoTransform(bool encrypting)
123 return CreateCryptoTransform(_outer.BaseKey, _outer.IV, encrypting, _outer.Padding, _outer.Mode, _outer.FeedbackSize);
126 return CreatePersistedCryptoTransformCore(ProduceCngKey, _outer.IV, encrypting, _outer.Padding, _outer.Mode, _outer.FeedbackSize);
129 public ILiteSymmetricCipher CreateLiteSymmetricCipher(ReadOnlySpan<byte> iv, bool encrypting, CipherMode mode, int feedbackSizeInBits)
133 return CreateLiteSymmetricCipher(_outer.BaseKey, iv, encrypting, mode, feedbackSizeInBits);
136 return CreatePersistedLiteSymmetricCipher(ProduceCngKey, iv, encrypting, mode, feedbackSizeInBits);
139 private ILiteSymmetricCipher CreateLiteSymmetricCipher(
140 ReadOnlySpan<byte> key,
141 ReadOnlySpan<byte> iv,
144 int feedbackSizeInBits)
146 ValidateFeedbackSize(mode, feedbackSizeInBits);
148 if (!iv.IsEmpty && iv.Length != AsymmetricAlgorithmHelpers.BitsToBytes(_outer.BlockSize))
150 throw new ArgumentException(SR.Cryptography_InvalidIVSize, nameof(iv));
153 if (mode.UsesIv() && iv.IsEmpty)
155 throw new CryptographicException(SR.Cryptography_MissingIV);
158 byte[] processedKey = CopyAndValidateKey(key);
159 int blockSizeInBytes = AsymmetricAlgorithmHelpers.BitsToBytes(_outer.BlockSize);
160 SafeAlgorithmHandle algorithmModeHandle = _outer.GetEphemeralModeHandle(mode, feedbackSizeInBits);
162 return new BasicSymmetricCipherLiteBCrypt(
165 _outer.GetPaddingSize(mode, feedbackSizeInBits),
167 ownsParentHandle: false,
172 private UniversalCryptoTransform CreateCryptoTransform(byte[] rgbKey, byte[]? rgbIV, bool encrypting, PaddingMode padding, CipherMode mode, int feedbackSizeInBits)
174 ArgumentNullException.ThrowIfNull(rgbKey);
176 ValidateFeedbackSize(mode, feedbackSizeInBits);
177 byte[] key = CopyAndValidateKey(rgbKey);
179 if (rgbIV != null && rgbIV.Length != AsymmetricAlgorithmHelpers.BitsToBytes(_outer.BlockSize))
181 throw new ArgumentException(SR.Cryptography_InvalidIVSize, nameof(rgbIV));
184 // CloneByteArray is null-preserving. So even when GetCipherIv returns null the iv variable
185 // is correct, and detached from the input parameter.
186 byte[]? iv = mode.GetCipherIv(rgbIV).CloneByteArray();
188 return CreateEphemeralCryptoTransformCore(key, iv, encrypting, padding, mode, feedbackSizeInBits);
191 private UniversalCryptoTransform CreateEphemeralCryptoTransformCore(byte[] key, byte[]? iv, bool encrypting, PaddingMode padding, CipherMode mode, int feedbackSizeInBits)
193 int blockSizeInBytes = AsymmetricAlgorithmHelpers.BitsToBytes(_outer.BlockSize);
194 SafeAlgorithmHandle algorithmModeHandle = _outer.GetEphemeralModeHandle(mode, feedbackSizeInBits);
196 BasicSymmetricCipher cipher = new BasicSymmetricCipherBCrypt(
200 _outer.GetPaddingSize(mode, feedbackSizeInBits),
202 ownsParentHandle: false,
206 return UniversalCryptoTransform.Create(padding, cipher, encrypting);
209 private ILiteSymmetricCipher CreatePersistedLiteSymmetricCipher(
210 Func<CngKey> cngKeyFactory,
211 ReadOnlySpan<byte> iv,
214 int feedbackSizeInBits)
216 ValidateFeedbackSize(mode, feedbackSizeInBits);
217 Debug.Assert(mode == CipherMode.CFB ? feedbackSizeInBits == 8 : true);
218 int blockSizeInBytes = AsymmetricAlgorithmHelpers.BitsToBytes(_outer.BlockSize);
220 return new BasicSymmetricCipherLiteNCrypt(
226 _outer.GetPaddingSize(mode, feedbackSizeInBits));
229 private UniversalCryptoTransform CreatePersistedCryptoTransformCore(Func<CngKey> cngKeyFactory, byte[]? iv, bool encrypting, PaddingMode padding, CipherMode mode, int feedbackSizeInBits)
231 // note: iv is guaranteed to be cloned before this method, so no need to clone it again
233 ValidateFeedbackSize(mode, feedbackSizeInBits);
234 Debug.Assert(mode == CipherMode.CFB ? feedbackSizeInBits == 8 : true);
236 int blockSizeInBytes = AsymmetricAlgorithmHelpers.BitsToBytes(_outer.BlockSize);
237 BasicSymmetricCipher cipher = new BasicSymmetricCipherNCrypt(
243 _outer.GetPaddingSize(mode, feedbackSizeInBits));
244 return UniversalCryptoTransform.Create(padding, cipher, encrypting);
247 private CngKey ProduceCngKey()
249 Debug.Assert(!KeyInPlainText);
251 return CngKey.Open(_keyName!, _provider!, _optionOptions);
254 private bool KeyInPlainText
256 get { return _keyName == null; }
259 private void ValidateFeedbackSize(CipherMode mode, int feedbackSizeInBits)
261 if (mode != CipherMode.CFB)
266 if (!_outer.IsValidEphemeralFeedbackSize(feedbackSizeInBits))
268 throw new CryptographicException(SR.Format(SR.Cryptography_CipherModeFeedbackNotSupported, feedbackSizeInBits, CipherMode.CFB));
271 else if (feedbackSizeInBits != 8)
273 // Persisted CNG keys in CFB mode always use CFB8 when in CFB mode,
274 // so require the feedback size to be set to 8.
275 throw new CryptographicException(SR.Format(SR.Cryptography_CipherModeFeedbackNotSupported, feedbackSizeInBits, CipherMode.CFB));
279 private byte[] CopyAndValidateKey(ReadOnlySpan<byte> rgbKey)
281 long keySize = rgbKey.Length * (long)BitsPerByte;
282 if (keySize > int.MaxValue || !((int)keySize).IsLegalSize(_outer.LegalKeySizes))
284 throw new ArgumentException(SR.Cryptography_InvalidKeySize, nameof(rgbKey));
287 byte[] key = rgbKey.ToArray();
289 if (_outer.IsWeakKey(key))
291 throw new CryptographicException(
293 SR.Cryptography_InvalidKey_Weak,
294 _outer.GetNCryptAlgorithmIdentifier()));
297 return _outer.PreprocessKey(key);
300 private readonly ICngSymmetricAlgorithm _outer;
302 // If using a stored CNG key, these fields provide the CngKey.Open() parameters. If using a plaintext key, _keyName is set to null.
303 private string? _keyName;
304 private readonly CngProvider? _provider;
305 private readonly CngKeyOpenOptions _optionOptions;
307 private const int BitsPerByte = 8;