generate certificate using ECDSA in c#

ⅰ亾dé卋堺 提交于 2019-11-28 01:30:45

Unfortunately, it's not possible to do straight out of the box right now. You can get the rest of the way with P/Invokes and .NET 4.6.2 (currently in preview). Or, with a detour through .NET Core you can build a PFX that works in .NET 4.6.1.

"ECDSA" vs "ECDH"

The Windows CNG libraries split ECC into ECDSA and ECDH. ECDSA key objects can only be used for ECDSA; but whenever Windows can't determine the usage during a PFX import (or PKCS#8 import) it calls a private key ECDH. Why? Because Windows lets ECDH key objects do both key agreement (ECDH) and digital signature (ECDSA), so ECDH is more flexible.

But .NET 4.6.1 didn't know that.

.NET Core doesn't have this limitation (see https://github.com/dotnet/corefx/pull/5850), and .NET 4.6.2 has also removed the restriction (per https://github.com/Microsoft/dotnet/blob/master/releases/net462/dotnet462-changes.md#user-content-bcl).

Generating "ECDSA" keys, instead of "ECDH"

.NET Core now has an ImportParameters method on ECDsa. If you can translate the BC.ECPrivateKeyProperty object to an MS.ECParameters structure you can import the blob into an ECDsaCng object. (Be sure to use it as a named curve, instead of explicitly copying all of the curve parameters).

Since it was purposefully imported into an ECDsa object it gets an ECDSA key, and that information will be embedded in the PFX.

Building the PFX (tying it all together)

With a bit of P/Invoking you can convince Windows to build a PFX using an ephemeral key. While .NET can't access ephemeral private keys from certificates, it will be able to make use of it if loaded from a PFX:

[DllImport(Libraries.Crypt32, CharSet = CharSet.Unicode, SetLastError = true)]
private static extern unsafe bool CertSetCertificateContextProperty(IntPtr pCertContext, CertContextPropId dwPropId, CertSetPropertyFlags dwFlags, SafeNCryptKeyHandle pvData);

internal enum CertContextPropId : int
{
    CERT_NCRYPT_KEY_HANDLE_PROP_ID = 78,
}

[Flags]
internal enum CertSetPropertyFlags : int
{
    None = 0,
}

private static X509Certificate2 MateECDsaPrivateKey(
    X509Certificate2 cert,
    CngKey privateKey)
{
    // Make a new certificate instance which isn't tied to the current one
    using (var tmpCert = new X509Certificate2(cert.RawData))
    {
        SafeNCryptKeyHandle keyHandle = privateKey.Handle;

        // Set the ephemeral key handle property
        if (!CertSetCertificateContextProperty(
            tmpCert.Handle,
            CertContextPropId.CERT_NCRYPT_KEY_HANDLE_PROP_ID,
            CertSetPropertyFlags.None,
            keyHandle))
        {
            throw new CryptographicException(Marshal.GetLastWin32Error());
        }

        // You could emit this, if you prefer.
        byte[] pfxBytes = tmpCert.Export(X509ContentType.Pkcs12);

        // Clear the key handle out again to prevent double-free
        keyHandle = new SafeNCryptKeyHandle();

        if (!CertSetCertificateContextProperty(
            tmpCert.Handle,
            CertContextPropId.CERT_NCRYPT_KEY_HANDLE_PROP_ID,
            CertSetPropertyFlags.None,
            keyHandle))
        {
            throw new CryptographicException(Marshal.GetLastWin32Error());
        }

        // Now load a new certificate which has a temporary keyfile on disk.
        // Note: If you don't want exportability here, don't request it.
        var matedCert = new X509Certificate2(pfxBytes, (string)null, X509KeyStorageFlags.Exportable);

        using (ECDsa ecdsa = matedCert.GetECDsaPrivateKey())
        {
            if (ecdsa == null)
            {
                throw new InvalidOperationException("It didn't work");
            }
        }

        return matedCert;
    }
}

You'll need .NET 4.6.1 (or newer) to have access to GetECDsaPrivateKey().

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!