11

I'm trying to generate a certificate self-signed by a KeyPair stored in Azure KeyVault.

My end result is a certificate with an invalid signature:

enter image description here

Generating the certificate parameters:

     DateTime startDate = DateTime.Now.AddDays(-30);
     DateTime expiryDate = startDate.AddYears(100);

     BigInteger serialNumber = new BigInteger(32, new Random());
     X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();

     X509Name selfSignedCA = new X509Name("CN=Test Root CA");

     certGen.SetSerialNumber(serialNumber);
     certGen.SetIssuerDN(selfSignedCA); //Self Signed
     certGen.SetNotBefore(startDate);
     certGen.SetNotAfter(expiryDate);
     certGen.SetSubjectDN(selfSignedCA);
      

Fetching a reference to the Azure KeyVault stored key (HSM like service):

    //Create a client connector to Azure KeyVault
    var keyClient = new Azure.Security.KeyVault.Keys.KeyClient(
         vaultUri: new Uri("https://xxxx.vault.azure.net/"),
         credential: new ClientSecretCredential(
             tenantId: "xxxx", //Active Directory
             clientId: "xxxx", //Application id?
             clientSecret: "xxxx"
             )
         );

        var x = keyClient.GetKey("key-new-ec"); //Fetch the reference to the key

The key is successfully retrieved. I then try to generate a ECPublicKeyParameters object with the key's public data:

    X9ECParameters x9 = ECNamedCurveTable.GetByName("P-256");
    Org.BouncyCastle.Math.EC.ECCurve curve = x9.Curve;

    var ecPoint = curve.CreatePoint(new Org.BouncyCastle.Math.BigInteger(1, x.Value.Key.X), new Org.BouncyCastle.Math.BigInteger(1, x.Value.Key.Y));
    ECDomainParameters dParams = new ECDomainParameters(curve, ecPoint, x9.N);
    ECPublicKeyParameters pubKey = new ECPublicKeyParameters(ecPoint, dParams);

    certGen.SetPublicKey(pubKey); //Setting the certificate's public key with the fetched one

Next step is generating a certificate signed with the key. I implemented a new ISignatureFactory object that should sign with an external signature function of KeyVault:

      AzureKeyVaultSignatureFactory customSignatureFactory = new AzureKeyVaultSignatureFactory(1);
      Org.BouncyCastle.X509.X509Certificate cert = certGen.Generate(customSignatureFactory);

This is my custom AzureKeyVaultSignatureFactory:

public class AzureKeyVaultSignatureFactory : ISignatureFactory
{
    private readonly int _keyHandle;

    public AzureKeyVaultSignatureFactory(int keyHandle)
    {
        this._keyHandle = keyHandle;
    }

    public IStreamCalculator CreateCalculator()
    {
        var sig = new CustomAzureKeyVaultDigestSigner(this._keyHandle);

        sig.Init(true, null);

        return new DefaultSignatureCalculator(sig);
    }

    internal class CustomAzureKeyVaultDigestSigner : ISigner
    {
        private readonly int _keyHandle;
        private byte[] _input;

        public CustomAzureKeyVaultDigestSigner(int keyHandle)
        {
            this._keyHandle = keyHandle;
        }

        public void Init(bool forSigning, ICipherParameters parameters)
        {
            this.Reset();
        }

        public void Update(byte input)
        {
            return;
        }

        public void BlockUpdate(byte[] input, int inOff, int length)
        {
            this._input = input.Skip(inOff).Take(length).ToArray();
        }

        public byte[] GenerateSignature()
        {
            //Crypto Client (Specific Key)
            try
            {

                //Crypto Client (Specific Key)
                CryptographyClient identitiesCAKey_cryptoClient = new CryptographyClient(
                    keyId: new Uri("https://xxxx.vault.azure.net/keys/key-new-ec/xxxx"),
                    credential: new ClientSecretCredential(

                          tenantId: "xxxx", //Active Directory
                          clientId: "xxxx", //Application id?
                          clientSecret: "xxxx"
                          )
                );

                SignResult signResult = identitiesCAKey_cryptoClient.SignData(SignatureAlgorithm.ES256, this._input);
                return signResult.Signature;


            }
            catch (Exception ex)
            {

                throw ex;
            }

            return null;
        }

