Sie müssen sich zuerst mit der Terminologie und den Mechanismen vertraut machen.
Ein X.509- Zertifikat enthält per Definition keinen privaten Schlüssel. Stattdessen handelt es sich um eine von der Zertifizierungsstelle signierte Version des öffentlichen Schlüssels (zusammen mit allen Attributen, die die Zertifizierungsstelle in die Signatur einfügt). Das PEM-Format unterstützt wirklich nur die getrennte Speicherung des Schlüssels und des Zertifikats - obwohl Sie die beiden dann verketten können.
In jedem Fall müssen Sie mehr als 20 verschiedene Funktionen der OpenSSL-API aufrufen, um einen Schlüssel und ein selbstsigniertes Zertifikat zu erstellen. Ein Beispiel befindet sich in der OpenSSL-Quelle selbst in demos / x509 / mkcert.c
Eine ausführlichere Antwort finden Sie in der folgenden Erklärung von Nathan Osman .
Mir ist klar, dass dies eine sehr späte (und lange) Antwort ist. Aber wenn man bedenkt, wie gut diese Frage in den Suchmaschinenergebnissen zu rangieren scheint, dachte ich, dass es sich lohnen könnte, eine anständige Antwort zu schreiben.
Vieles, was Sie unten lesen werden, stammt aus dieser Demo und den OpenSSL-Dokumenten. Der folgende Code gilt sowohl für C als auch für C ++.
Bevor wir tatsächlich ein Zertifikat erstellen können, müssen wir einen privaten Schlüssel erstellen. OpenSSL bietet die
EVP_PKEY
Struktur zum Speichern eines algorithmisch unabhängigen privaten Schlüssels im Speicher. Diese Struktur ist in deklariertopenssl/evp.h
, wird aber vonopenssl/x509.h
(was wir später benötigen werden) eingeschlossen, sodass Sie den Header nicht wirklich explizit einschließen müssen.Um eine
EVP_PKEY
Struktur zuzuweisen , verwenden wirEVP_PKEY_new
:Es gibt auch eine entsprechende Funktion zum Freigeben der Struktur -
EVP_PKEY_free
-, die ein einziges Argument akzeptiert: dieEVP_PKEY
oben initialisierte Struktur.Jetzt müssen wir einen Schlüssel generieren. In unserem Beispiel generieren wir einen RSA-Schlüssel. Dies geschieht mit der
RSA_generate_key
Funktion, die in deklariert istopenssl/rsa.h
. Diese Funktion gibt einen Zeiger auf eineRSA
Struktur zurück.Ein einfacher Aufruf der Funktion könnte folgendermaßen aussehen:
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 */ );
Wenn der Rückgabewert von
RSA_generate_key
istNULL
, ist ein Fehler aufgetreten. Wenn nicht, haben wir jetzt einen RSA-Schlüssel, den wir unsererEVP_PKEY
Struktur von früher zuweisen können :Die
RSA
Struktur wird automatisch freigegeben, wenn dieEVP_PKEY
Struktur freigegeben wird.Nun zum Zertifikat selbst.
OpenSSL verwendet die
X509
Struktur, um ein x509-Zertifikat im Speicher darzustellen. Die Definition für diese Struktur ist inopenssl/x509.h
. Die erste Funktion, die wir brauchen werden, istX509_new
. Die Verwendung ist relativ einfach:Wie bei
EVP_PKEY
gibt es eine entsprechende Funktion zum Freigeben der Struktur -X509_free
.Jetzt müssen wir einige Eigenschaften des Zertifikats mit einigen
X509_*
Funktionen festlegen :ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
Dies setzt die Seriennummer unseres Zertifikats auf '1'. Einige Open-Source-HTTP-Server lehnen es ab, ein Zertifikat mit der Seriennummer '0' zu akzeptieren. Dies ist die Standardeinstellung. Der nächste Schritt besteht darin, die Zeitspanne anzugeben, in der das Zertifikat tatsächlich gültig ist. Wir machen das mit den folgenden zwei Funktionsaufrufen:
X509_gmtime_adj(X509_get_notBefore(x509), 0); X509_gmtime_adj(X509_get_notAfter(x509), 31536000L);
In der ersten Zeile wird die
notBefore
Eigenschaft des Zertifikats auf die aktuelle Zeit gesetzt. (DieX509_gmtime_adj
Funktion addiert die angegebene Anzahl von Sekunden zur aktuellen Zeit - in diesem Fall keine.) In der zweiten Zeile wird dienotAfter
Eigenschaft des Zertifikats auf 365 Tage festgelegt (60 Sekunden * 60 Minuten * 24 Stunden * 365 Tage).Jetzt müssen wir den öffentlichen Schlüssel für unser Zertifikat mit dem zuvor generierten Schlüssel festlegen:
Da es sich um ein selbstsigniertes Zertifikat handelt, setzen wir den Namen des Ausstellers auf den Namen des Betreffs. Der erste Schritt in diesem Prozess besteht darin, den Betreffnamen zu erhalten:
Wenn Sie zuvor ein selbstsigniertes Zertifikat in der Befehlszeile erstellt haben, werden Sie wahrscheinlich nach einem Ländercode gefragt. Hier stellen wir es zusammen mit der Organisation ('O') und dem allgemeinen Namen ('CN') zur Verfügung:
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);
(Ich verwende hier den Wert 'CA', weil ich Kanadier bin und das ist unser Ländercode. Beachten Sie auch, dass Parameter 4 explizit in einen umgewandelt werden muss
unsigned char *
.)Jetzt können wir den Namen des Ausstellers tatsächlich festlegen:
Und schließlich sind wir bereit, den Signierprozess durchzuführen. Wir rufen
X509_sign
mit dem Schlüssel an, den wir zuvor generiert haben. Der Code dafür ist schmerzlich einfach:Beachten Sie, dass wir den SHA-1- Hashing-Algorithmus verwenden, um den Schlüssel zu signieren. Dies unterscheidet sich von der
mkcert.c
Demo, die ich zu Beginn dieser Antwort erwähnt habe und die MD5 verwendet.Wir haben jetzt ein selbstsigniertes Zertifikat! Aber wir sind noch nicht fertig - wir müssen diese Dateien auf die Festplatte schreiben. Zum Glück hat OpenSSL uns auch dort mit den
PEM_*
Funktionen abgedeckt, die in deklariert sindopenssl/pem.h
. Der erste, den wir benötigen, ist dasPEM_write_PrivateKey
Speichern unseres privaten Schlüssels.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 */ );
Wenn Sie den privaten Schlüssel nicht verschlüsseln möchten, übergeben Sie einfach
NULL
den dritten und vierten Parameter oben. In jedem Fall sollten Sie unbedingt sicherstellen, dass die Datei nicht für die Welt lesbar ist. (Für Unix-Benutzer bedeutet dieschmod 600 key.pem
.)Wütend! Jetzt haben wir nur noch eine Funktion: Wir müssen das Zertifikat auf die Festplatte schreiben. Die Funktion, die wir dafür benötigen, ist
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 */ );
Und wir sind fertig! Hoffentlich reichen die Informationen in dieser Antwort aus, um Ihnen eine ungefähre Vorstellung davon zu geben, wie alles funktioniert, obwohl wir die Oberfläche von OpenSSL kaum zerkratzt haben.
Für diejenigen, die sehen möchten, wie der gesamte obige Code in einer realen Anwendung aussieht, habe ich einen Gist (in C ++ geschrieben) zusammengestellt, den Sie hier anzeigen können .
quelle
Now we need to set the public key for our certificate using the key we generated earlier:
Ist dieser Satz ein Tippfehler? Sollte das nichtpublic key
seinprivate key
?Gibt es eine Möglichkeit, dies über einen
system
Anruf in Ihrer App zu tun ? Mehrere gute Gründe dafür:Lizenzierung: Wenn Sie die
openssl
ausführbare Datei aufrufen, wird sie möglicherweise von Ihrer Anwendung getrennt und bietet möglicherweise bestimmte Vorteile. Haftungsausschluss: Wenden Sie sich hierzu an einen Anwalt.Dokumentation: OpenSSL enthält eine phänomenale Befehlszeilendokumentation, die ein möglicherweise kompliziertes Tool erheblich vereinfacht.
Testbarkeit: Sie können OpenSSL über die Befehlszeile ausführen, bis Sie genau wissen, wie Sie Ihre Zertifikate erstellen. Es gibt viele Möglichkeiten; Erwarten Sie ungefähr einen Tag damit, bis Sie alle Details richtig verstanden haben. Danach ist es trivial, den Befehl in Ihre App zu integrieren.
Wenn Sie sich für die Verwendung der API entscheiden, überprüfen Sie die
openssl-dev
Entwicklerliste auf www.openssl.org.Viel Glück!
quelle
Nathan Osman erklärte es sehr ausführlich und hatte das gleiche Problem in C ++ zu lösen. Hier ist mein kleines, umgeschriebenes Konzept im cpp-Stil mit ein paar Einschränkungen:
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), 0); // now X509_gmtime_adj(X509_get_notAfter(cert), 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; }
Natürlich sollte es mehr Kontrollen der Rückgabewerte der Funktion, tatsächlich alle von ihnen überprüft werden soll , aber das wäre eine Probe zu „branchy“ zu machen und ist recht einfach irgendwie zu verbessern.
quelle