/
ECDsaOpenSsl.cs
336 lines (289 loc) · 12.4 KB
/
ECDsaOpenSsl.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
// 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 ECDsaOpenSsl : ECDsa, IRuntimeAlgorithm
{
// secp521r1 maxes out at 139 bytes, so 256 should always be enough
private const int SignatureStackBufSize = 256;
private ECOpenSsl? _key;
/// <summary>
/// Create an ECDsaOpenSsl algorithm with a named curve.
/// </summary>
/// <param name="curve">The <see cref="ECCurve"/> representing the curve.</param>
/// <exception cref="ArgumentNullException">if <paramref name="curve" /> is null.</exception>
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("browser")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("tvos")]
[UnsupportedOSPlatform("windows")]
public ECDsaOpenSsl(ECCurve curve)
{
ThrowIfNotSupported();
_key = new ECOpenSsl(curve);
ForceSetKeySize(_key.KeySize);
}
/// <summary>
/// Create an ECDsaOpenSsl algorithm with a random 521 bit key pair.
/// </summary>
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("browser")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("tvos")]
[UnsupportedOSPlatform("windows")]
public ECDsaOpenSsl()
: this(521)
{
}
/// <summary>
/// Creates a new ECDsaOpenSsl object that will use a randomly generated key of the specified size.
/// </summary>
/// <param name="keySize">Size of the key to generate, in bits.</param>
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("browser")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("tvos")]
[UnsupportedOSPlatform("windows")]
public ECDsaOpenSsl(int keySize)
{
ThrowIfNotSupported();
// Use the base setter to get the validation and field assignment without the
// side effect of dereferencing _key.
base.KeySize = keySize;
_key = new ECOpenSsl(this);
}
/// <summary>
/// Set the KeySize without validating against LegalKeySizes.
/// </summary>
/// <param name="newKeySize">The value to set the KeySize to.</param>
private void ForceSetKeySize(int newKeySize)
{
// In the event that a key was loaded via ImportParameters, curve name, 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;
}
// Return the three sizes that can be explicitly set (for backwards compatibility)
public override KeySizes[] LegalKeySizes => s_defaultKeySizes.CloneKeySizesArray();
public override byte[] SignHash(byte[] hash)
{
ArgumentNullException.ThrowIfNull(hash);
ThrowIfDisposed();
SafeEcKeyHandle key = _key.Value;
int signatureLength = Interop.Crypto.EcDsaSize(key);
Span<byte> signDestination = stackalloc byte[SignatureStackBufSize];
ReadOnlySpan<byte> derSignature = SignHash(hash, signDestination, signatureLength, key);
byte[] converted = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(derSignature, KeySize);
return converted;
}
public override bool TrySignHash(ReadOnlySpan<byte> hash, Span<byte> destination, out int bytesWritten)
{
return TrySignHashCore(
hash,
destination,
DSASignatureFormat.IeeeP1363FixedFieldConcatenation,
out bytesWritten);
}
protected override bool TrySignHashCore(
ReadOnlySpan<byte> hash,
Span<byte> destination,
DSASignatureFormat signatureFormat,
out int bytesWritten)
{
ThrowIfDisposed();
SafeEcKeyHandle key = _key.Value;
int signatureLength = Interop.Crypto.EcDsaSize(key);
Span<byte> signDestination = stackalloc byte[SignatureStackBufSize];
if (signatureFormat == DSASignatureFormat.IeeeP1363FixedFieldConcatenation)
{
int encodedSize = 2 * AsymmetricAlgorithmHelpers.BitsToBytes(KeySize);
if (destination.Length < encodedSize)
{
bytesWritten = 0;
return false;
}
ReadOnlySpan<byte> derSignature = SignHash(hash, signDestination, signatureLength, key);
bytesWritten = AsymmetricAlgorithmHelpers.ConvertDerToIeee1363(derSignature, KeySize, destination);
Debug.Assert(bytesWritten == encodedSize);
return true;
}
else if (signatureFormat == DSASignatureFormat.Rfc3279DerSequence)
{
if (destination.Length >= signatureLength)
{
signDestination = destination;
}
else if (signatureLength > signDestination.Length)
{
Debug.Fail($"Stack-based signDestination is insufficient ({signatureLength} needed)");
bytesWritten = 0;
return false;
}
ReadOnlySpan<byte> derSignature = SignHash(hash, signDestination, signatureLength, key);
if (destination == signDestination)
{
bytesWritten = derSignature.Length;
return true;
}
return Helpers.TryCopyToDestination(derSignature, destination, out bytesWritten);
}
else
{
throw new ArgumentOutOfRangeException(nameof(signatureFormat));
}
}
private static ReadOnlySpan<byte> SignHash(
ReadOnlySpan<byte> hash,
Span<byte> destination,
int signatureLength,
SafeEcKeyHandle key)
{
if (signatureLength > destination.Length)
{
Debug.Fail($"Stack-based signDestination is insufficient ({signatureLength} needed)");
destination = new byte[signatureLength];
}
if (!Interop.Crypto.EcDsaSign(hash, destination, out int actualLength, key))
{
throw Interop.Crypto.CreateOpenSslCryptographicException();
}
Debug.Assert(
actualLength <= signatureLength,
"ECDSA_sign reported an unexpected signature size",
"ECDSA_sign reported signatureSize was {0}, when <= {1} was expected",
actualLength,
signatureLength);
return destination.Slice(0, actualLength);
}
public override bool VerifyHash(byte[] hash, byte[] signature)
{
ArgumentNullException.ThrowIfNull(hash);
ArgumentNullException.ThrowIfNull(signature);
return VerifyHash((ReadOnlySpan<byte>)hash, (ReadOnlySpan<byte>)signature);
}
public override bool VerifyHash(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> signature) =>
VerifyHashCore(hash, signature, DSASignatureFormat.IeeeP1363FixedFieldConcatenation);
protected override bool VerifyHashCore(
ReadOnlySpan<byte> hash,
ReadOnlySpan<byte> signature,
DSASignatureFormat signatureFormat)
{
ThrowIfDisposed();
Span<byte> derSignature = stackalloc byte[SignatureStackBufSize];
ReadOnlySpan<byte> toVerify = derSignature;
if (signatureFormat == DSASignatureFormat.IeeeP1363FixedFieldConcatenation)
{
// The signature format for .NET is r.Concat(s). Each of r and s are of length BitsToBytes(KeySize), even
// when they would have leading zeroes. If it's the correct size, then we need to encode it from
// r.Concat(s) to SEQUENCE(INTEGER(r), INTEGER(s)), because that's the format that OpenSSL expects.
int expectedBytes = 2 * AsymmetricAlgorithmHelpers.BitsToBytes(KeySize);
if (signature.Length != expectedBytes)
{
// The input isn't of the right length, so we can't sensibly re-encode it.
return false;
}
if (AsymmetricAlgorithmHelpers.TryConvertIeee1363ToDer(signature, derSignature, out int derSize))
{
toVerify = derSignature.Slice(0, derSize);
}
else
{
toVerify = AsymmetricAlgorithmHelpers.ConvertIeee1363ToDer(signature);
}
}
else if (signatureFormat == DSASignatureFormat.Rfc3279DerSequence)
{
toVerify = signature;
}
else
{
Debug.Fail($"Missing internal implementation handler for signature format {signatureFormat}");
throw new CryptographicException(
SR.Cryptography_UnknownSignatureFormat,
signatureFormat.ToString());
}
SafeEcKeyHandle key = _key.Value;
int verifyResult = Interop.Crypto.EcDsaVerify(hash, toVerify, key);
return verifyResult == 1;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_key?.Dispose();
_key = null;
}
base.Dispose(disposing);
}
public override int KeySize
{
get
{
return base.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();
_key.Dispose();
_key = new ECOpenSsl(this);
}
}
public override void GenerateKey(ECCurve curve)
{
ThrowIfDisposed();
_key.GenerateKey(curve);
// Use ForceSet instead of the property setter to ensure that LegalKeySizes doesn't interfere
// with the already loaded key.
ForceSetKeySize(_key.KeySize);
}
public override void ImportParameters(ECParameters parameters)
{
ThrowIfDisposed();
_key.ImportParameters(parameters);
ForceSetKeySize(_key.KeySize);
}
public override ECParameters ExportExplicitParameters(bool includePrivateParameters)
{
ThrowIfDisposed();
return ECOpenSsl.ExportExplicitParameters(_key.Value, includePrivateParameters);
}
public override ECParameters ExportParameters(bool includePrivateParameters)
{
ThrowIfDisposed();
return ECOpenSsl.ExportParameters(_key.Value, includePrivateParameters);
}
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);
}
[MemberNotNull(nameof(_key))]
private void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_key is null, this);
}
static partial void ThrowIfNotSupported();
}
}