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

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

问题描述

我正在努力使用 Xamarin 和 BouncyCastle 创建 pfx 文件.我有以下设置/规范

I am struggling with the creation of a pfx file with Xamarin and BouncyCastle. I have the following settings/ specification

  • .NET:PCL .Net 框架 4.5.1
  • Xamarin:4.5.0.476
  • BouncyCastle:BouncyCastle 签名 1.7.0.1(NuGet 包)

我想为我的移动客户端生成一个自签名证书,以针对我的服务器进行身份验证.使用 BouncyCastle 创建效果非常好.我的问题是,当我想将证书及其私钥存储为 PKCS#12 (pfx) 容器并从该容器恢复它以发送由它签名的 Web 请求时,抛出异常告诉我 Input data cannot编码为有效证书.

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.

以下是我创建证书、密钥和存储 pfx 容器的步骤.

Here are the steps how i create my certificate, keys and store the pfx container.

创建密钥对:

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;
    }
}

创建 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 字符串存储到设置变量中.

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.

注意:我删除了行 store.SetCertificateEntry(Core.Constants.CertificateAlias, certEntry); 因为它导致 pfx 容器两次包含证书.

Note: I removed the line store.SetCertificateEntry(Core.Constants.CertificateAlias, certEntry); because it caused the pfx container to contain the certificate twice.

请不要,因为现在我们只使用 BouncyCastle 的东西.

Please not, since now we are just using BouncyCastle stuff.

要签署我的请求,我使用以下代码.参数 cert 将通过从设置中检索 base64 字符串来创建,并使用 Convert.FromBase64String(settingsValue) 获取字节.

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;
    }
}

现在我们使用来自 .Net 的证书 (System.Security.Cryptography.X509Certificates.X509Certificate2).当我想加载证书时,在 x509Certifcate2 的构造函数中抛出的异常是输入数据无法编码为有效证书 Mono.Security 抛出的

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

我用 openssl 测试了证书和密钥,我也尝试从我的 pem 证书和 pem 私钥生成一个 pfx 容器,也工作正常.只有当我在代码中加载证书时,它才会起作用.所以我认为容器的创建有一个我现在还没有弄清楚的错误.

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.

感谢您的帮助.

推荐答案

经过更多的反复试验,我弄清楚了我的实现的错误是什么.

After some more trial and error I figured out what was the error of my implementation.

首先,我认为我所经历的行为是 PCL 和 Mono 的充气城堡的组合.

First of all, I think that the behavior I experienced was a combination of a Bouncy Castle for PCL and Mono.

我生成 pfx 容器的新方法如下所示.

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));
}

第一个更改是将 MemoryStream 包装到 using 中.我不太确定这会改变什么,但它摆脱了内部异常.

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.

第二个更改是使用 Pkcs12StoreBuilder 并设置 SetUseDerEcnoding(true) 并以此构建商店.

Second change was to use the Pkcs12StoreBuilder and set the SetUseDerEcnoding(true) and build a store from this.

第三个变化是使用 Pkcs12Urilities.ConvertToDefiniteLength() 方法为数据设置一个确定的长度.

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.

注意:还要确保您没有使用 string.Empty 作为密码来保存容器.我经常看到这是容器问题的答案.

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天全站免登陆