使用带有外部 Azure KeyVault (HSM) 密钥的 BouncyCastle 创建证书时签名无效
Invalid signature when creating a certificate using BouncyCastle with an external Azure KeyVault (HSM) Key
我正在尝试生成由存储在 Azure KeyVault 中的密钥对自签名的证书。
我的最终结果是一个带有无效签名的证书:
正在生成证书参数:
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);
获取对 Azure KeyVault 存储密钥的引用(类似 HSM 的服务):
//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
密钥已成功检索。
然后我尝试使用密钥的 public 数据生成一个 ECPublicKeyParameters 对象:
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
下一步是生成一个用密钥签名的证书。我实现了一个新的 ISignatureFactory 对象,它应该使用 KeyVault 的外部签名函数进行签名:
AzureKeyVaultSignatureFactory customSignatureFactory = new AzureKeyVaultSignatureFactory(1);
Org.BouncyCastle.X509.X509Certificate cert = certGen.Generate(customSignatureFactory);
这是我的自定义 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);
}
然后我将证书转换并写入文件:
//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!
我做错了什么?也许从 X/Y 构建 ECCurve 还不够?
谢谢!
问题是密钥保管库 return 编辑的签名是“原始”(64 字节)格式,其中前 32 个是 R最后 32 个是 S。为了让它在 bouncycastle 中工作,你的 GenerateSignature
方法需要 return 在 ASN.1 格式的字节数组中,它最终将在 70 到 72 字节之间。
您可以在线查看这实际上意味着什么,但您需要:
- 为您的结果创建一个新的字节数组
- 将 Key Vault 的输出拆分为两个最初的 32 位数组,R 和 S
- 如果R或S数组的第0个元素的MSB高,则需要在相应数组的开始(否则什么也不做,数组保持 32 字节长)。
- 构建必要的 ASN.1 headers(像我在下面展示的那样手动构建,或者 bouncycastle 可能有一些库功能来创建 ASN.1 消息)。所以最后,输出字节数组应该包含[=36=]
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
- Return 这个数组作为
GenerateSignature
的输出
* 所以整个长度将是 R 的长度 + S 的长度 + 4 header 字节(R 长度,R header,S 长度,S header)
我已经用我自己的密钥测试了这种方法,由云服务 return 编辑,该服务还 return 64 字节 R+S 响应并且有效。
我正在尝试生成由存储在 Azure KeyVault 中的密钥对自签名的证书。
我的最终结果是一个带有无效签名的证书:
正在生成证书参数:
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);
获取对 Azure KeyVault 存储密钥的引用(类似 HSM 的服务):
//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
密钥已成功检索。 然后我尝试使用密钥的 public 数据生成一个 ECPublicKeyParameters 对象:
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
下一步是生成一个用密钥签名的证书。我实现了一个新的 ISignatureFactory 对象,它应该使用 KeyVault 的外部签名函数进行签名:
AzureKeyVaultSignatureFactory customSignatureFactory = new AzureKeyVaultSignatureFactory(1);
Org.BouncyCastle.X509.X509Certificate cert = certGen.Generate(customSignatureFactory);
这是我的自定义 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);
}
然后我将证书转换并写入文件:
//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!
我做错了什么?也许从 X/Y 构建 ECCurve 还不够?
谢谢!
问题是密钥保管库 return 编辑的签名是“原始”(64 字节)格式,其中前 32 个是 R最后 32 个是 S。为了让它在 bouncycastle 中工作,你的 GenerateSignature
方法需要 return 在 ASN.1 格式的字节数组中,它最终将在 70 到 72 字节之间。
您可以在线查看这实际上意味着什么,但您需要:
- 为您的结果创建一个新的字节数组
- 将 Key Vault 的输出拆分为两个最初的 32 位数组,R 和 S
- 如果R或S数组的第0个元素的MSB高,则需要在相应数组的开始(否则什么也不做,数组保持 32 字节长)。
- 构建必要的 ASN.1 headers(像我在下面展示的那样手动构建,或者 bouncycastle 可能有一些库功能来创建 ASN.1 消息)。所以最后,输出字节数组应该包含[=36=]
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
- Return 这个数组作为
GenerateSignature
的输出
* 所以整个长度将是 R 的长度 + S 的长度 + 4 header 字节(R 长度,R header,S 长度,S header)
我已经用我自己的密钥测试了这种方法,由云服务 return 编辑,该服务还 return 64 字节 R+S 响应并且有效。