        public bool VerifySignature(byte[] signature)
        {
            return false;
        }

        public void Reset() { }

        public string AlgorithmName => "SHA-256withECDSA";
    }

    public object AlgorithmDetails => new AlgorithmIdentifier(X9ObjectIdentifiers.ECDsaWithSha256, DerNull.Instance);
}

Then I convert and write the certificate to a file:

 //convert to windows type 2 and get Base64 
 X509Certificate2 cert2 = new X509Certificate2(DotNetUtilities.ToX509Certificate(cert));
 byte[] encoded = cert2.GetRawCertData();
 string certOutString = Convert.ToBase64String(encoded);
 System.IO.File.WriteAllBytes(@"test-signed2.cer", encoded); //-this is good!

What am I doing wrong? Maybe constructing the ECCurve from X/Y is not enough?

Thanks!

NOP-MOV
  • 792
  • 2
  • 8
  • 28
  • Wow, that is incredibly complicated for a simple ecdsa signature. One thing for certain that's wrong, your signature algorithm is not `public string AlgorithmName => "SHA-256withRSA";` – President James K. Polk Aug 05 '20 at 19:55
  • It is, because the signature happens externally and apparently it is not a common practice. I would love it if Azure had a PKI service but they don't. Thanks, I will check the algorithm name. – NOP-MOV Aug 06 '20 at 03:10
  • Changed to SHA-256withECDSA, same error – NOP-MOV Aug 08 '20 at 03:01
  • Is this key a BYOK? Was it created in accordance with Microsoft guidelines and does it meet all the prerequisites? – mnistic Aug 09 '20 at 21:57
  • @mnistic BYOK? what is that? it's just a plain self signed ECDSA with SHA256 certificate. I don't know, that's a part of my question... – NOP-MOV Aug 10 '20 at 07:09
  • BYOK is an HSM bound key generated in accordance with the MS guidelines: https://learn.microsoft.com/en-us/azure/key-vault/keys/hsm-protected-keys – mnistic Aug 10 '20 at 13:21
  • Did you generate this key yourself following those instructions? – mnistic Aug 10 '20 at 13:21
  • The key pair was generated using Azure KeyVault with a click of a button. Nothing programmatic around it, all in their cloud UI. I didn't import the keys but generated them already inside the HSM. – NOP-MOV Aug 10 '20 at 17:30
  • Ah OK, safe to assume the key is compliant then... – mnistic Aug 10 '20 at 22:56

1 Answers1

2

The problem is that the signature being returned by key vault is in a "raw" (64-byte) format, where the first 32 are R and the last 32 are S. For this to work in bouncycastle, your GenerateSignature method needs to return this in an ASN.1 formatted byte array, which in the end will be somewhere between 70 and 72 bytes.

You can look around online on what this practically means, but you will want to:

  1. Create a new byte array for your result
  2. split the output from key vault into two initially 32-bit arrays, R and S
  3. If the 0th element of either of the R or S arrays has a high MSB, you need to insert a 0 before the start of the respective array (otherwise do nothing and the array stays 32 bytes long).
  4. Build the necessary ASN.1 headers (either manually like I showed below, or maybe bouncycastle has some library features to create an ASN.1 message). So at the end, the output byte array should contain
0x30
one byte containing the length of the rest of the array*
0x02
a byte containing the length of the R array (either 32 or 33 depending on if + or -)
0x02
a byte containing the length of the S array (either 32 or 33 depending on if + or -)
the entire S array

  1. Return this array as the output of GenerateSignature

* so the entire length will be length of R + length of S + 4 header bytes (R length, R header, S length, S header)

I have tested this approach with a key of my own as returned by a cloud service which also returns the 64 byte R+S response and it works.