40 votes

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) de manière programmatique en utilisant C# et l'outil de gestion des certificats X509. Château gonflable bibliothèque. Nous avons essayé d'utiliser une partie du code de cet échantillon par Felix Kollmann mais la partie clé privée du certificat renvoie un résultat nul. 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 unitaire :

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

32voto

Bruno Points 47560

Juste pour clarifier, un certificat X.509 ne contient pas la clé privée. Le mot certificat est parfois utilisé à tort pour représenter la combinaison du certificat et de la clé privée, mais il s'agit de deux entités distinctes. L'intérêt d'utiliser des certificats est de pouvoir les envoyer plus ou moins ouvertement, sans envoyer la clé privée, qui doit rester secrète. Un X509Certificate2 peut avoir une clé privée associée à lui (par l'intermédiaire de son PrivateKey ), mais c'est seulement une commodité qui fait partie de la conception de cette classe.

Dans votre premier exemple de code BouncyCastle, newCert n'est en fait que le certificat et DotNetUtilities.ToX509Certificate(newCert) est construit à partir du certificat uniquement.

Considérant que le format PKCS#12 requiert la présence d'une clé privée, je suis assez surpris que la partie suivante fonctionne (considérant 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 en utilisant la clé privée, mais ne met pas la clé privée dans le certificat, ce qui n'aurait pas de sens).

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

  • Retourner un X509Certificate2 dans lequel vous avez initialisé l'objet PrivateKey propriété
  • Construire un magasin PKCS#12 et renvoyer son byte[] le contenu (comme s'il s'agissait d'un fichier). Étape 3 dans le lien que vous avez envoyé ( miroir ) explique comment construire un magasin PKCS#12.

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

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

7voto

haymansfield Points 827

Je sais que c'est un vieux post mais j'ai trouvé ces excellents articles qui expliquent le processus :

https://blog.differentpla.net/post/53

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X