web-dev-qa-db-fra.com

Est-il possible de générer par programme un certificat X509 en utilisant uniquement C #?

Nous essayons de générer un certificat X509 (y compris la clé privée) par programmation en utilisant C # et la bibliothèque BouncyCastle . Nous avons essayé d'utiliser une partie du code de cet exemple de Felix Kollmann mais la partie clé privée du certificat renvoie null. Le code et le test unitaire sont les suivants:

using System;
using System.Collections;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;

namespace MyApp
{
    public class CertificateGenerator
    {
        /// <summary>
        /// 
        /// </summary>
        /// <remarks>Based on <see cref="http://www.fkollmann.de/v2/post/Creating-certificates-using-BouncyCastle.aspx"/></remarks>
        /// <param name="subjectName"></param>
        /// <returns></returns>
        public static byte[] GenerateCertificate(string subjectName)
        {
            var kpgen = new RsaKeyPairGenerator();

            kpgen.Init(new KeyGenerationParameters(new SecureRandom(new CryptoApiRandomGenerator()), 1024));

            var kp = kpgen.GenerateKeyPair();

            var gen = new X509V3CertificateGenerator();

            var certName = new X509Name("CN=" + subjectName);
            var serialNo = BigInteger.ProbablePrime(120, new Random());

            gen.SetSerialNumber(serialNo);
            gen.SetSubjectDN(certName);
            gen.SetIssuerDN(certName);
            gen.SetNotAfter(DateTime.Now.AddYears(100));
            gen.SetNotBefore(DateTime.Now.Subtract(new TimeSpan(7, 0, 0, 0)));
            gen.SetSignatureAlgorithm("MD5WithRSA");
            gen.SetPublicKey(kp.Public);

            gen.AddExtension(
                X509Extensions.AuthorityKeyIdentifier.Id,
                false,
                new AuthorityKeyIdentifier(
                    SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(kp.Public),
                    new GeneralNames(new GeneralName(certName)),
                    serialNo));

            gen.AddExtension(
                X509Extensions.ExtendedKeyUsage.Id,
                false,
                new ExtendedKeyUsage(new ArrayList() { new DerObjectIdentifier("1.3.6.1.5.5.7.3.1") }));

            var newCert = gen.Generate(kp.Private);
            return DotNetUtilities.ToX509Certificate(newCert).Export(System.Security.Cryptography.X509Certificates.X509ContentType.Pkcs12, "password");
        }
    }
}

Test de l'unité:

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace MyApp
{
    [TestClass]
    public class CertificateGeneratorTests
    {
        [TestMethod]
        public void GenerateCertificate_Test_ValidCertificate()
        {
            // Arrange
            string subjectName = "test";

            // Act
            byte[] actual = CertificateGenerator.GenerateCertificate(subjectName);

            // Assert
            var cert = new X509Certificate2(actual, "password");
            Assert.AreEqual("CN=" + subjectName, cert.Subject);
            Assert.IsInstanceOfType(cert.PrivateKey, typeof(RSACryptoServiceProvider));
        }
    }
}
40
Tom Robinson

Juste pour clarifier, un certificat X.509 ne contient pas la clé privée. Le certificat Word est parfois utilisé à mauvais escient pour représenter la combinaison du certificat et de la clé privée, mais ce sont deux entités distinctes. L'intérêt de l'utilisation de certificats est de les envoyer plus ou moins ouvertement, sans envoyer la clé privée, qui doit être tenue secrète. Un objet X509Certificate2 Peut être associé à une clé privée (via sa propriété PrivateKey), mais ce n'est qu'une commodité dans le cadre de la conception de cette classe.

Dans votre premier exemple de code BouncyCastle, newCert est vraiment juste le certificat et DotNetUtilities.ToX509Certificate(newCert) est construit uniquement à partir du certificat.

Étant donné que le format PKCS # 12 nécessite la présence d'une clé privée, je suis assez surpris que la partie suivante fonctionne même (étant donné que vous l'appelez sur un certificat qui ne peut pas connaître la clé privée):

.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Pkcs12,
    "password");

(gen.Generate(kp.Private) signe le certificat à l'aide de la clé privée, mais ne place pas la clé privée dans le certificat, ce qui n'aurait aucun sens.)

Si vous souhaitez que votre méthode renvoie à la fois le certificat et la clé privée, vous pouvez:

  • Renvoie un objet X509Certificate2 Dans lequel vous avez initialisé la propriété PrivateKey
  • Créez un magasin PKCS # 12 et retournez son contenu byte[] (Comme s'il s'agissait d'un fichier). Étape 3 du lien que vous avez envoyé ( miroir ) explique comment créer un magasin PKCS # 12.

Le renvoi de la structure byte[] (DER) pour le certificat X.509 lui-même ne contiendra pas la clé privée.

Si votre principale préoccupation (selon votre cas de test) est de vérifier que le certificat a été construit à partir d'une paire de clés RSA, vous pouvez plutôt vérifier le type de sa clé publique.

31
Bruno

Je me rends compte que c'est un ancien article mais j'ai trouvé ces excellents articles qui passent par le processus:

en utilisant Bouncy Castle de .NET

7
haymansfield