Moving BSTR conv to native code in SecureStringToBSTR.
[mono.git] / mcs / class / referencesource / System.Core / System / Security / Cryptography / ECDiffieHellmanCng.cs
1 // ==++==
2 // 
3 //   Copyright (c) Microsoft Corporation.  All rights reserved.
4 // 
5 // ==--==
6
7 using System;
8 using System.Collections.Generic;
9 using System.Diagnostics;
10 using System.Diagnostics.CodeAnalysis;
11 using System.Runtime.CompilerServices;
12 using System.Runtime.InteropServices;
13 using System.Security;
14 using System.Security.Permissions;
15 using System.Diagnostics.Contracts;
16 using Microsoft.Win32.SafeHandles;
17
18 namespace System.Security.Cryptography {
19     /// <summary>
20     ///     Key derivation functions used to transform the raw secret agreement into key material
21     /// </summary>
22     public enum ECDiffieHellmanKeyDerivationFunction {
23         Hash,
24         Hmac,
25         Tls
26     }
27
28     /// <summary>
29     ///     Wrapper for CNG's implementation of elliptic curve Diffie-Hellman key exchange
30     /// </summary>
31     [System.Security.Permissions.HostProtection(MayLeakOnAbort = true)]
32     public sealed class ECDiffieHellmanCng : ECDiffieHellman {
33         private static KeySizes[] s_legalKeySizes = new KeySizes[] { new KeySizes(256, 384, 128), new KeySizes(521, 521, 0) };
34
35         private CngAlgorithm m_hashAlgorithm = CngAlgorithm.Sha256;
36         private byte[] m_hmacKey;
37         private CngKey m_key;
38         private ECDiffieHellmanKeyDerivationFunction m_kdf = ECDiffieHellmanKeyDerivationFunction.Hash;
39         private byte[] m_label;
40         private byte[] m_secretAppend;
41         private byte[] m_secretPrepend;
42         private byte[] m_seed;
43
44         //
45         // Constructors
46         //
47
48         public ECDiffieHellmanCng() : this(521) {
49             Contract.Ensures(LegalKeySizesValue != null);
50         }
51
52         public ECDiffieHellmanCng(int keySize) {
53             Contract.Ensures(LegalKeySizesValue != null);
54
55             if (!NCryptNative.NCryptSupported) {
56                 throw new PlatformNotSupportedException(SR.GetString(SR.Cryptography_PlatformNotSupported));
57             }
58
59             LegalKeySizesValue = s_legalKeySizes;
60             KeySize = keySize;
61         }
62
63         [SecuritySafeCritical]
64         public ECDiffieHellmanCng(CngKey key) {
65             Contract.Ensures(LegalKeySizesValue != null);
66             Contract.Ensures(m_key != null && m_key.AlgorithmGroup == CngAlgorithmGroup.ECDiffieHellman);
67
68             if (key == null) {
69                 throw new ArgumentNullException("key");
70             }
71             if (key.AlgorithmGroup != CngAlgorithmGroup.ECDiffieHellman) {
72                 throw new ArgumentException(SR.GetString(SR.Cryptography_ArgECDHRequiresECDHKey), "key");
73             }
74
75             if (!NCryptNative.NCryptSupported) {
76                 throw new PlatformNotSupportedException(SR.GetString(SR.Cryptography_PlatformNotSupported));
77             }
78
79             LegalKeySizesValue = s_legalKeySizes;
80
81             // Make a copy of the key so that we continue to work if it gets disposed before this algorithm
82             //
83             // This requires an assert for UnmanagedCode since we'll need to access the raw handles of the key
84             // and the handle constructor of CngKey.  The assert is safe since ECDiffieHellmanCng will never
85             // expose the key handles to calling code (without first demanding UnmanagedCode via the Handle
86             // property of CngKey).
87             //
88             // The bizzare looking disposal of the key.Handle property is intentional - Handle returns a
89             // duplicate - without disposing it, we keep the key alive until the GC runs.
90             new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert();
91             using (SafeNCryptKeyHandle importHandle = key.Handle) {
92                 Key = CngKey.Open(importHandle, key.IsEphemeral ? CngKeyHandleOpenOptions.EphemeralKey : CngKeyHandleOpenOptions.None);
93             }
94             CodeAccessPermission.RevertAssert();
95
96             KeySize = m_key.KeySize;
97         }
98
99         /// <summary>
100         ///     Hash algorithm used with the Hash and HMAC KDFs
101         /// </summary>
102         public CngAlgorithm HashAlgorithm {
103             get {
104                 Contract.Ensures(Contract.Result<CngAlgorithm>() != null);
105                 return m_hashAlgorithm;
106             }
107
108             set {
109                 Contract.Ensures(m_hashAlgorithm != null);
110
111                 if (m_hashAlgorithm == null) {
112                     throw new ArgumentNullException("value");
113                 }
114
115                 m_hashAlgorithm = value;
116             }
117         }
118
119         /// <summary>
120         ///     Key used with the HMAC KDF
121         /// </summary>
122         [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Reviewed API design exception since these are really setters for explicit byte arrays rather than properties that will be iterated by users")]
123         public byte[] HmacKey {
124             get { return m_hmacKey; }
125             set { m_hmacKey = value; }
126         }
127
128         /// <summary>
129         ///     KDF used to transform the secret agreement into key material
130         /// </summary>
131         public ECDiffieHellmanKeyDerivationFunction KeyDerivationFunction {
132             get {
133                 Contract.Ensures(Contract.Result<ECDiffieHellmanKeyDerivationFunction>() >= ECDiffieHellmanKeyDerivationFunction.Hash &&
134                                  Contract.Result<ECDiffieHellmanKeyDerivationFunction>() <= ECDiffieHellmanKeyDerivationFunction.Tls);
135
136                 return m_kdf;
137             }
138
139             set {
140                 Contract.Ensures(m_kdf >= ECDiffieHellmanKeyDerivationFunction.Hash &&
141                                         m_kdf <= ECDiffieHellmanKeyDerivationFunction.Tls);
142
143                 if (value < ECDiffieHellmanKeyDerivationFunction.Hash || value > ECDiffieHellmanKeyDerivationFunction.Tls) {
144                     throw new ArgumentOutOfRangeException("value");
145                 }
146
147                 m_kdf = value;
148             }
149         }
150
151         /// <summary>
152         ///     Label bytes used for the TLS KDF
153         /// </summary>
154         [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Reviewed API design exception since these are really setters for explicit byte arrays rather than properties that will be iterated by users")]
155         public byte[] Label {
156             get { return m_label; }
157             set { m_label = value; }
158         }
159
160         /// <summary>
161         ///     Bytes to append to the raw secret agreement before processing by the KDF
162         /// </summary>
163         [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Reviewed API design exception since these are really setters for explicit byte arrays rather than properties that will be iterated by users")]
164         public byte[] SecretAppend {
165             get { return m_secretAppend; }
166             set { m_secretAppend = value; }
167         }
168
169         /// <summary>
170         ///     Bytes to prepend to the raw secret agreement before processing by the KDF
171         /// </summary>
172         [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Reviewed API design exception since these are really setters for explicit byte arrays rather than properties that will be iterated by users")]
173         public byte[] SecretPrepend {
174             get { return m_secretPrepend; }
175             set { m_secretPrepend = value; }
176         }
177
178         /// <summary>
179         ///     Seed bytes used for the TLS KDF
180         /// </summary>
181         [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Reviewed API design exception since these are really setters for explicit byte arrays rather than properties that will be iterated by users")]
182         public byte[] Seed {
183             get { return m_seed; }
184             set { m_seed = value; }
185         }
186
187         /// <summary>
188         ///     Full key pair being used for key generation
189         /// </summary>
190         public CngKey Key {
191             get {
192                 Contract.Ensures(Contract.Result<CngKey>() != null);
193                 Contract.Ensures(Contract.Result<CngKey>().AlgorithmGroup == CngAlgorithmGroup.ECDiffieHellman);
194                 Contract.Ensures(m_key != null && m_key.AlgorithmGroup == CngAlgorithmGroup.ECDiffieHellman);
195
196                 // If the size of the key no longer matches our stored value, then we need to replace it with
197                 // a new key of the correct size.
198                 if (m_key != null && m_key.KeySize != KeySize) {
199                     m_key.Dispose();
200                     m_key = null;
201                 }
202
203                 if (m_key == null) {
204                     // Map the current key size to a CNG algorithm name
205                     CngAlgorithm algorithm = null;
206                     switch (KeySize) {
207                         case 256:
208                             algorithm = CngAlgorithm.ECDiffieHellmanP256;
209                             break;
210
211                         case 384:
212                             algorithm = CngAlgorithm.ECDiffieHellmanP384;
213                             break;
214
215                         case 521:
216                             algorithm = CngAlgorithm.ECDiffieHellmanP521;
217                             break;
218
219                         default:
220                             Debug.Assert(false, "Illegal key size set");
221                             break;
222                     }
223
224                     m_key = CngKey.Create(algorithm);
225                 }
226
227                 return m_key;
228             }
229
230             private set {
231                 Contract.Requires(value != null);
232                 Contract.Ensures(m_key != null && m_key.AlgorithmGroup == CngAlgorithmGroup.ECDiffieHellman);
233
234                 if (value.AlgorithmGroup != CngAlgorithmGroup.ECDiffieHellman) {
235                     throw new ArgumentException(SR.GetString(SR.Cryptography_ArgECDHRequiresECDHKey));
236                 }
237
238                 if (m_key != null) {
239                     m_key.Dispose();
240                 }
241
242                 //
243                 // We do not duplicate the handle because the only time the user has access to the key itself
244                 // to dispose underneath us is when they construct via the CngKey constructor, which does a
245                 // duplication. Otherwise all key lifetimes are controlled directly by the ECDiffieHellmanCng
246                 // class.
247                 //
248
249                 m_key = value;
250                 KeySize = m_key.KeySize;
251             }
252         }
253
254         /// <summary>
255         ///     Public key used to generate key material with the second party
256         /// </summary>
257         public override ECDiffieHellmanPublicKey PublicKey {
258             get {
259                 Contract.Ensures(Contract.Result<ECDiffieHellmanPublicKey>() != null);
260                 return new ECDiffieHellmanCngPublicKey(Key);
261             }
262         }
263
264         /// <summary>
265         ///     Use the secret agreement as the HMAC key rather than supplying a seperate one
266         /// </summary>
267         public bool UseSecretAgreementAsHmacKey {
268             get { return HmacKey == null; }
269         }
270
271         /// <summary>
272         ///     Given a second party's public key, derive shared key material
273         /// </summary>
274         public override byte[] DeriveKeyMaterial(ECDiffieHellmanPublicKey otherPartyPublicKey) {
275             Contract.Ensures(Contract.Result<byte[]>() != null);
276             Contract.Assert(m_kdf >= ECDiffieHellmanKeyDerivationFunction.Hash &&
277                             m_kdf <= ECDiffieHellmanKeyDerivationFunction.Tls);
278
279             if (otherPartyPublicKey == null) {
280                 throw new ArgumentNullException("otherPartyPublicKey");
281             }
282
283             // We can only work with ECDiffieHellmanCngPublicKeys
284             ECDiffieHellmanCngPublicKey otherKey = otherPartyPublicKey as ECDiffieHellmanCngPublicKey;
285             if (otherPartyPublicKey == null) {
286                 throw new ArgumentException(SR.GetString(SR.Cryptography_ArgExpectedECDiffieHellmanCngPublicKey));
287             }
288
289             using (CngKey import = otherKey.Import()) {
290                 return DeriveKeyMaterial(import);
291             }
292         }
293
294         /// <summary>
295         ///     Given a second party's public key, derive shared key material
296         /// </summary>
297         [SecuritySafeCritical]
298         public byte[] DeriveKeyMaterial(CngKey otherPartyPublicKey) {
299             Contract.Ensures(Contract.Result<byte[]>() != null);
300             Contract.Assert(m_kdf >= ECDiffieHellmanKeyDerivationFunction.Hash &&
301                             m_kdf <= ECDiffieHellmanKeyDerivationFunction.Tls);
302
303             if (otherPartyPublicKey == null) {
304                 throw new ArgumentNullException("otherPartyPublicKey");
305             }
306             if (otherPartyPublicKey.AlgorithmGroup != CngAlgorithmGroup.ECDiffieHellman) {
307                 throw new ArgumentException(SR.GetString(SR.Cryptography_ArgECDHRequiresECDHKey), "otherPartyPublicKey");
308             }
309             if (otherPartyPublicKey.KeySize != KeySize) {
310                 throw new ArgumentException(SR.GetString(SR.Cryptography_ArgECDHKeySizeMismatch), "otherPartyPublicKey");
311             }
312
313             NCryptNative.SecretAgreementFlags flags =
314                 UseSecretAgreementAsHmacKey ? NCryptNative.SecretAgreementFlags.UseSecretAsHmacKey : NCryptNative.SecretAgreementFlags.None;
315
316             // We require access to the handles for generating key material. This is safe since we will never
317             // expose these handles to user code
318             new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert();
319
320             // This looks horribly wrong - but accessing the handle property actually returns a duplicate handle, which
321             // we need to dispose of - otherwise, we're stuck keepign the resource alive until the GC runs.  This explicitly
322             // is not disposing of the handle underlying the key dispite what the syntax looks like.
323             using (SafeNCryptKeyHandle localKey = Key.Handle)
324             using (SafeNCryptKeyHandle otherKey = otherPartyPublicKey.Handle) {
325                 CodeAccessPermission.RevertAssert();
326
327                 //
328                 // Generating key material is a two phase process.
329                 //   1. Generate the secret agreement
330                 //   2. Pass the secret agreement through a KDF to get key material
331                 //
332
333                 using (SafeNCryptSecretHandle secretAgreement = NCryptNative.DeriveSecretAgreement(localKey, otherKey)) {
334                     if (KeyDerivationFunction == ECDiffieHellmanKeyDerivationFunction.Hash) {
335                         byte[] secretAppend = SecretAppend == null ? null : SecretAppend.Clone() as byte[];
336                         byte[] secretPrepend = SecretPrepend == null ? null : SecretPrepend.Clone() as byte[];
337
338                         return NCryptNative.DeriveKeyMaterialHash(secretAgreement,
339                                                                   HashAlgorithm.Algorithm,
340                                                                   secretPrepend,
341                                                                   secretAppend,
342                                                                   flags);
343                     }
344                     else if (KeyDerivationFunction == ECDiffieHellmanKeyDerivationFunction.Hmac) {
345                         byte[] hmacKey = HmacKey == null ? null : HmacKey.Clone() as byte[];
346                         byte[] secretAppend = SecretAppend == null ? null : SecretAppend.Clone() as byte[];
347                         byte[] secretPrepend = SecretPrepend == null ? null : SecretPrepend.Clone() as byte[];
348
349                         return NCryptNative.DeriveKeyMaterialHmac(secretAgreement,
350                                                                   HashAlgorithm.Algorithm,
351                                                                   hmacKey,
352                                                                   secretPrepend,
353                                                                   secretAppend,
354                                                                   flags);
355                     }
356                     else {
357                         Debug.Assert(KeyDerivationFunction == ECDiffieHellmanKeyDerivationFunction.Tls, "Unknown KDF");
358
359                         byte[] label = Label == null ? null : Label.Clone() as byte[];
360                         byte[] seed = Seed == null ? null : Seed.Clone() as byte[];
361
362                         if (label == null || seed == null) {
363                             throw new InvalidOperationException(SR.GetString(SR.Cryptography_TlsRequiresLabelAndSeed));
364                         }
365
366                         return NCryptNative.DeriveKeyMaterialTls(secretAgreement, label, seed, flags);
367                     }
368                 }
369             }
370         }
371
372         /// <summary>
373         ///     Get a handle to the secret agreement generated between two parties
374         /// </summary>
375         public SafeNCryptSecretHandle DeriveSecretAgreementHandle(ECDiffieHellmanPublicKey otherPartyPublicKey) {
376             if (otherPartyPublicKey == null) {
377                 throw new ArgumentNullException("otherPartyPublicKey");
378             }
379             
380             // We can only work with ECDiffieHellmanCngPublicKeys
381             ECDiffieHellmanCngPublicKey otherKey = otherPartyPublicKey as ECDiffieHellmanCngPublicKey;
382             if (otherPartyPublicKey == null) {
383                 throw new ArgumentException(SR.GetString(SR.Cryptography_ArgExpectedECDiffieHellmanCngPublicKey));
384             }
385
386             using (CngKey importedKey = otherKey.Import()) {
387                 return DeriveSecretAgreementHandle(importedKey);
388             }
389         }
390
391         /// <summary>
392         ///     Get a handle to the secret agreement between two parties
393         /// </summary>
394         [System.Security.SecurityCritical]
395         [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
396         public SafeNCryptSecretHandle DeriveSecretAgreementHandle(CngKey otherPartyPublicKey) {
397             if (otherPartyPublicKey == null) {
398                 throw new ArgumentNullException("otherPartyPublicKey");
399             }
400             if (otherPartyPublicKey.AlgorithmGroup != CngAlgorithmGroup.ECDiffieHellman) {
401                 throw new ArgumentException(SR.GetString(SR.Cryptography_ArgECDHRequiresECDHKey), "otherPartyPublicKey");
402             }
403             if (otherPartyPublicKey.KeySize != KeySize) {
404                 throw new ArgumentException(SR.GetString(SR.Cryptography_ArgECDHKeySizeMismatch), "otherPartyPublicKey");
405             }
406
407             // This looks strange, but the Handle property returns a duplicate so we need to dispose of it when we're done
408             using (SafeNCryptKeyHandle localHandle = Key.Handle)
409             using (SafeNCryptKeyHandle otherPartyHandle = otherPartyPublicKey.Handle) {
410                 return NCryptNative.DeriveSecretAgreement(localHandle, otherPartyHandle);
411             }
412         }
413
414         /// <summary>
415         ///     Clean up the algorithm
416         /// </summary>
417         protected override void Dispose(bool disposing) {
418             try {
419                 if (disposing) {
420                     if (m_key != null) {
421                         m_key.Dispose();
422                     }
423                 }
424             }
425             finally {
426                 base.Dispose(disposing);
427             }
428         }
429
430         //
431         // XML Import
432         //
433         // See code:System.Security.Cryptography.ECDsaCng#ECCXMLFormat and
434         // code:System.Security.Cryptography.Rfc4050KeyFormatter#RFC4050ECKeyFormat for information about
435         // elliptic curve XML formats.
436         //
437
438         public override void FromXmlString(string xmlString) {
439             throw new NotImplementedException(SR.GetString(SR.Cryptography_ECXmlSerializationFormatRequired));
440         }
441
442         public void FromXmlString(string xml, ECKeyXmlFormat format) {
443             if (xml == null) {
444                 throw new ArgumentNullException("xml");
445             }
446             if (format != ECKeyXmlFormat.Rfc4050) {
447                 throw new ArgumentOutOfRangeException("format");
448             }
449
450             Key = Rfc4050KeyFormatter.FromXml(xml);
451         }
452
453         //
454         // XML Export
455         //
456         // See code:System.Security.Cryptography.ECDsaCng#ECCXMLFormat and
457         // code:System.Security.Cryptography.Rfc4050KeyFormatter#RFC4050ECKeyFormat for information about
458         // elliptic curve XML formats.
459         //
460
461         public override string ToXmlString(bool includePrivateParameters) {
462             throw new NotImplementedException(SR.GetString(SR.Cryptography_ECXmlSerializationFormatRequired));
463         }
464
465         public string ToXmlString(ECKeyXmlFormat format) {
466             Contract.Ensures(Contract.Result<string>() != null);
467
468             if (format != ECKeyXmlFormat.Rfc4050) {
469                 throw new ArgumentOutOfRangeException("format");
470             }
471
472             return Rfc4050KeyFormatter.ToXml(Key);
473         }
474     }
475 }