91 votes

Créer par programme un certificat X509 avec OpenSSL

J'ai une application C/C++ et je dois créer un certificat X509 pem contenant une clé publique et une clé privée. Le certificat peut être auto-signé ou non signé, peu importe.

Je veux faire cela à l'intérieur d'une application, pas à partir de la ligne de commande.

Quelles sont les fonctions OpenSSL qui peuvent le faire pour moi ? Tout exemple de code est un bonus !

230voto

Nathan Osman Points 13475

Je me rends compte que cette réponse est très tardive (et longue). Mais compte tenu de l'importance de cette question dans les résultats des moteurs de recherche, je me suis dit qu'elle méritait une réponse décente.

Une grande partie de ce que vous lirez ci-dessous est empruntée à cette démonstration et la documentation d'OpenSSL. Le code ci-dessous s'applique à la fois au C et au C++.


Avant de pouvoir créer un certificat, nous devons créer une clé privée. OpenSSL fournit la clé EVP_PKEY structure permettant de stocker en mémoire une clé privée indépendante de l'algorithme. Cette structure est déclarée dans openssl/evp.h mais elle est incluse par openssl/x509.h (dont nous aurons besoin plus tard), il n'est donc pas nécessaire d'inclure explicitement l'en-tête.

Afin d'attribuer un EVP_PKEY nous utilisons EVP_PKEY_new :

EVP_PKEY * pkey;
pkey = EVP_PKEY_new();

Il existe également une fonction correspondante pour libérer la structure - EVP_PKEY_free - qui accepte un seul argument : le EVP_PKEY initialisée ci-dessus.

Nous devons maintenant générer une clé. Pour notre exemple, nous allons générer une clé RSA. Cela se fait à l'aide de la commande RSA_generate_key qui est déclarée dans openssl/rsa.h . Cette fonction renvoie un pointeur sur un RSA structure.

Une invocation simple de la fonction peut ressembler à ceci :

RSA * rsa;
rsa = RSA_generate_key(
    2048,   /* number of bits for the key - 2048 is a sensible value */
    RSA_F4, /* exponent - RSA_F4 is defined as 0x10001L */
    NULL,   /* callback - can be NULL if we aren't displaying progress */
    NULL    /* callback argument - not needed in this case */
);

Si la valeur de retour de RSA_generate_key es NULL mais quelque chose n'a pas fonctionné. Si ce n'est pas le cas, nous avons maintenant une clé RSA, et nous pouvons l'assigner à notre EVP_PKEY de la structure précédente :

EVP_PKEY_assign_RSA(pkey, rsa);

En RSA sera automatiquement libérée lorsque la structure EVP_PKEY est libérée.


Passons maintenant au certificat proprement dit.

OpenSSL utilise l'option X509 pour représenter un certificat x509 en mémoire. La définition de cette structure se trouve dans openssl/x509.h . La première fonction dont nous aurons besoin est X509_new . Son utilisation est relativement simple :

X509 * x509;
x509 = X509_new();

Comme ce fut le cas pour EVP_PKEY il existe une fonction correspondante pour libérer la structure - X509_free .

Nous devons maintenant définir quelques propriétés du certificat à l'aide d'un certain nombre d'éléments X509_* fonctions :

ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);

Le numéro de série de notre certificat est ainsi fixé à "1". Certains serveurs HTTP à code source ouvert refusent d'accepter un certificat dont le numéro de série est "0", ce qui est la valeur par défaut. L'étape suivante consiste à spécifier la durée de validité du certificat. Pour ce faire, nous faisons appel aux deux fonctions suivantes :

X509_gmtime_adj(X509_get_notBefore(x509), 0);
X509_gmtime_adj(X509_get_notAfter(x509), 31536000L);

La première ligne définit la valeur notBefore à l'heure actuelle. (Le X509_gmtime_adj ajoute le nombre de secondes spécifié à l'heure actuelle - dans ce cas, aucune). La deuxième ligne définit la valeur notAfter à 365 jours à partir de maintenant (60 secondes * 60 minutes * 24 heures * 365 jours).

Nous devons maintenant définir la clé publique de notre certificat en utilisant la clé que nous avons générée précédemment :

X509_set_pubkey(x509, pkey);

Comme il s'agit d'un certificat auto-signé, le nom de l'émetteur correspond au nom du sujet. La première étape de ce processus consiste à obtenir le nom du sujet :

