3 // Copyright (c) Microsoft Corporation. All rights reserved.
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;
18 namespace System.Security.Cryptography {
20 /// Key derivation functions used to transform the raw secret agreement into key material
22 public enum ECDiffieHellmanKeyDerivationFunction {
29 /// Wrapper for CNG's implementation of elliptic curve Diffie-Hellman key exchange
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) };
35 private CngAlgorithm m_hashAlgorithm = CngAlgorithm.Sha256;
36 private byte[] m_hmacKey;
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;
48 public ECDiffieHellmanCng() : this(521) {
49 Contract.Ensures(LegalKeySizesValue != null);
52 public ECDiffieHellmanCng(int keySize) {
53 Contract.Ensures(LegalKeySizesValue != null);
55 if (!NCryptNative.NCryptSupported) {
56 throw new PlatformNotSupportedException(SR.GetString(SR.Cryptography_PlatformNotSupported));
59 LegalKeySizesValue = s_legalKeySizes;
63 [SecuritySafeCritical]
64 public ECDiffieHellmanCng(CngKey key) {
65 Contract.Ensures(LegalKeySizesValue != null);
66 Contract.Ensures(m_key != null && m_key.AlgorithmGroup == CngAlgorithmGroup.ECDiffieHellman);
69 throw new ArgumentNullException("key");
71 if (key.AlgorithmGroup != CngAlgorithmGroup.ECDiffieHellman) {
72 throw new ArgumentException(SR.GetString(SR.Cryptography_ArgECDHRequiresECDHKey), "key");
75 if (!NCryptNative.NCryptSupported) {
76 throw new PlatformNotSupportedException(SR.GetString(SR.Cryptography_PlatformNotSupported));
79 LegalKeySizesValue = s_legalKeySizes;
81 // Make a copy of the key so that we continue to work if it gets disposed before this algorithm
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).
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);
94 CodeAccessPermission.RevertAssert();
96 KeySize = m_key.KeySize;
100 /// Hash algorithm used with the Hash and HMAC KDFs
102 public CngAlgorithm HashAlgorithm {
104 Contract.Ensures(Contract.Result<CngAlgorithm>() != null);
105 return m_hashAlgorithm;
109 Contract.Ensures(m_hashAlgorithm != null);
111 if (m_hashAlgorithm == null) {
112 throw new ArgumentNullException("value");
115 m_hashAlgorithm = value;
120 /// Key used with the HMAC KDF
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; }
129 /// KDF used to transform the secret agreement into key material
131 public ECDiffieHellmanKeyDerivationFunction KeyDerivationFunction {
133 Contract.Ensures(Contract.Result<ECDiffieHellmanKeyDerivationFunction>() >= ECDiffieHellmanKeyDerivationFunction.Hash &&
134 Contract.Result<ECDiffieHellmanKeyDerivationFunction>() <= ECDiffieHellmanKeyDerivationFunction.Tls);
140 Contract.Ensures(m_kdf >= ECDiffieHellmanKeyDerivationFunction.Hash &&
141 m_kdf <= ECDiffieHellmanKeyDerivationFunction.Tls);
143 if (value < ECDiffieHellmanKeyDerivationFunction.Hash || value > ECDiffieHellmanKeyDerivationFunction.Tls) {
144 throw new ArgumentOutOfRangeException("value");
152 /// Label bytes used for the TLS KDF
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; }
161 /// Bytes to append to the raw secret agreement before processing by the KDF
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; }
170 /// Bytes to prepend to the raw secret agreement before processing by the KDF
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; }
179 /// Seed bytes used for the TLS KDF
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")]
183 get { return m_seed; }
184 set { m_seed = value; }
188 /// Full key pair being used for key generation
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);
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) {
204 // Map the current key size to a CNG algorithm name
205 CngAlgorithm algorithm = null;
208 algorithm = CngAlgorithm.ECDiffieHellmanP256;
212 algorithm = CngAlgorithm.ECDiffieHellmanP384;
216 algorithm = CngAlgorithm.ECDiffieHellmanP521;
220 Debug.Assert(false, "Illegal key size set");
224 m_key = CngKey.Create(algorithm);
231 Contract.Requires(value != null);
232 Contract.Ensures(m_key != null && m_key.AlgorithmGroup == CngAlgorithmGroup.ECDiffieHellman);
234 if (value.AlgorithmGroup != CngAlgorithmGroup.ECDiffieHellman) {
235 throw new ArgumentException(SR.GetString(SR.Cryptography_ArgECDHRequiresECDHKey));
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
250 KeySize = m_key.KeySize;
255 /// Public key used to generate key material with the second party
257 public override ECDiffieHellmanPublicKey PublicKey {
259 Contract.Ensures(Contract.Result<ECDiffieHellmanPublicKey>() != null);
260 return new ECDiffieHellmanCngPublicKey(Key);
265 /// Use the secret agreement as the HMAC key rather than supplying a seperate one
267 public bool UseSecretAgreementAsHmacKey {
268 get { return HmacKey == null; }
272 /// Given a second party's public key, derive shared key material
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);
279 if (otherPartyPublicKey == null) {
280 throw new ArgumentNullException("otherPartyPublicKey");
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));
289 using (CngKey import = otherKey.Import()) {
290 return DeriveKeyMaterial(import);
295 /// Given a second party's public key, derive shared key material
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);
303 if (otherPartyPublicKey == null) {
304 throw new ArgumentNullException("otherPartyPublicKey");
306 if (otherPartyPublicKey.AlgorithmGroup != CngAlgorithmGroup.ECDiffieHellman) {
307 throw new ArgumentException(SR.GetString(SR.Cryptography_ArgECDHRequiresECDHKey), "otherPartyPublicKey");
309 if (otherPartyPublicKey.KeySize != KeySize) {
310 throw new ArgumentException(SR.GetString(SR.Cryptography_ArgECDHKeySizeMismatch), "otherPartyPublicKey");
313 NCryptNative.SecretAgreementFlags flags =
314 UseSecretAgreementAsHmacKey ? NCryptNative.SecretAgreementFlags.UseSecretAsHmacKey : NCryptNative.SecretAgreementFlags.None;
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();
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();
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
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[];
338 return NCryptNative.DeriveKeyMaterialHash(secretAgreement,
339 HashAlgorithm.Algorithm,
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[];
349 return NCryptNative.DeriveKeyMaterialHmac(secretAgreement,
350 HashAlgorithm.Algorithm,
357 Debug.Assert(KeyDerivationFunction == ECDiffieHellmanKeyDerivationFunction.Tls, "Unknown KDF");
359 byte[] label = Label == null ? null : Label.Clone() as byte[];
360 byte[] seed = Seed == null ? null : Seed.Clone() as byte[];
362 if (label == null || seed == null) {
363 throw new InvalidOperationException(SR.GetString(SR.Cryptography_TlsRequiresLabelAndSeed));
366 return NCryptNative.DeriveKeyMaterialTls(secretAgreement, label, seed, flags);
373 /// Get a handle to the secret agreement generated between two parties
375 public SafeNCryptSecretHandle DeriveSecretAgreementHandle(ECDiffieHellmanPublicKey otherPartyPublicKey) {
376 if (otherPartyPublicKey == null) {
377 throw new ArgumentNullException("otherPartyPublicKey");
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));
386 using (CngKey importedKey = otherKey.Import()) {
387 return DeriveSecretAgreementHandle(importedKey);
392 /// Get a handle to the secret agreement between two parties
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");
400 if (otherPartyPublicKey.AlgorithmGroup != CngAlgorithmGroup.ECDiffieHellman) {
401 throw new ArgumentException(SR.GetString(SR.Cryptography_ArgECDHRequiresECDHKey), "otherPartyPublicKey");
403 if (otherPartyPublicKey.KeySize != KeySize) {
404 throw new ArgumentException(SR.GetString(SR.Cryptography_ArgECDHKeySizeMismatch), "otherPartyPublicKey");
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);
415 /// Clean up the algorithm
417 protected override void Dispose(bool disposing) {
426 base.Dispose(disposing);
433 // See code:System.Security.Cryptography.ECDsaCng#ECCXMLFormat and
434 // code:System.Security.Cryptography.Rfc4050KeyFormatter#RFC4050ECKeyFormat for information about
435 // elliptic curve XML formats.
438 public override void FromXmlString(string xmlString) {
439 throw new NotImplementedException(SR.GetString(SR.Cryptography_ECXmlSerializationFormatRequired));
442 public void FromXmlString(string xml, ECKeyXmlFormat format) {
444 throw new ArgumentNullException("xml");
446 if (format != ECKeyXmlFormat.Rfc4050) {
447 throw new ArgumentOutOfRangeException("format");
450 Key = Rfc4050KeyFormatter.FromXml(xml);
456 // See code:System.Security.Cryptography.ECDsaCng#ECCXMLFormat and
457 // code:System.Security.Cryptography.Rfc4050KeyFormatter#RFC4050ECKeyFormat for information about
458 // elliptic curve XML formats.
461 public override string ToXmlString(bool includePrivateParameters) {
462 throw new NotImplementedException(SR.GetString(SR.Cryptography_ECXmlSerializationFormatRequired));
465 public string ToXmlString(ECKeyXmlFormat format) {
466 Contract.Ensures(Contract.Result<string>() != null);
468 if (format != ECKeyXmlFormat.Rfc4050) {
469 throw new ArgumentOutOfRangeException("format");
472 return Rfc4050KeyFormatter.ToXml(Key);