使用Bouncycastle存储PKCS#12容器(pfx) [英] Store PKCS#12 Container (pfx) with Bouncycastle
问题描述
我正在努力使用Xamarin和BouncyCastle创建pfx文件. 我有以下设置/规格
- .NET:PCL .Net Framework 4.5.1
- Xamarin:4.5.0.476
- BouncyCastle:BouncyCastle签名的1.7.0.1( NuGet软件包)
我想为我的移动客户端生成一个自签名证书,以针对我的服务器进行身份验证.使用BouncyCastle可以很好地进行创建.我的问题是,当我要将带有私钥的证书存储为PKCS#12(pfx)容器并从该容器中还原该证书以发送由其签名的Web请求时,抛出异常,告诉我Input data cannot be coded as a valide certificate
.>
以下是我创建证书,密钥和存储pfx容器的步骤.
创建密钥对:
private AsymmetricCipherKeyPair GenerateKeyPair()
{
var random = new SecureRandom();
var keyGenerationParameter = new KeyGenerationParameters(random, 4096);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameter);
var keyPair = keyPairGenerator.GenerateKeyPair();
return keyPair;
}
创建证书,该证书将调用以创建密钥并创建容器:
public void CreateCertificate()
{
var random = new SecureRandom();
var keyPair = this.GenerateKeyPair();
// generate certificate generator and set public key.
var generator = new X509V3CertificateGenerator();
generator.SetPublicKey(keyPair.Public);
// generate and set serial number
BigInteger serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), random);
generator.SetSerialNumber(serialNumber);
// set signing algorithm.
generator.SetSignatureAlgorithm(Core.Constants.SignatureAlgorithmName);
// set name
string fullQualifiedName = $"CN=dummy,O=DummyOrg,OU=Dummy";
var name = new X509Name(fullQualifiedName);
generator.SetSubjectDN(name);
generator.SetIssuerDN(name);
// set valide time
generator.SetNotBefore(DateTime.UtcNow.AddHours(-1));
generator.SetNotAfter(DateTime.UtcNow.AddYears(10));
// add extensions.
var authorityKeyIdentifier = new AuthorityKeyIdentifier(
SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public).GetEncoded(),
new GeneralNames(new GeneralName(name)),
serialNumber);
var subjectKeyIdentifier = new SubjectKeyIdentifier(
SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public).GetEncoded());
generator.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, authorityKeyIdentifier);
generator.AddExtension(X509Extensions.SubjectKeyIdentifier, false, subjectKeyIdentifier);
generator.AddExtension(X509Extensions.BasicConstraints.Id, true, new BasicConstraints(0));
// generate and validate certificate.
var cert = generator.Generate(keyPair.Private, random);
cert.CheckValidity(DateTime.UtcNow);
cert.Verify(keyPair.Public);
// generate pem string
using (var stringWriter = new StringWriter())
{
var writer = new PemWriter(stringWriter);
var pog = new PemObject("CERTIFICATE", cert.GetEncoded());
writer.WriteObject(pog);
// pem value
var value = stringWriter.ToString();
this.StoreCertificate(value);
}
// store the private key
using (var stringWriter = new StringWriter())
{
var writer = new PemWriter(stringWriter);
PrivateKeyInfo info = PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private);
var pog = new PemObject("RSA PRIVATE KEY", info.ToAsn1Object().GetEncoded());
writer.WriteObject(pog);
// pem value
var value = stringWriter.ToString();
this.StorePrivateKey(value);
}
try
{
this.CreatePfxFile();
}
catch (Exception ex)
{
throw;
}
}
创建PFX容器:
public void CreatePfxFile()
{
var certificate = this.GetCertificate();
var certEntry = new X509CertificateEntry(certificate);
string friendlyName = certificate.SubjectDN.ToString();
var privateKey = this.ReadPrivateKey();
PrivateKeyInfo info = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKey);
byte[] keyBytes = info.ToAsn1Object().GetEncoded();
var store = new Pkcs12StoreBuilder().Build();
var keyEntry = new AsymmetricKeyEntry(privateKey, );
////store.SetCertificateEntry(Core.Constants.CertificateAlias, certEntry);
store.SetKeyEntry(Core.Constants.PrivateKeyAlias, new AsymmetricKeyEntry(privateKey), new X509CertificateEntry[] { certEntry });
MemoryStream stream = new MemoryStream();
store.Save(stream, Core.Constants.Password.ToCharArray(), new SecureRandom());
var pfxBytes = stream.ToArray();
var base64 = Convert.ToBase64String(pfxBytes);
this.StorePfxContainer(base64);
}
方法GetCertificate()
和ReadPrivateKey()
将返回X509Certificate
和AsymmetricKeyParameter
.
方法StorePfxContainer
只会将base64字符串存储到设置变量中.
注意:我删除了store.SetCertificateEntry(Core.Constants.CertificateAlias, certEntry);
行,因为它导致pfx容器两次包含证书.
请不要,因为现在我们只使用BouncyCastle东西.
要签署我的请求,我使用以下代码.通过从设置中检索base64字符串来创建参数cert
,并使用Convert.FromBase64String(settingsValue)
获取字节.
private void SetCertificate(HttpWebRequest request, byte[] cert)
{
try
{
System.Security.Cryptography.X509Certificates.X509Certificate2 certificate =
new System.Security.Cryptography.X509Certificates.X509Certificate2(cert, Core.Constants.Password); // <-- throws the exception
request.ClientCertificates.Add(certificate);
}
catch (Exception ex)
{
string message = ex.Message;
}
}
现在,我们正在使用来自.Net的证书(System.Security.Cryptography.X509Certificates.X509Certificate2).我要加载证书时在x509Certifcate2
的构造函数上引发的异常是
Mono.Security抛出的Input data cannot be coded as a valide certificate
我用openssl测试了证书和密钥,我也尝试从我的pem证书和pem私钥生成pfx容器,也可以正常工作.只有当我将证书加载到我的代码中时,它才起作用.因此,我认为容器的创建存在一个错误,到目前为止我还没有弄清楚.
感谢您的帮助.
经过多次试验和错误后,我弄清楚了实现的错误所在.
首先,我认为我所经历的行为是PCL和Mono的Bouncy Castle的结合.
我生成pfx容器的新方法如下所示.
private void CreatePfxFile(X509Certificate certificate, AsymmetricKeyParameter privateKey)
{
// create certificate entry
var certEntry = new X509CertificateEntry(certificate);
string friendlyName = certificate.SubjectDN.ToString();
// get bytes of private key.
PrivateKeyInfo keyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKey);
byte[] keyBytes = keyInfo.ToAsn1Object().GetEncoded();
var builder = new Pkcs12StoreBuilder();
builder.SetUseDerEncoding(true);
var store = builder.Build();
// create store entry
store.SetKeyEntry(Core.Constants.PrivateKeyAlias, new AsymmetricKeyEntry(privateKey), new X509CertificateEntry[] { certEntry });
byte[] pfxBytes = null;
var password = Guid.NewGuid().ToString("N");
using (MemoryStream stream = new MemoryStream())
{
store.Save(stream, password.ToCharArray(), new SecureRandom());
pfxBytes = stream.ToArray();
}
var result = Pkcs12Utilities.ConvertToDefiniteLength(pfxBytes);
this.StoreCertificate(Convert.ToBase64String(result));
}
第一个更改是将MemoryStream
包装到using
中.我不太确定会发生什么变化,但是摆脱了内部异常.
第二个变化是使用Pkcs12StoreBuilder
并设置SetUseDerEcnoding(true)
并以此建立商店.
第三项变化是使用Pkcs12Urilities.ConvertToDefiniteLength()
方法为数据设置确定的长度.
这些更改之后,我的证书可以被存储和恢复而没有任何问题.
注意:
还要确保您没有使用string.Empty
作为密码来保存容器.我经常看到这是容器问题的答案.
I am struggling with the creation of a pfx file with Xamarin and BouncyCastle. I have the following settings/ specification
- .NET: PCL .Net Framework 4.5.1
- Xamarin: 4.5.0.476
- BouncyCastle: BouncyCastle-Signed 1.7.0.1 (NuGet Package)
I want to generate a Self-Signed Certificate for my mobile client to autheniticate itself against my server. The creation works pretty well using BouncyCastle. My problem is, when I want to store the certificate with its private key as a PKCS#12 (pfx) container and restore it from that container to send my web requests signed by it an exception is thrown telling me Input data cannot be coded as a valide certificate
.
Here are the steps how i create my certificate, keys and store the pfx container.
Create the key pair:
private AsymmetricCipherKeyPair GenerateKeyPair()
{
var random = new SecureRandom();
var keyGenerationParameter = new KeyGenerationParameters(random, 4096);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameter);
var keyPair = keyPairGenerator.GenerateKeyPair();
return keyPair;
}
Create the certificate, which will call to create the keys and create the container:
public void CreateCertificate()
{
var random = new SecureRandom();
var keyPair = this.GenerateKeyPair();
// generate certificate generator and set public key.
var generator = new X509V3CertificateGenerator();
generator.SetPublicKey(keyPair.Public);
// generate and set serial number
BigInteger serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), random);
generator.SetSerialNumber(serialNumber);
// set signing algorithm.
generator.SetSignatureAlgorithm(Core.Constants.SignatureAlgorithmName);
// set name
string fullQualifiedName = $"CN=dummy,O=DummyOrg,OU=Dummy";
var name = new X509Name(fullQualifiedName);
generator.SetSubjectDN(name);
generator.SetIssuerDN(name);
// set valide time
generator.SetNotBefore(DateTime.UtcNow.AddHours(-1));
generator.SetNotAfter(DateTime.UtcNow.AddYears(10));
// add extensions.
var authorityKeyIdentifier = new AuthorityKeyIdentifier(
SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public).GetEncoded(),
new GeneralNames(new GeneralName(name)),
serialNumber);
var subjectKeyIdentifier = new SubjectKeyIdentifier(
SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public).GetEncoded());
generator.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, authorityKeyIdentifier);
generator.AddExtension(X509Extensions.SubjectKeyIdentifier, false, subjectKeyIdentifier);
generator.AddExtension(X509Extensions.BasicConstraints.Id, true, new BasicConstraints(0));
// generate and validate certificate.
var cert = generator.Generate(keyPair.Private, random);
cert.CheckValidity(DateTime.UtcNow);
cert.Verify(keyPair.Public);
// generate pem string
using (var stringWriter = new StringWriter())
{
var writer = new PemWriter(stringWriter);
var pog = new PemObject("CERTIFICATE", cert.GetEncoded());
writer.WriteObject(pog);
// pem value
var value = stringWriter.ToString();
this.StoreCertificate(value);
}
// store the private key
using (var stringWriter = new StringWriter())
{
var writer = new PemWriter(stringWriter);
PrivateKeyInfo info = PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private);
var pog = new PemObject("RSA PRIVATE KEY", info.ToAsn1Object().GetEncoded());
writer.WriteObject(pog);
// pem value
var value = stringWriter.ToString();
this.StorePrivateKey(value);
}
try
{
this.CreatePfxFile();
}
catch (Exception ex)
{
throw;
}
}
Create the PFX container:
public void CreatePfxFile()
{
var certificate = this.GetCertificate();
var certEntry = new X509CertificateEntry(certificate);
string friendlyName = certificate.SubjectDN.ToString();
var privateKey = this.ReadPrivateKey();
PrivateKeyInfo info = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKey);
byte[] keyBytes = info.ToAsn1Object().GetEncoded();
var store = new Pkcs12StoreBuilder().Build();
var keyEntry = new AsymmetricKeyEntry(privateKey, );
////store.SetCertificateEntry(Core.Constants.CertificateAlias, certEntry);
store.SetKeyEntry(Core.Constants.PrivateKeyAlias, new AsymmetricKeyEntry(privateKey), new X509CertificateEntry[] { certEntry });
MemoryStream stream = new MemoryStream();
store.Save(stream, Core.Constants.Password.ToCharArray(), new SecureRandom());
var pfxBytes = stream.ToArray();
var base64 = Convert.ToBase64String(pfxBytes);
this.StorePfxContainer(base64);
}
The methods GetCertificate()
and ReadPrivateKey()
will return an X509Certificate
and an AsymmetricKeyParameter
.
The method StorePfxContainer
will just store the base64 string into a settings variable.
Note: I removed the line store.SetCertificateEntry(Core.Constants.CertificateAlias, certEntry);
because it caused the pfx container to contain the certificate twice.
Please not, since now we are just using BouncyCastle stuff.
To sign my request i use the following code. The parameter cert
will be created by retrieving the base64 string from the settings and use Convert.FromBase64String(settingsValue)
to get the bytes.
private void SetCertificate(HttpWebRequest request, byte[] cert)
{
try
{
System.Security.Cryptography.X509Certificates.X509Certificate2 certificate =
new System.Security.Cryptography.X509Certificates.X509Certificate2(cert, Core.Constants.Password); // <-- throws the exception
request.ClientCertificates.Add(certificate);
}
catch (Exception ex)
{
string message = ex.Message;
}
}
Now we are using the Certificate from .Net (System.Security.Cryptography.X509Certificates.X509Certificate2). The exception which is thrown at the constructor of the x509Certifcate2
when I want to load the certificate is
Input data cannot be coded as a valide certificate
thrown by Mono.Security
I tested the certificate and key with openssl, also i tried to generate a pfx container from my pem Certificate and pem private key, also works fine. Only when i load the certificate inside my code it will not work. So i think that the creation of the container has a bug which I haven't figured out by now.
Thanks for your help.
After some more trial and error I figured out what was the error of my implementation.
First of all, I think that the behavior I experienced was a combination of a Bouncy Castle for PCL and Mono.
My new method to generate the pfx container looks like the following.
private void CreatePfxFile(X509Certificate certificate, AsymmetricKeyParameter privateKey)
{
// create certificate entry
var certEntry = new X509CertificateEntry(certificate);
string friendlyName = certificate.SubjectDN.ToString();
// get bytes of private key.
PrivateKeyInfo keyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKey);
byte[] keyBytes = keyInfo.ToAsn1Object().GetEncoded();
var builder = new Pkcs12StoreBuilder();
builder.SetUseDerEncoding(true);
var store = builder.Build();
// create store entry
store.SetKeyEntry(Core.Constants.PrivateKeyAlias, new AsymmetricKeyEntry(privateKey), new X509CertificateEntry[] { certEntry });
byte[] pfxBytes = null;
var password = Guid.NewGuid().ToString("N");
using (MemoryStream stream = new MemoryStream())
{
store.Save(stream, password.ToCharArray(), new SecureRandom());
pfxBytes = stream.ToArray();
}
var result = Pkcs12Utilities.ConvertToDefiniteLength(pfxBytes);
this.StoreCertificate(Convert.ToBase64String(result));
}
First change was to wrap the MemoryStream
into a using
. I am not so sure what this would change but it got rid of an inner exception.
Second change was to use the Pkcs12StoreBuilder
and set the SetUseDerEcnoding(true)
and build a store from this.
Third change was to use the Pkcs12Urilities.ConvertToDefiniteLength()
method to set a definite length to the data.
After these changes my certificate could be stored and restored without any problems.
Note:
Also make sure that you are not using string.Empty
as password to save the container. I have seen this often to be the answer to problems with the container.
这篇关于使用Bouncycastle存储PKCS#12容器(pfx)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!