/
DSAOpenSsl.cs
381 lines (318 loc) · 13.9 KB
/
DSAOpenSsl.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
// 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.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.Versioning;
using Internal.Cryptography;
using Microsoft.Win32.SafeHandles;
namespace System.Security.Cryptography
{
public sealed partial class DSAOpenSsl : DSA, IRuntimeAlgorithm
{
// The biggest key allowed by FIPS 186-4 has N=256 (bit), which
// maximally produces a 72-byte DER signature.
// If a future version of the standard continues to enhance DSA,
// we may want to bump this limit to allow the max-1 (expected size)
// TryCreateSignature to pass.
// Future updates seem unlikely, though, as FIPS 186-5 October 2019 draft has
// DSA as a no longer supported/updated algorithm.
private const int SignatureStackBufSize = 72;
private const int BitsPerByte = 8;
private Lazy<SafeDsaHandle>? _key;
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("browser")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("tvos")]
[UnsupportedOSPlatform("windows")]
public DSAOpenSsl()
: this(2048)
{
}
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("browser")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("tvos")]
[UnsupportedOSPlatform("windows")]
public DSAOpenSsl(int keySize)
{
ThrowIfNotSupported();
LegalKeySizesValue = s_legalKeySizes;
base.KeySize = keySize;
_key = new Lazy<SafeDsaHandle>(GenerateKey);
}
public override int KeySize
{
set
{
if (KeySize == value)
{
return;
}
// Set the KeySize before FreeKey so that an invalid value doesn't throw away the key
base.KeySize = value;
ThrowIfDisposed();
FreeKey();
_key = new Lazy<SafeDsaHandle>(GenerateKey);
}
}
private void ForceSetKeySize(int newKeySize)
{
// In the event that a key was loaded via ImportParameters or an IntPtr/SafeHandle
// it could be outside of the bounds that we currently represent as "legal key sizes".
// Since that is our view into the underlying component it can be detached from the
// component's understanding. If it said it has opened a key, and this is the size, trust it.
KeySizeValue = newKeySize;
}
public override KeySizes[] LegalKeySizes
{
get
{
return base.LegalKeySizes;
}
}
public override DSAParameters ExportParameters(bool includePrivateParameters)
{
// It's entirely possible that this line will cause the key to be generated in the first place.
SafeDsaHandle key = GetKey();
DSAParameters dsaParameters = Interop.Crypto.ExportDsaParameters(key, includePrivateParameters);
bool hasPrivateKey = dsaParameters.X != null;
if (hasPrivateKey != includePrivateParameters)
throw new CryptographicException(SR.Cryptography_CSP_NoPrivateKey);
return dsaParameters;
}
public override void ImportParameters(DSAParameters parameters)
{
if (parameters.P == null || parameters.Q == null || parameters.G == null || parameters.Y == null)
throw new ArgumentException(SR.Cryptography_InvalidDsaParameters_MissingFields);
// J is not required and is not even used on CNG blobs. It should however be less than P (J == (P-1) / Q). This validation check
// is just to maintain parity with DSACNG and DSACryptoServiceProvider, which also perform this check.
if (parameters.J != null && parameters.J.Length >= parameters.P.Length)
throw new ArgumentException(SR.Cryptography_InvalidDsaParameters_MismatchedPJ);
bool hasPrivateKey = parameters.X != null;
int keySize = parameters.P.Length;
if (parameters.G.Length != keySize || parameters.Y.Length != keySize)
throw new ArgumentException(SR.Cryptography_InvalidDsaParameters_MismatchedPGY);
if (hasPrivateKey && parameters.X!.Length != parameters.Q.Length)
throw new ArgumentException(SR.Cryptography_InvalidDsaParameters_MismatchedQX);
ThrowIfDisposed();
SafeDsaHandle key;
if (!Interop.Crypto.DsaKeyCreateByExplicitParameters(
out key,
parameters.P, parameters.P.Length,
parameters.Q, parameters.Q.Length,
parameters.G, parameters.G.Length,
parameters.Y, parameters.Y.Length,
parameters.X, parameters.X != null ? parameters.X.Length : 0))
{
Exception e = Interop.Crypto.CreateOpenSslCryptographicException();
key.Dispose();
throw e;
}
SetKey(key);
}
public override void ImportEncryptedPkcs8PrivateKey(
ReadOnlySpan<byte> passwordBytes,
ReadOnlySpan<byte> source,
out int bytesRead)
{
ThrowIfDisposed();
base.ImportEncryptedPkcs8PrivateKey(passwordBytes, source, out bytesRead);
}
public override void ImportEncryptedPkcs8PrivateKey(
ReadOnlySpan<char> password,
ReadOnlySpan<byte> source,
out int bytesRead)
{
ThrowIfDisposed();
base.ImportEncryptedPkcs8PrivateKey(password, source, out bytesRead);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
FreeKey();
_key = null;
}
base.Dispose(disposing);
}
private void FreeKey()
{
if (_key != null && _key.IsValueCreated)
{
_key.Value?.Dispose();
}
}
private static void CheckInvalidKey(SafeDsaHandle key)
{
if (key == null || key.IsInvalid)
{
throw new CryptographicException(SR.Cryptography_OpenInvalidHandle);
}
}
private SafeDsaHandle GenerateKey()
{
SafeDsaHandle key;
if (!Interop.Crypto.DsaGenerateKey(out key, KeySize))
{
Exception e = Interop.Crypto.CreateOpenSslCryptographicException();
key.Dispose();
throw e;
}
return key;
}
public override byte[] CreateSignature(byte[] rgbHash)
{
ArgumentNullException.ThrowIfNull(rgbHash);
SafeDsaHandle key = GetKey();
int signatureSize = Interop.Crypto.DsaEncodedSignatureSize(key);
int signatureFieldSize = Interop.Crypto.DsaSignatureFieldSize(key) * BitsPerByte;
Span<byte> signDestination = stackalloc byte[SignatureStackBufSize];
ReadOnlySpan<byte> derSignature = SignHash(rgbHash, signDestination, signatureSize, key);
return AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(derSignature, signatureFieldSize);
}
public override bool TryCreateSignature(
ReadOnlySpan<byte> hash,
Span<byte> destination,
out int bytesWritten)
{
return TryCreateSignatureCore(
hash,
destination,
DSASignatureFormat.IeeeP1363FixedFieldConcatenation,
out bytesWritten);
}
protected override bool TryCreateSignatureCore(
ReadOnlySpan<byte> hash,
Span<byte> destination,
DSASignatureFormat signatureFormat,
out int bytesWritten)
{
SafeDsaHandle key = GetKey();
int maxSignatureSize = Interop.Crypto.DsaEncodedSignatureSize(key);
Span<byte> signDestination = stackalloc byte[SignatureStackBufSize];
if (signatureFormat == DSASignatureFormat.IeeeP1363FixedFieldConcatenation)
{
int fieldSizeBytes = Interop.Crypto.DsaSignatureFieldSize(key);
int p1363SignatureSize = 2 * fieldSizeBytes;
if (destination.Length < p1363SignatureSize)
{
bytesWritten = 0;
return false;
}
int fieldSizeBits = fieldSizeBytes * 8;
ReadOnlySpan<byte> derSignature = SignHash(hash, signDestination, maxSignatureSize, key);
bytesWritten = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(derSignature, fieldSizeBits, destination);
Debug.Assert(bytesWritten == p1363SignatureSize);
return true;
}
else if (signatureFormat == DSASignatureFormat.Rfc3279DerSequence)
{
if (destination.Length >= maxSignatureSize)
{
signDestination = destination;
}
else if (maxSignatureSize > signDestination.Length)
{
Debug.Fail($"Stack-based signDestination is insufficient ({maxSignatureSize} needed)");
bytesWritten = 0;
return false;
}
ReadOnlySpan<byte> derSignature = SignHash(hash, signDestination, maxSignatureSize, key);
if (destination == signDestination)
{
bytesWritten = derSignature.Length;
return true;
}
return Helpers.TryCopyToDestination(derSignature, destination, out bytesWritten);
}
else
{
Debug.Fail($"Missing internal implementation handler for signature format {signatureFormat}");
throw new CryptographicException(
SR.Cryptography_UnknownSignatureFormat,
signatureFormat.ToString());
}
}
private static ReadOnlySpan<byte> SignHash(
ReadOnlySpan<byte> hash,
Span<byte> destination,
int signatureLength,
SafeDsaHandle key)
{
if (signatureLength > destination.Length)
{
Debug.Fail($"Stack-based signDestination is insufficient ({signatureLength} needed)");
destination = new byte[signatureLength];
}
if (!Interop.Crypto.DsaSign(key, hash, destination, out int actualLength))
{
throw Interop.Crypto.CreateOpenSslCryptographicException();
}
Debug.Assert(
actualLength <= signatureLength,
"DSA_sign reported an unexpected signature size",
"DSA_sign reported signatureSize was {0}, when <= {1} was expected",
actualLength,
signatureLength);
return destination.Slice(0, actualLength);
}
public override bool VerifySignature(byte[] rgbHash, byte[] rgbSignature)
{
ArgumentNullException.ThrowIfNull(rgbHash);
ArgumentNullException.ThrowIfNull(rgbSignature);
return VerifySignature((ReadOnlySpan<byte>)rgbHash, (ReadOnlySpan<byte>)rgbSignature);
}
public override bool VerifySignature(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> signature) =>
VerifySignatureCore(hash, signature, DSASignatureFormat.IeeeP1363FixedFieldConcatenation);
protected override bool VerifySignatureCore(
ReadOnlySpan<byte> hash,
ReadOnlySpan<byte> signature,
DSASignatureFormat signatureFormat)
{
SafeDsaHandle key = GetKey();
if (signatureFormat == DSASignatureFormat.IeeeP1363FixedFieldConcatenation)
{
int expectedSignatureBytes = Interop.Crypto.DsaSignatureFieldSize(key) * 2;
if (signature.Length != expectedSignatureBytes)
{
// The input isn't of the right length (assuming no DER), so we can't sensibly re-encode it with DER.
return false;
}
signature = AsymmetricAlgorithmHelpers.ConvertIeee1363ToDer(signature);
}
else if (signatureFormat != DSASignatureFormat.Rfc3279DerSequence)
{
Debug.Fail($"Missing internal implementation handler for signature format {signatureFormat}");
throw new CryptographicException(
SR.Cryptography_UnknownSignatureFormat,
signatureFormat.ToString());
}
return Interop.Crypto.DsaVerify(key, hash, signature);
}
[MemberNotNull(nameof(_key))]
private void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_key is null, this);
}
private SafeDsaHandle GetKey()
{
ThrowIfDisposed();
SafeDsaHandle key = _key.Value;
CheckInvalidKey(key);
return key;
}
[MemberNotNull(nameof(_key))]
private void SetKey(SafeDsaHandle newKey)
{
// Do not call ThrowIfDisposed here, as it breaks the SafeEvpPKey ctor
// Use ForceSet instead of the property setter to ensure that LegalKeySizes doesn't interfere
// with the already loaded key.
ForceSetKeySize(BitsPerByte * Interop.Crypto.DsaKeySize(newKey));
FreeKey();
_key = new Lazy<SafeDsaHandle>(newKey);
}
static partial void ThrowIfNotSupported();
private static readonly KeySizes[] s_legalKeySizes = new KeySizes[] { new KeySizes(minSize: 512, maxSize: 3072, skipSize: 64) };
}
}