Programmatically Create X509 Certificate using OpenSSL
Asked Answered
W

4

94

I have a C/C++ application and I need to create a X509 pem certificate containing both a public and private key. The certificate can be self signed, or unsigned, doesn't matter.

I want to do this inside an app, not from command line.

What OpenSSL functions will do this for me? Any sample code is a bonus!

Weighted answered 2/11, 2008 at 2:16 Comment(0)
B
53

You'll need to familiarize yourself with the terminology and mechanisms first.

An X.509 certificate, by definition, does not include a private key. Instead, it is a CA-signed version of the public key (along with any attributes the CA puts into the signature). The PEM format really only supports separate storage of the key and the certificate - although you can then concatenate the two.

In any case, you'll need to invoke 20+ different functions of the OpenSSL API to create a key and a self-signed certificate. An example is in the OpenSSL source itself, in demos/x509/mkcert.c

For a more detailed answer, please see Nathan Osman's explanation below.

Binominal answered 2/11, 2008 at 6:51 Comment(2)
Yes - I do need to familiarize myself more with ssl concepts. I will check out the example, thanks for the link (the link has a problem though, but I will figure it out.) I have also used Crypto++ for some stuff, it might be eaiser to use than OpenSSL in this case.Evocation
Thanks! Selected this answer because of the provided link.Evocation
W
234

I realize that this is a very late (and long) answer. But considering how well this question seems to rank in search engine results, I figured it might be worth writing a decent answer for.

A lot of what you will read below is borrowed from this demo and the OpenSSL docs. The code below applies to both C and C++.


Before we can actually create a certificate, we need to create a private key. OpenSSL provides the EVP_PKEY structure for storing an algorithm-independent private key in memory. This structure is declared in openssl/evp.h but is included by openssl/x509.h (which we will need later) so you don't really need to explicitly include the header.

In order to allocate an EVP_PKEY structure, we use EVP_PKEY_new:

EVP_PKEY * pkey;
pkey = EVP_PKEY_new();

There is also a corresponding function for freeing the structure - EVP_PKEY_free - which accepts a single argument: the EVP_PKEY structure initialized above.

Now we need to generate a key. For our example, we will generate an RSA key. This is done with the RSA_generate_key function which is declared in openssl/rsa.h. This function returns a pointer to an RSA structure.

A simple invocation of the function might look like this:

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 */
);

If the return value of RSA_generate_key is NULL, then something went wrong. If not, then we now have an RSA key, and we can assign it to our EVP_PKEY structure from earlier:

EVP_PKEY_assign_RSA(pkey, rsa);

The RSA structure will be automatically freed when the EVP_PKEY structure is freed.


Now for the certificate itself.

OpenSSL uses the X509 structure to represent an x509 certificate in memory. The definition for this struct is in openssl/x509.h. The first function we are going to need is X509_new. Its use is relatively straightforward:

X509 * x509;
x509 = X509_new();

As was the case with EVP_PKEY, there is a corresponding function for freeing the structure - X509_free.

Now we need to set a few properties of the certificate using some X509_* functions:

ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);

This sets the serial number of our certificate to '1'. Some open-source HTTP servers refuse to accept a certificate with a serial number of '0', which is the default. The next step is to specify the span of time during which the certificate is actually valid. We do that with the following two function calls:

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

The first line sets the certificate's notBefore property to the current time. (The X509_gmtime_adj function adds the specified number of seconds to the current time - in this case none.) The second line sets the certificate's notAfter property to 365 days from now (60 seconds * 60 minutes * 24 hours * 365 days).

Now we need to set the public key for our certificate using the key we generated earlier:

X509_set_pubkey(x509, pkey);

Since this is a self-signed certificate, we set the name of the issuer to the name of the subject. The first step in that process is to get the subject name:

X509_NAME * name;
name = X509_get_subject_name(x509);

If you've ever created a self-signed certificate on the command line before, you probably remember being asked for a country code. Here's where we provide it along with the organization ('O') and common name ('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);

(I'm using the value 'CA' here because I'm Canadian and that's our country code. Also note that parameter #4 needs to be explicitly cast to an unsigned char *.)

Now we can actually set the issuer name:

X509_set_issuer_name(x509, name);

And finally we are ready to perform the signing process. We call X509_sign with the key we generated earlier. The code for this is painfully simple:

X509_sign(x509, pkey, EVP_sha1());

Note that we are using the SHA-1 hashing algorithm to sign the key. This differs from the mkcert.c demo I mentioned at the beginning of this answer, which uses MD5.


We now have a self-signed certificate! But we're not done yet - we need to write these files out to disk. Thankfully OpenSSL has us covered there too with the PEM_* functions which are declared in openssl/pem.h. The first one we will need is PEM_write_PrivateKey for saving our private key.

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 */
);

If you don't want to encrypt the private key, then simply pass NULL for the third and fourth parameter above. Either way, you will definitely want to ensure that the file is not world-readable. (For Unix users, this means chmod 600 key.pem.)

Whew! Now we are down to one function - we need to write the certificate out to disk. The function we need for this is 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 */
);

And we're done! Hopefully the information in this answer is enough to give you a rough idea of how everything works, although we've barely scratched the surface of OpenSSL.

