Is it possible to create a RSA-SHA1 signature with a X509-certificate stored in Azure Key Vault? [non-repudiation certificate]
Unfortunately, I can't change the hash-algorithm to SHA256 or something safer, and I really need to store the certficate in Azure Key Vault as Key.
So far I've tried
await kvClient.SignAsync(keyVaultUrl, "RSNULL", digest); // digest = 20byte SHA1
await kvClient.SignAsync(keyVaultUrl, "RSNULL", ans1Digest); // asn1Digest = 35byte SHA1 wrapped in ANS1 structure
The signature length seems to be correct (256 bytes), but verification fails (on a node with a correctly implemented signature-verification implementation).
I've also tried to implement the signature-algorithm manually like this (using keyVault.EncryptAsync
):
I must be doing something wrong. Not sure if all steps are needed.
If anyone from Microsoft reads this. Can you implement SHA1 signing even if it isn't considered safe? Pretty, please with sugar on top :-)
November 2020 Update:
Here is a link to the sample describing how to do this using the latest Azure SDK client library:
How to encrypt and decrypt a single block of plain text with an RSA key
Note that it also describes use of the DefaultAzureCredential for authentication, which is much simpler to use than the previous callback pattern.
There is also a migration guide comparing how this would be done with the older client here.
My blindly shot "professional" opinion is that you are not constructring PKCS#1 DigestInfo structure correctly. Following console application is working fine for me with both SHA1 and SHA256 algorithms (didn't test the others):
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.KeyVault;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
namespace AzureKeyVaultTestApp1
{
static class Program
{
static HashAlgorithmName _hashAlg = HashAlgorithmName.SHA1;
static string _clientId = "00000000-0000-0000-0000-000000000000";
static string _clientSecret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
static string _certId = "https://XXXXXXXX.vault.azure.net/certificates/TestCert1/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
static string _keyId = "https://XXXXXXXX.vault.azure.net/keys/TestCert1/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
static async Task<string> AuthenticationCallback(string authority, string resource, string scope)
{
var context = new AuthenticationContext(authority);
var result = await context.AcquireTokenAsync(resource, new ClientCredential(_clientId, _clientSecret));
return result.AccessToken;
}
static async Task Main(string[] args)
{
KeyVaultClient client = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(AuthenticationCallback));
// Compute digest of data
byte[] dataToSign = Encoding.ASCII.GetBytes("Hello world!");
byte[] hash = HashAlgorithm.Create(_hashAlg.Name).ComputeHash(dataToSign);
// Construct DER encoded PKCS#1 DigestInfo structure defined in RFC 8017
byte[] pkcs1DigestInfo = CreatePkcs1DigestInfo(hash, _hashAlg);
// Sign digest with private key
var keyOperationResult = await client.SignAsync(_keyId, "RSNULL", pkcs1DigestInfo).ConfigureAwait(false);
byte[] signature = keyOperationResult.Result;
// Get public key from certificate
var certBundle = await client.GetCertificateAsync(_certId).ConfigureAwait(false);
X509Certificate2 cert = new X509Certificate2(certBundle.Cer);
RSA rsaPubKey = cert.GetRSAPublicKey();
// Verify digest signature with public key
if (!rsaPubKey.VerifyHash(hash, signature, _hashAlg, RSASignaturePadding.Pkcs1))
throw new Exception("Invalid signature");
}
private static byte[] CreatePkcs1DigestInfo(byte[] hash, HashAlgorithmName hashAlgorithm)
{
if (hash == null || hash.Length == 0)
throw new ArgumentNullException(nameof(hash));
byte[] pkcs1DigestInfo = null;
if (hashAlgorithm == HashAlgorithmName.MD5)
{
if (hash.Length != 16)
throw new ArgumentException("Invalid lenght of hash value");
pkcs1DigestInfo = new byte[] { 0x30, 0x20, 0x30, 0x0C, 0x06, 0x08, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
Array.Copy(hash, 0, pkcs1DigestInfo, pkcs1DigestInfo.Length - hash.Length, hash.Length);
}
else if (hashAlgorithm == HashAlgorithmName.SHA1)
{
if (hash.Length != 20)
throw new ArgumentException("Invalid lenght of hash value");
pkcs1DigestInfo = new byte[] { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
Array.Copy(hash, 0, pkcs1DigestInfo, pkcs1DigestInfo.Length - hash.Length, hash.Length);
}
else if (hashAlgorithm == HashAlgorithmName.SHA256)
{
if (hash.Length != 32)
throw new ArgumentException("Invalid lenght of hash value");
pkcs1DigestInfo = new byte[] { 0x30, 0x31, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
Array.Copy(hash, 0, pkcs1DigestInfo, pkcs1DigestInfo.Length - hash.Length, hash.Length);
}
else if (hashAlgorithm == HashAlgorithmName.SHA384)
{
if (hash.Length != 48)
throw new ArgumentException("Invalid lenght of hash value");
pkcs1DigestInfo = new byte[] { 0x30, 0x41, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
Array.Copy(hash, 0, pkcs1DigestInfo, pkcs1DigestInfo.Length - hash.Length, hash.Length);
}
else if (hashAlgorithm == HashAlgorithmName.SHA512)
{
if (hash.Length != 64)
throw new ArgumentException("Invalid lenght of hash value");
pkcs1DigestInfo = new byte[] { 0x30, 0x51, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
Array.Copy(hash, 0, pkcs1DigestInfo, pkcs1DigestInfo.Length - hash.Length, hash.Length);
}
return pkcs1DigestInfo;
}
}
}
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