Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use BouncyCastle to generate a Root Certificate and then a site certificate that is signed by that Root certificate?

I'm trying to build a self hosted service that uses WebAPI with SSL and I need to be able to self-generate SSL certificates to use. I want to be able to do this all from C#. I've been playing with BouncyCastle.

I need to generate 2 certificates, a root and a site certificate. Then I need to install them in Windows in their correct places.

I can't figure out how to make my second certificate reference my root ca. Everything I've tried just gets me an untrusted certificate error. Any help would be appreciated.

like image 855
user1712714 Avatar asked Oct 01 '12 19:10

user1712714


1 Answers

This is what I do (I'm using DSA, but if you are using RSA, just change the key generation).

public void IssueClientFromCA()
{
    // get CA
    string caCn = "MyCA CommonName";
    Stream caCertFile = File.OpenRead(string.Format(@"{0}\{1}", _certificatesDir, "MyCAFile.pfx"));
    char[] caPass = "passwordForThePfx".ToCharArray();

    Pkcs12Store store = new Pkcs12StoreBuilder().Build();
    store.Load(caCertFile, caPass);            
    var caCert = store.GetCertificate(caCn).Certificate;
    var caPrivKey = store.GetKey(caCn).Key;

    var clientCert = CertIssuer.GenerateDsaCertificateAsPkcs12(
        "My Client FriendlyName",
        "My Client SubjectName", 
        "GT",
        new DateTime(2011,9,19), 
        new DateTime(2014,9,18),
        "PFXPASS",
        caCert,
        caPrivKey);

    var saveAS = string.Format(@"{0}\{1}", _certificatesDir, "clientCertFile.pfx");
    File.WriteAllBytes(saveAS, clientCert);
}

public static byte[] GenerateDsaCertificateAsPkcs12(
    string friendlyName,
    string subjectName,
    string country,
    DateTime validStartDate,
    DateTime validEndDate,
    string password,
    Org.BouncyCastle.X509.X509Certificate caCert,
    AsymmetricKeyParameter caPrivateKey)
{
    var keys = GenerateDsaKeys();

    #region build certificate
    var certGen = new X509V3CertificateGenerator();

    // build name attributes
    var nameOids = new ArrayList();
    nameOids.Add(Org.BouncyCastle.Asn1.X509.X509Name.CN);
    nameOids.Add(X509Name.O);
    nameOids.Add(X509Name.C);

    var nameValues = new ArrayList();
    nameValues.Add(friendlyName);
    nameValues.Add(subjectName);
    nameValues.Add(country);
    var subjectDN = new X509Name(nameOids, nameValues);

    // certificate fields
    certGen.SetSerialNumber(BigInteger.ValueOf(1));
    certGen.SetIssuerDN(caCert.SubjectDN);
    certGen.SetNotBefore(validStartDate);
    certGen.SetNotAfter(validEndDate);
    certGen.SetSubjectDN(subjectDN);
    certGen.SetPublicKey(keys.Public);
    certGen.SetSignatureAlgorithm("SHA1withDSA");

    // extended information
    certGen.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(caCert.GetPublicKey()));
    certGen.AddExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(keys.Public));
    #endregion

    // generate x509 certificate
    var cert = certGen.Generate(caPrivateKey);
    //ert.Verify(caCert.GetPublicKey());

    var chain = new Dictionary<string, Org.BouncyCastle.X509.X509Certificate>();
    //chain.Add("CertiFirmas CA", caCert);
    var caCn = caCert.SubjectDN.GetValues(X509Name.CN)[0].ToString();
    chain.Add(caCn, caCert);

    // store the file
    return GeneratePkcs12(keys, cert, friendlyName, password, chain);
}

private static byte[] GeneratePkcs12(AsymmetricCipherKeyPair keys, Org.BouncyCastle.X509.X509Certificate cert, string friendlyName, string password,
    Dictionary<string, Org.BouncyCastle.X509.X509Certificate> chain)
{
    var chainCerts = new List<X509CertificateEntry>();

    // Create the PKCS12 store
    Pkcs12Store store = new Pkcs12StoreBuilder().Build();

    // Add a Certificate entry
    X509CertificateEntry certEntry = new X509CertificateEntry(cert);
    store.SetCertificateEntry(friendlyName, certEntry); // use DN as the Alias.
    //chainCerts.Add(certEntry);

    // Add chain entries
    var additionalCertsAsBytes = new List<byte[]>();
    if (chain != null && chain.Count > 0)
    {
        foreach (var additionalCert in chain)
        {
            additionalCertsAsBytes.Add(additionalCert.Value.GetEncoded());
        }
    }

    if (chain != null && chain.Count > 0)
    {
        var addicionalCertsAsX09Chain = BuildCertificateChainBC(cert.GetEncoded(), additionalCertsAsBytes);

        foreach (var addCertAsX09 in addicionalCertsAsX09Chain)
        {
            chainCerts.Add(new X509CertificateEntry(addCertAsX09));
        }
    }

    // Add a key entry
    AsymmetricKeyEntry keyEntry = new AsymmetricKeyEntry(keys.Private);

    // no chain
    store.SetKeyEntry(friendlyName, keyEntry, new X509CertificateEntry[] { certEntry });

    using (var memoryStream = new MemoryStream())
    {
        store.Save(memoryStream, password.ToCharArray(), new SecureRandom());
        return memoryStream.ToArray();
    }
}

Some missing methods:

static IEnumerable<Org.BouncyCastle.X509.X509Certificate> BuildCertificateChainBC(byte[] primary, IEnumerable<byte[]> additional)
{
    X509CertificateParser parser = new X509CertificateParser();
    PkixCertPathBuilder builder = new PkixCertPathBuilder();

    // Separate root from itermediate
    var intermediateCerts = new List<Org.BouncyCastle.X509.X509Certificate>();
    HashSet rootCerts = new HashSet();

    foreach (byte[] cert in additional)
    {
        var x509Cert = parser.ReadCertificate(cert);

        // Separate root and subordinate certificates
        if (x509Cert.IssuerDN.Equivalent(x509Cert.SubjectDN))
            rootCerts.Add(new TrustAnchor(x509Cert, null));
        else
            intermediateCerts.Add(x509Cert);
    }

    // Create chain for this certificate
    X509CertStoreSelector holder = new X509CertStoreSelector();
    holder.Certificate = parser.ReadCertificate(primary);

    // WITHOUT THIS LINE BUILDER CANNOT BEGIN BUILDING THE CHAIN
    intermediateCerts.Add(holder.Certificate);

    PkixBuilderParameters builderParams = new PkixBuilderParameters(rootCerts, holder);
    builderParams.IsRevocationEnabled = false;

    X509CollectionStoreParameters intermediateStoreParameters =
        new X509CollectionStoreParameters(intermediateCerts);

    builderParams.AddStore(X509StoreFactory.Create(
        "Certificate/Collection", intermediateStoreParameters));

    PkixCertPathBuilderResult result = builder.Build(builderParams);

    return result.CertPath.Certificates.Cast<Org.BouncyCastle.X509.X509Certificate>();
}

private static AsymmetricCipherKeyPair GenerateDsaKeys()
{
    DSACryptoServiceProvider DSA = new DSACryptoServiceProvider();
    var dsaParams = DSA.ExportParameters(true);
    AsymmetricCipherKeyPair keys = DotNetUtilities.GetDsaKeyPair(dsaParams);
    return keys;
}

Also: you have to install you CA certificate into the Trusted CAs store in the client machine, as well as the client certificate (it could be in the Personal or ThirdParty store).

like image 172
David Elizondo Avatar answered Oct 17 '22 02:10

David Elizondo