1

I need to create "Client" ECDSA certificate signed by "Root" certificate (self-signed, ECDSA). "Root" certificate was created as described in Translating Elliptic Curve parameters (BC to MS).

To create "Client" certificate (signed by "Root") slightly modified algorithm can be used. The difference is that the private key (used to sign public key from keypair generated for "Client" certificate) must be supplied from the "outside" - it is a private-key of "Root" certificate. But this is the issue. I cannot find a way how to get and translate private key to type Org.BouncyCastle.Crypto.Parameters.ECPrivateKeyParameters that could be passed to signature-factory.

// 1. get private-key of "Root" certificate from existing certificate:
byte[] msRootCertData = File.ReadAllBytes(@"c:\root_ecdsa_cert.pfx");
X509Certificate2 msRootCert = new X509Certificate2(msRootCertData);
ECDsaCng msRootPrivateKey = msRootCert.GetECDsaPrivateKey() as ECDsaCng;
ECParameters msRootPrivateKeyParameters = msRootPrivateKey.ExportParameters(true);

// here comes the issue:
ECPrivateKeyParameters bcRootPrivateKeysParameters = TranslateMSKeysToBouncy(msRootPrivateKeyParameters);

// 2. generate "Client" key-pair:
AsymmetricCipherKeyPair bcClientKeyPair = bcKeyGen.GenerateKeyPair();
ECPrivateKeyParameters bcClientPrivKey = (ECPrivateKeyParameters)bcClientKeyPair.Private;
ECPrivateKeyParameters bcClientPublKey = (ECPublicKeyParameters)bcClientKeyPair.Public;

// 3. create X509 certificate:
X509V3CertificateGenerator bcCertGen = new X509V3CertificateGenerator();
bcCertGen.SetPublicKey(bcClientPublKey);
// .. set subject, validity period etc
ISignatureFactory sigFac = new Asn1SignatureFactory("Sha256WithECDSA", bcRootPrivateKeysParameters);
Org.BouncyCastle.X509.X509Certificate bcClientX509Cert = bcCertGen.Generate(sigFac);
byte[] x509CertEncoded = bcClientX509Cert.GetEncoded();

// the rest is the same as in the mentioned example.

Any hints? Or is there other way? (for example: passing instance of X509Certificate2 directly to BouncyCastle library (avoid to translate private-keys to Cng), or generating "Client" certificate without BouncyCastle) Thanks.

grim.ub
  • 192
  • 2
  • 11

1 Answers1

4

If you can take a dependency on .NET Framework 4.7.2 (or .NET Core 2.0) you can do it without BouncyCastle, via the new CertificateRequest class:

X509Certificate2 publicPrivate;

using (ECDsa clientPrivateKey = ECDsa.Create())
{
    var request = new CertificateRequest(
        "CN=Et. Cetera",
        clientPrivateKey,
        HashAlgorithmName.SHA256);

    // Assuming this isn't another CA cert:
    request.CertificateExtensions.Add(
        new X509BasicConstraintsExtension(false, false, 0, false));

    // other CertificateExtensions as you desire.

    // Assign, or derive, a serial number.
    // RFC 3280 recommends that it have no more than 20 bytes encoded.
    // 12 random bytes seems long enough.
    byte[] serial = new byte[12];

    using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
    {
        rng.GetBytes(serial);
    }

    DateTimeOffset notBefore = DateTimeOffset.UtcNow;
    DateTimeOffset notAfter = notBefore.AddMonths(15);

    using (X509Certificate2 publicOnly = request.Create(
        msRootCert,
        notBefore,
        notAfter,
        serial))
    {
        publicPrivate = publicOnly.CopyWithPrivateKey(clientPrivateKey);
    }
}

// The original key object was disposed,
// but publicPrivate.GetECDsaPrivateKey() still works.

If you want to add publicPrivate to an X509Store you need to either 1) export it to a PFX and re-import it, or 2) change the key creation to use a named key. Otherwise, only the public portion will be saved (on Windows).

bartonjs
  • 30,352
  • 2
  • 71
  • 111
  • I can use 4.7.1 only.. Forgot to mention that. Sorry. Any other way? – grim.ub Jun 03 '18 at 06:25
  • The last paragraph is very helpful - I was struggling to make netsh accept my cert because the private key was not being stored correctly. This did the trick: `var cert = new X509Certificate2(tempCert.Export(X509ContentType.Pfx), null as string, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet);` Now after adding that cert to the LocalMachine AuthRoot store, it finally works! – piedar Oct 03 '18 at 19:23
  • finally switched to 4.7.2. @piedar: useful comment for me. (wasn't able to bind ecdsa cert using netsh or via IIS Manager (inetmgr.exe) on p.443). Thanks. – grim.ub Oct 05 '18 at 11:30
  • Or just call "CertificateRequest.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.MaxValue);" – zezba9000 May 16 '19 at 23:58