ded5b10aa7c85343de334410763bfb5249d8cd64
[platform/upstream/dotnet/runtime.git] /
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3
4 using System.Diagnostics;
5 using Internal.Cryptography;
6 using Internal.NativeCrypto;
7
8 namespace System.Security.Cryptography
9 {
10     //
11     // Common infrastructure for crypto symmetric algorithms that use Cng.
12     //
13     internal struct CngSymmetricAlgorithmCore
14     {
15         /// <summary>
16         /// Configures the core to use plaintext keys (to be auto-generated when first needed.)
17         /// </summary>
18         public CngSymmetricAlgorithmCore(ICngSymmetricAlgorithm outer)
19         {
20             _outer = outer;
21
22             _keyName = null; // Setting _keyName to null signifies that this object is based on a plaintext key, not a stored CNG key.
23             _provider = null;
24             _optionOptions = CngKeyOpenOptions.None;
25         }
26
27         /// <summary>
28         /// Constructs the core to use a stored CNG key.
29         /// </summary>
30         public CngSymmetricAlgorithmCore(ICngSymmetricAlgorithm outer, string keyName, CngProvider provider, CngKeyOpenOptions openOptions)
31         {
32             ArgumentNullException.ThrowIfNull(keyName);
33             ArgumentNullException.ThrowIfNull(provider);
34
35             _outer = outer;
36
37             _keyName = keyName;
38             _provider = provider;
39             _optionOptions = openOptions;
40
41             using (CngKey cngKey = ProduceCngKey())
42             {
43                 CngAlgorithm actualAlgorithm = cngKey.Algorithm;
44                 string algorithm = _outer.GetNCryptAlgorithmIdentifier();
45
46                 if (algorithm != actualAlgorithm.Algorithm)
47                     throw new CryptographicException(SR.Format(SR.Cryptography_CngKeyWrongAlgorithm, actualAlgorithm.Algorithm, algorithm));
48
49                 _outer.BaseKeySize = cngKey.KeySize;
50             }
51         }
52
53         /// <summary>
54         /// Note! This can and likely will throw if the algorithm was given a hardware-based key.
55         /// </summary>
56         public byte[] GetKeyIfExportable()
57         {
58             if (KeyInPlainText)
59             {
60                 return _outer.BaseKey;
61             }
62             else
63             {
64                 using (CngKey cngKey = ProduceCngKey())
65                 {
66                     return cngKey.GetSymmetricKeyDataIfExportable(_outer.GetNCryptAlgorithmIdentifier());
67                 }
68             }
69         }
70
71         public void SetKey(byte[] key)
72         {
73             _outer.BaseKey = key;
74             _keyName = null; // Setting _keyName to null signifies that this object is now based on a plaintext key, not a stored CNG key.
75         }
76
77         public void SetKeySize(int keySize, ICngSymmetricAlgorithm outer)
78         {
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);
82
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.
85         }
86
87         public void GenerateKey()
88         {
89             byte[] key = RandomNumberGenerator.GetBytes(AsymmetricAlgorithmHelpers.BitsToBytes(_outer.BaseKeySize));
90             SetKey(key);
91         }
92
93         public void GenerateIV()
94         {
95             byte[] iv = RandomNumberGenerator.GetBytes(AsymmetricAlgorithmHelpers.BitsToBytes(_outer.BlockSize));
96             _outer.IV = iv;
97         }
98
99         public ICryptoTransform CreateEncryptor()
100         {
101             return CreateCryptoTransform(encrypting: true);
102         }
103
104         public ICryptoTransform CreateDecryptor()
105         {
106             return CreateCryptoTransform(encrypting: false);
107         }
108
109         public ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[]? rgbIV)
110         {
111             return CreateCryptoTransform(rgbKey, rgbIV, encrypting: true, _outer.Padding, _outer.Mode, _outer.FeedbackSize);
112         }
113
114         public ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[]? rgbIV)
115         {
116             return CreateCryptoTransform(rgbKey, rgbIV, encrypting: false, _outer.Padding, _outer.Mode, _outer.FeedbackSize);
117         }
118
119         private ICryptoTransform CreateCryptoTransform(bool encrypting)
120         {
121             if (KeyInPlainText)
122             {
123                 return CreateCryptoTransform(_outer.BaseKey, _outer.IV, encrypting, _outer.Padding, _outer.Mode, _outer.FeedbackSize);
124             }
125
126             return CreatePersistedCryptoTransformCore(ProduceCngKey, _outer.IV, encrypting, _outer.Padding, _outer.Mode, _outer.FeedbackSize);
127         }
128
129         public ILiteSymmetricCipher CreateLiteSymmetricCipher(ReadOnlySpan<byte> iv, bool encrypting, CipherMode mode, int feedbackSizeInBits)
130         {
131             if (KeyInPlainText)
132             {
133                 return CreateLiteSymmetricCipher(_outer.BaseKey, iv, encrypting, mode, feedbackSizeInBits);
134             }
135
136             return CreatePersistedLiteSymmetricCipher(ProduceCngKey, iv, encrypting, mode, feedbackSizeInBits);
137         }
138
139         private ILiteSymmetricCipher CreateLiteSymmetricCipher(
140             ReadOnlySpan<byte> key,
141             ReadOnlySpan<byte> iv,
142             bool encrypting,
143             CipherMode mode,
144             int feedbackSizeInBits)
145         {
146             ValidateFeedbackSize(mode, feedbackSizeInBits);
147
148             if (!iv.IsEmpty && iv.Length != AsymmetricAlgorithmHelpers.BitsToBytes(_outer.BlockSize))
149             {
150                 throw new ArgumentException(SR.Cryptography_InvalidIVSize, nameof(iv));
151             }
152
153             if (mode.UsesIv() && iv.IsEmpty)
154             {
155                 throw new CryptographicException(SR.Cryptography_MissingIV);
156             }
157
158             byte[] processedKey = CopyAndValidateKey(key);
159             int blockSizeInBytes = AsymmetricAlgorithmHelpers.BitsToBytes(_outer.BlockSize);
160             SafeAlgorithmHandle algorithmModeHandle = _outer.GetEphemeralModeHandle(mode, feedbackSizeInBits);
161
162             return new BasicSymmetricCipherLiteBCrypt(
163                 algorithmModeHandle,
164                 blockSizeInBytes,
165                 _outer.GetPaddingSize(mode, feedbackSizeInBits),
166                 processedKey,
167                 ownsParentHandle: false,
168                 iv,
169                 encrypting);
170         }
171
172         private UniversalCryptoTransform CreateCryptoTransform(byte[] rgbKey, byte[]? rgbIV, bool encrypting, PaddingMode padding, CipherMode mode, int feedbackSizeInBits)
173         {
174             ArgumentNullException.ThrowIfNull(rgbKey);
175
176             ValidateFeedbackSize(mode, feedbackSizeInBits);
177             byte[] key = CopyAndValidateKey(rgbKey);
178
179             if (rgbIV != null && rgbIV.Length != AsymmetricAlgorithmHelpers.BitsToBytes(_outer.BlockSize))
180             {
181                 throw new ArgumentException(SR.Cryptography_InvalidIVSize, nameof(rgbIV));
182             }
183
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();
187
188             return CreateEphemeralCryptoTransformCore(key, iv, encrypting, padding, mode, feedbackSizeInBits);
189         }
190
191         private UniversalCryptoTransform CreateEphemeralCryptoTransformCore(byte[] key, byte[]? iv, bool encrypting, PaddingMode padding, CipherMode mode, int feedbackSizeInBits)
192         {
193             int blockSizeInBytes = AsymmetricAlgorithmHelpers.BitsToBytes(_outer.BlockSize);
194             SafeAlgorithmHandle algorithmModeHandle = _outer.GetEphemeralModeHandle(mode, feedbackSizeInBits);
195
196             BasicSymmetricCipher cipher = new BasicSymmetricCipherBCrypt(
197                 algorithmModeHandle,
198                 mode,
199                 blockSizeInBytes,
200                 _outer.GetPaddingSize(mode, feedbackSizeInBits),
201                 key,
202                 ownsParentHandle: false,
203                 iv,
204                 encrypting);
205
206             return UniversalCryptoTransform.Create(padding, cipher, encrypting);
207         }
208
209         private ILiteSymmetricCipher CreatePersistedLiteSymmetricCipher(
210             Func<CngKey> cngKeyFactory,
211             ReadOnlySpan<byte> iv,
212             bool encrypting,
213             CipherMode mode,
214             int feedbackSizeInBits)
215         {
216             ValidateFeedbackSize(mode, feedbackSizeInBits);
217             Debug.Assert(mode == CipherMode.CFB ? feedbackSizeInBits == 8 : true);
218             int blockSizeInBytes = AsymmetricAlgorithmHelpers.BitsToBytes(_outer.BlockSize);
219
220             return new BasicSymmetricCipherLiteNCrypt(
221                 cngKeyFactory,
222                 mode,
223                 blockSizeInBytes,
224                 iv,
225                 encrypting,
226                 _outer.GetPaddingSize(mode, feedbackSizeInBits));
227         }
228
229         private UniversalCryptoTransform CreatePersistedCryptoTransformCore(Func<CngKey> cngKeyFactory, byte[]? iv, bool encrypting, PaddingMode padding, CipherMode mode, int feedbackSizeInBits)
230         {
231             // note: iv is guaranteed to be cloned before this method, so no need to clone it again
232
233             ValidateFeedbackSize(mode, feedbackSizeInBits);
234             Debug.Assert(mode == CipherMode.CFB ? feedbackSizeInBits == 8 : true);
235
236             int blockSizeInBytes = AsymmetricAlgorithmHelpers.BitsToBytes(_outer.BlockSize);
237             BasicSymmetricCipher cipher = new BasicSymmetricCipherNCrypt(
238                 cngKeyFactory,
239                 mode,
240                 blockSizeInBytes,
241                 iv,
242                 encrypting,
243                 _outer.GetPaddingSize(mode, feedbackSizeInBits));
244             return UniversalCryptoTransform.Create(padding, cipher, encrypting);
245         }
246
247         private CngKey ProduceCngKey()
248         {
249             Debug.Assert(!KeyInPlainText);
250
251             return CngKey.Open(_keyName!, _provider!, _optionOptions);
252         }
253
254         private bool KeyInPlainText
255         {
256             get { return _keyName == null; }
257         }
258
259         private void ValidateFeedbackSize(CipherMode mode, int feedbackSizeInBits)
260         {
261             if (mode != CipherMode.CFB)
262                 return;
263
264             if (KeyInPlainText)
265             {
266                 if (!_outer.IsValidEphemeralFeedbackSize(feedbackSizeInBits))
267                 {
268                     throw new CryptographicException(SR.Format(SR.Cryptography_CipherModeFeedbackNotSupported, feedbackSizeInBits, CipherMode.CFB));
269                 }
270             }
271             else if (feedbackSizeInBits != 8)
272             {
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));
276             }
277         }
278
279         private byte[] CopyAndValidateKey(ReadOnlySpan<byte> rgbKey)
280         {
281             long keySize = rgbKey.Length * (long)BitsPerByte;
282             if (keySize > int.MaxValue || !((int)keySize).IsLegalSize(_outer.LegalKeySizes))
283             {
284                 throw new ArgumentException(SR.Cryptography_InvalidKeySize, nameof(rgbKey));
285             }
286
287             byte[] key = rgbKey.ToArray();
288
289             if (_outer.IsWeakKey(key))
290             {
291                 throw new CryptographicException(
292                     SR.Format(
293                         SR.Cryptography_InvalidKey_Weak,
294                         _outer.GetNCryptAlgorithmIdentifier()));
295             }
296
297             return _outer.PreprocessKey(key);
298         }
299
300         private readonly ICngSymmetricAlgorithm _outer;
301
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;
306
307         private const int BitsPerByte = 8;
308     }
309 }