File: System\Security\Cryptography\ECDiffieHellmanCng.Derive.cs
Web Access
Project: src\src\runtime\src\libraries\System.Security.Cryptography\src\System.Security.Cryptography.csproj (System.Security.Cryptography)
// 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 Microsoft.Win32.SafeHandles;

namespace System.Security.Cryptography
{
    public sealed partial class ECDiffieHellmanCng : ECDiffieHellman
    {
        public override byte[] DeriveKeyMaterial(ECDiffieHellmanPublicKey otherPartyPublicKey)
        {
            ArgumentNullException.ThrowIfNull(otherPartyPublicKey);

            if (otherPartyPublicKey is ECDiffieHellmanCngPublicKey otherKey)
            {
                using (CngKey import = otherKey.Import())
                {
                    return DeriveKeyMaterial(import);
                }
            }

            // This deviates from the .NET Framework behavior.  .NET Framework can't handle unknown public
            // key types, but on .NET Core there are automatically two: the public class produced by
            // this class' PublicKey member, and the private class produced by ECDiffieHellman.Create().PublicKey
            //
            // So let's just work.
            ECParameters otherPartyParameters = otherPartyPublicKey.ExportParameters();

            using (ECDiffieHellmanCng otherPartyCng = new ECDiffieHellmanCng())
            {
                otherPartyCng.ImportParameters(otherPartyParameters);

                using (otherKey = (ECDiffieHellmanCngPublicKey)otherPartyCng.PublicKey)
                using (CngKey importedKey = otherKey.Import())
                {
                    return DeriveKeyMaterial(importedKey);
                }
            }
        }

        public byte[] DeriveKeyMaterial(CngKey otherPartyPublicKey)
        {
            ArgumentNullException.ThrowIfNull(otherPartyPublicKey);

            if (otherPartyPublicKey.AlgorithmGroup != CngAlgorithmGroup.ECDiffieHellman)
                throw new ArgumentException(SR.Cryptography_ArgECDHRequiresECDHKey, nameof(otherPartyPublicKey));
            if (otherPartyPublicKey.KeySize != KeySize)
                throw new ArgumentException(SR.Cryptography_ArgECDHKeySizeMismatch, nameof(otherPartyPublicKey));

            // Setting the flag to UseSecretAsHmacKey even when the KDF isn't HMAC, because that's what .NET Framework does.
            Interop.NCrypt.SecretAgreementFlags flags =
                UseSecretAgreementAsHmacKey
                    ? Interop.NCrypt.SecretAgreementFlags.UseSecretAsHmacKey
                    : Interop.NCrypt.SecretAgreementFlags.None;

            using (SafeNCryptSecretHandle handle = DeriveSecretAgreementHandle(otherPartyPublicKey))
            {
                switch (KeyDerivationFunction)
                {
                    case ECDiffieHellmanKeyDerivationFunction.Hash:
                        return Interop.NCrypt.DeriveKeyMaterialHash(
                            handle,
                            HashAlgorithm.Algorithm,
                            _secretPrepend,
                            _secretAppend,
                            flags);
                    case ECDiffieHellmanKeyDerivationFunction.Hmac:
                        return Interop.NCrypt.DeriveKeyMaterialHmac(
                            handle,
                            HashAlgorithm.Algorithm,
                            _hmacKey,
                            _secretPrepend,
                            _secretAppend,
                            flags);
                    case ECDiffieHellmanKeyDerivationFunction.Tls:
                        if (_label == null || _seed == null)
                        {
                            throw new InvalidOperationException(SR.Cryptography_TlsRequiresLabelAndSeed);
                        }

                        return Interop.NCrypt.DeriveKeyMaterialTls(
                            handle,
                            _label,
                            _seed,
                            flags);
                    default:
                        Debug.Fail($"Unknown KDF ({KeyDerivationFunction})");
                        // Match .NET Framework behavior
                        goto case ECDiffieHellmanKeyDerivationFunction.Tls;
                }
            }
        }

        /// <summary>
        ///     Get a handle to the secret agreement generated between two parties
        /// </summary>
        public SafeNCryptSecretHandle DeriveSecretAgreementHandle(ECDiffieHellmanPublicKey otherPartyPublicKey)
        {
            ArgumentNullException.ThrowIfNull(otherPartyPublicKey);

            if (otherPartyPublicKey is ECDiffieHellmanCngPublicKey otherKey)
            {
                using (CngKey importedKey = otherKey.Import())
                {
                    return DeriveSecretAgreementHandle(importedKey);
                }
            }

            ECParameters otherPartyParameters = otherPartyPublicKey.ExportParameters();

            using (ECDiffieHellmanCng otherPartyCng = new ECDiffieHellmanCng())
            {
                otherPartyCng.ImportParameters(otherPartyParameters);

                using (otherKey = (ECDiffieHellmanCngPublicKey)otherPartyCng.PublicKey)
                using (CngKey importedKey = otherKey.Import())
                {
                    return DeriveSecretAgreementHandle(importedKey);
                }
            }
        }

        /// <summary>
        ///     Get a handle to the secret agreement between two parties
        /// </summary>
        public SafeNCryptSecretHandle DeriveSecretAgreementHandle(CngKey otherPartyPublicKey)
        {
            ArgumentNullException.ThrowIfNull(otherPartyPublicKey);

            if (otherPartyPublicKey.AlgorithmGroup != CngAlgorithmGroup.ECDiffieHellman)
                throw new ArgumentException(SR.Cryptography_ArgECDHRequiresECDHKey, nameof(otherPartyPublicKey));
            if (otherPartyPublicKey.KeySize != KeySize)
                throw new ArgumentException(SR.Cryptography_ArgECDHKeySizeMismatch, nameof(otherPartyPublicKey));

            // This looks strange, but the Handle property returns a duplicate so we need to dispose of it when we're done
            using (SafeNCryptKeyHandle localHandle = Key.Handle)
            using (SafeNCryptKeyHandle otherPartyHandle = otherPartyPublicKey.Handle)
            {
                return Interop.NCrypt.DeriveSecretAgreement(localHandle, otherPartyHandle);
            }
        }
    }
}