X509_NAME * name;
name = X509_get_subject_name(x509);

Si vous avez déjà créé un certificat auto-signé en ligne de commande, vous vous souvenez probablement qu'on vous a demandé un code de pays. C'est ici que nous le fournissons, ainsi que l'organisation ("O") et le nom commun ("CN") :

X509_NAME_add_entry_by_txt(name, "C",  MBSTRING_ASC,
                           (unsigned char *)"CA", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "O",  MBSTRING_ASC,
                           (unsigned char *)"MyCompany Inc.", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
                           (unsigned char *)"localhost", -1, -1, 0);

(J'utilise ici la valeur "CA" parce que je suis Canadien et que c'est le code de notre pays. Notez également que le paramètre #4 doit être explicitement transformé en un objet de type unsigned char * .)

Nous pouvons maintenant définir le nom de l'émetteur :

X509_set_issuer_name(x509, name);

Enfin, nous sommes prêts à procéder à la signature. Nous appelons X509_sign avec la clé que nous avons générée précédemment. Le code pour cela est d'une simplicité déconcertante :

X509_sign(x509, pkey, EVP_sha1());

Notez que nous utilisons l'option SHA-1 algorithme de hachage pour signer la clé. Cela diffère de l'algorithme mkcert.c que j'ai mentionnée au début de cette réponse, qui utilise MD5.


Nous avons maintenant un certificat auto-signé ! Mais nous n'avons pas encore terminé - nous devons écrire ces fichiers sur le disque. Heureusement, OpenSSL nous couvre là aussi avec la fonction PEM_* qui sont déclarées dans openssl/pem.h . Le premier dont nous aurons besoin est PEM_write_PrivateKey pour enregistrer notre clé privée.

FILE * f;
f = fopen("key.pem", "wb");
PEM_write_PrivateKey(
    f,                  /* write the key to the file we've opened */
    pkey,               /* our key from earlier */
    EVP_des_ede3_cbc(), /* default cipher for encrypting the key on disk */
    "replace_me",       /* passphrase required for decrypting the key on disk */
    10,                 /* length of the passphrase string */
    NULL,               /* callback for requesting a password */
    NULL                /* data to pass to the callback */
);

Si vous ne souhaitez pas crypter la clé privée, passez simplement NULL pour les troisième et quatrième paramètres ci-dessus. Dans tous les cas, vous devrez vous assurer que le fichier n'est pas lisible par le monde entier. (Pour les utilisateurs d'Unix, cela signifie chmod 600 key.pem .)

La vie en noir et blanc Il ne nous reste plus qu'une seule fonction : écrire le certificat sur le disque. La fonction dont nous avons besoin pour cela est PEM_write_X509 :

FILE * f;
f = fopen("cert.pem", "wb");
PEM_write_X509(
    f,   /* write the certificate to the file we've opened */
    x509 /* our certificate */
);

Et c'est fini ! Nous espérons que les informations contenues dans cette réponse sont suffisantes pour vous donner une idée approximative de la façon dont tout fonctionne, bien que nous ayons à peine effleuré la surface d'OpenSSL.

Pour ceux qui souhaitent voir à quoi ressemble le code ci-dessus dans une application réelle, j'ai créé une Gist (écrite en C++) que vous pouvez consulter. aquí .

53voto

Martin v. Löwis Points 61768

Vous devez d'abord vous familiariser avec la terminologie et les mécanismes.

