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:
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!
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:
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With