使用Bouncycastle存储PKCS#12容器(pfx) [英] Store PKCS#12 Container (pfx) with Bouncycastle

查看:105
本文介绍了使用Bouncycastle存储PKCS#12容器(pfx)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在努力使用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()将返回X509CertificateAsymmetricKeyParameter. 方法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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