Un certificat X.509 certificat par définition, n'inclut pas de clé privée. Il s'agit plutôt d'une version de la clé publique signée par l'autorité de certification (avec tous les attributs que l'autorité de certification ajoute à la signature). Le format PEM ne permet réellement que le stockage séparé de la clé et du certificat, bien qu'il soit possible de concaténer les deux.

Dans tous les cas, vous devrez invoquer plus de 20 fonctions différentes de l'API OpenSSL pour créer une clé et un certificat auto-signé. Un exemple se trouve dans le code source d'OpenSSL lui-même, à l'adresse suivante demos/x509/mkcert.c

Pour une réponse plus détaillée, voir L'explication de Nathan Osman ci-dessous.

7voto

MasterAler Points 31

Nathan Osman l'a expliqué en long et en large, j'ai eu le même problème à résoudre en C++, alors voici mon petit ajout, un concept réécrit à la manière de cpp avec quelques mises en garde prises en compte :

bool generateX509(const std::string& certFileName, const std::string& keyFileName, long daysValid)
{
    bool result = false;

    std::unique_ptr<BIO, void (*)(BIO *)> certFile  { BIO_new_file(certFileName.data(), "wb"), BIO_free_all  };
    std::unique_ptr<BIO, void (*)(BIO *)> keyFile { BIO_new_file(keyFileName.data(), "wb"), BIO_free_all };

    if (certFile && keyFile)
    {
        std::unique_ptr<RSA, void (*)(RSA *)> rsa { RSA_new(), RSA_free };
        std::unique_ptr<BIGNUM, void (*)(BIGNUM *)> bn { BN_new(), BN_free };

        BN_set_word(bn.get(), RSA_F4);
        int rsa_ok = RSA_generate_key_ex(rsa.get(), RSA_KEY_LENGTH, bn.get(), nullptr);

        if (rsa_ok == 1)
        {
            // --- cert generation ---
            std::unique_ptr<X509, void (*)(X509 *)> cert { X509_new(), X509_free };
            std::unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)> pkey { EVP_PKEY_new(), EVP_PKEY_free};

            // The RSA structure will be automatically freed when the EVP_PKEY structure is freed.
            EVP_PKEY_assign(pkey.get(), EVP_PKEY_RSA, reinterpret_cast<char*>(rsa.release()));
            ASN1_INTEGER_set(X509_get_serialNumber(cert.get()), 1); // serial number

            X509_gmtime_adj(X509_get_notBefore(cert.get()), 0); // now
            X509_gmtime_adj(X509_get_notAfter(cert.get()), daysValid * 24 * 3600); // accepts secs

            X509_set_pubkey(cert.get(), pkey.get());

            // 1 -- X509_NAME may disambig with wincrypt.h
            // 2 -- DO NO FREE the name internal pointer
            X509_name_st* name = X509_get_subject_name(cert.get());

            const uchar country[] = "RU";
            const uchar company[] = "MyCompany, PLC";
            const uchar common_name[] = "localhost";

            X509_NAME_add_entry_by_txt(name, "C",  MBSTRING_ASC, country, -1, -1, 0);
            X509_NAME_add_entry_by_txt(name, "O",  MBSTRING_ASC, company, -1, -1, 0);
            X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, common_name, -1, -1, 0);

            X509_set_issuer_name(cert.get(), name);
            X509_sign(cert.get(), pkey.get(), EVP_sha256()); // some hash type here

            int ret  = PEM_write_bio_PrivateKey(keyFile.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr);
            int ret2 = PEM_write_bio_X509(certFile.get(), cert.get());

            result = (ret == 1) && (ret2 == 1); // OpenSSL return codes
        }
    }

    return result;
}

Bien entendu, il devrait y avoir plus les contrôles des valeurs de retour des fonctions, en fait todos Certains d'entre eux devraient être vérifiés, mais cela rendrait l'échantillon trop "ramifié" et il est de toute façon assez facile de l'améliorer.

2voto

Adam Liss Points 27815

Est-il possible de le faire par l'intermédiaire d'un system appel à partir de votre application ? Il y a plusieurs bonnes raisons de le faire :

  • Octroi de licences : Appeler le openssl le sépare sans doute de votre application et peut offrir certains avantages. Clause de non-responsabilité : consultez un avocat à ce sujet.

  • Documentation : OpenSSL est livré avec phénoménal une documentation en ligne de commande qui simplifie grandement un outil potentiellement compliqué.

  • Testabilité : vous pouvez utiliser OpenSSL à partir de la ligne de commande jusqu'à ce que vous compreniez exactement comment créer vos certificats. Il existe un lot Il faut s'attendre à passer environ une journée sur cette question jusqu'à ce que tous les détails soient bien réglés. Après cela, il est facile d'incorporer la commande dans votre application.

Si vous choisissez d'utiliser l'API, vérifiez l'option openssl-dev liste des développeurs sur www.openssl.org.

Bonne chance !

1voto

Jaime Hablutzel Points 1442

Tutoriel très simple pour créer des certificats numériques http://publib.boulder.ibm.com/infocenter/rsthelp/v8r0m0/index.jsp?topic=/com.ibm.rational.test.lt.doc/topics/tcreatecertopenssl.html

En ce qui concerne l'exécution de ces commandes à partir de votre code, je ne suis pas sûr.

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