For those interested in seeing what all of the code above looks like in a real application, I've thrown together a Gist (written in C++) that you can view here.

Watersoak answered 26/2, 2013 at 5:51 Comment(11)
Thanks for the excellent answer and explanation! Only a small doubt: is this sentence Now we need to set the public key for our certificate using the key we generated earlier: a typo? Shouldn't the public key be private key?Dahomey
I had to add fclose(f) at the end. Otherwise the file being written was 0BOvertly
nice and comprehensive clear answer. One thing more, how to add more parameter to the certificate as found in openssl.cnf file. e.g. to add extension subjectAltName?Fenny
This answer was a gigantic help. For folks that want to add an extension to their certificate, see: #35617353Jemena
What if we don't want to have only a selfsigned certificate? How to create a private key and a CSR? Which someone signs and returns the signed certificate?Possessive
@Possessive I would suggest asking a new question.Watersoak
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, "localhost", -1, -1, 0) is probably wrong. Hostnames always go in the SAN. If its present in the CN, then it must be present in the SAN too (you have to list it twice in this case). For more rules and reasons, see How do you sign Certificate Signing Request with your Certification Authority and How to create a self-signed certificate with openssl?Blowy
From the docs: "RSA_generate_key() was deprecated in OpenSSL 0.9.8; use RSA_generate_key_ex() instead." And both will be deprecated in OpenSSL 3.0.Pitta
In OpenSSL 3.0, use EVP_RSA_gen(2048) instead of EVP_PKEY_new + RSA_generate_key + EVP_PKEY_assign_RSA. This also happens to simplify the code by combining all three functions into one call.Staffer
According to this openssl manpage , X509_get_notBefore and X509_get_notAfter has been deprecated since version 1.1.0 (possibly removed in version 1.1.1), instead you should use X509_getm_notBefore and X509_getm_notAfter for modifying the time fieldsPlanography
EVP_des_ede3_cbc() can be omitted if it is not necessary to encrypt the private key file and you don't want pass-phrase prompt (might be useful in automatic testing), at risk of security vulnerabilityPlanography
B
53

You'll need to familiarize yourself with the terminology and mechanisms first.

An X.509 certificate, by definition, does not include a private key. Instead, it is a CA-signed version of the public key (along with any attributes the CA puts into the signature). The PEM format really only supports separate storage of the key and the certificate - although you can then concatenate the two.

In any case, you'll need to invoke 20+ different functions of the OpenSSL API to create a key and a self-signed certificate. An example is in the OpenSSL source itself, in demos/x509/mkcert.c

For a more detailed answer, please see Nathan Osman's explanation below.

Binominal answered 2/11, 2008 at 6:51 Comment(2)
Yes - I do need to familiarize myself more with ssl concepts. I will check out the example, thanks for the link (the link has a problem though, but I will figure it out.) I have also used Crypto++ for some stuff, it might be eaiser to use than OpenSSL in this case.Evocation
Thanks! Selected this answer because of the provided link.Evocation
P
7

Nathan Osman explained it greatly and fully, had the same problem to be solved in C++ so here is my small addition, cpp-style rewritten concept with a couple of caveats taken into account:

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

Of course, there should be more checks of function's return values, actually all of them should be checked but that would make a sample too "branchy" and is pretty easy to improve anyway.

Paquin answered 13/8, 2019 at 13:30 Comment(2)
Looks like we forgot to send the pointer behind the unique pointer when setting expiry.... I would paste the code but apparently I cannot get it to look like code.Frostbitten
@tripulse emm... because? Why?Paquin
C
2

Any chance of doing this via a system call from within your app? Several good reasons for doing this:

  • Licensing: Calling the openssl executable arguably separates it from your application and may provide certain advantages. Disclaimer: consult a lawyer on this.

  • Documentation: OpenSSL comes with phenomenal command-line documentation that greatly simplifies a potentially complicated tool.

  • Testability: you can exercise OpenSSL from the command line until you understand exactly how to create your certs. There are a lot of options; expect to spend about a day on this until you get all the details right. After that, it's trivial to incorporate the command into your app.

If you choose to use the API, check the openssl-dev developers' list on www.openssl.org.

Good luck!

Clan answered 2/11, 2008 at 2:44 Comment(4)
OpenSSL is license under an apache style license, it can be used in commercial apps just like any other non-copyleft license. People still might want to consult a lawyer to make sure everything they do is okay, but it does not have GPL related issuesSideburns
Noted and updated -- thank you. Separation of open-source from closed-source code is generally a good idea, and unless efficiency is of critical importance, the other reasons make a good case for using the stand-alone openssl utility.Clan
I would rather not use a system call to do this. Your point about documentation is very valid - the docs for the SSL side of OpenSSL don't help much.Evocation
Actually there are GPL related issues: the Apache 1.0 Licence and 4 clause BSD licence under which OpenSSL is distributed are both incompatible with GPL software. There is an exception in GPL for librairies provided by the OS, so if you link with the OpenSSL provided by your distribution you might get away with it. See alsoSuited

© 2022 - 2024 — McMap. All rights reserved.