Openssl CLI Self-Signed Certs – 2023

I set out this morning to write a post on configuring external ETCD for Kubernetes, with openssl self-signed certs (you know, the kind you use for your home lab). I got sidetracked on openssl and all of its ever-changing/deprecated options. So, this will be a preamble to that original intent.

Other than key sizes and algorithms, only a few command options have changed in openssl v3. I won’t dive into key size and algorithms (too much to cover there). In this post, I will cover the why and how of creating self-signed certificates with openssl, along with up to date commands for v3.  I think the first time I grappled with understanding SSL (now TLS) was 1997. Although SSL has now become TLS, not a lot has changed with the underlying basic dynamics of public key encryption.

Not to get deep into SSL vs. TLS, they both use public/private key pairs (public key encryption). TLS is (in simple terms) a newer version of SSL. While TLS fixes vulnerabilities contained within SSL, it still relies on the fundamental public/private key mechanism that SSL did. As such, nothing has really changed with regards to  the basics of public key encryption operation since the introduction of SSL (larger key sizes and additional algorithms have been introduced, but the principles of asymmetric encryption remain exactly the same).

Public key encryption relies on an algorithm that can encrypt a value with one key, that can only be decrypted with a corresponding different key. AND that the inverse is true. That is, we can encrypt message X with key A, and only key B can decrypt it. If we encrypt message X with key B, only key A can decrypt it. That’s the essence of public key encryption.

So, if you have both key A and Key B, you can decide to make one of them openly available (public key). I can give you key B and you can encrypt anything you want to send me with it. If we want to have a short-lived (session), fully encrypted transmission, you can encrypt a symmetric encryption key (a different encryption algorithm that uses a single key from both sides) with my public key and send it to me. Then we could use that same key to send each other encrypted data that we can both decrypt. When the conversation is over, we discard the symmetric key.

That’s the gist of using public key encryption to establish session encryption (the same as when you login to your bank account online and see that https icon).

Now, there is the authentication part of it. If you present your public key to me, how do I really know it’s you? This is where certificates and certificate authorities come in. A certificate is a public key digitally signed (hashed) with another private key. That signature/hash is created by a certificate authority. A certificate authority is simply an entity with a well known and trusted self-signed public key. Your browser comes with these entities and their certificates preconfigured.

I receive the certificate from my bank (my bank’s public key with a list of services it’s authorized to provide, and a hash of it created by a CA’s private key). I decrypt the hash with the CA’s public key (the one my browser came preconfigured to automagically trust) and compare the plaintext. If they match, I trust it as being the true certificate for my bank. I then can send some challenge message encrypted with the public key. If the entity I’m sending it to can decrypt and send back to me, I know it not only has a valid certificate, but also the corresponding private key, and it is authenticated.

There is a third element of authentication concerning the host’s FQDN, contained within the cert via the CSR process. And there is a nuance of something called a ‘subject alternative name (SAN)` associated with that. More on that in the next post.

We can implement our own ‘trust’ model, replacing the root trusted CA certificates with ‘self-signed’ certificates for authentication.. For this,  we create our own certificate to sign our various certificates with. We then configure our internal systems to trust that signing certificate exactly as a commercial certificate authority.

To accomplish self-signing, we need to create key pairs and certificates. The first key pair will be used for our self-signed CA cert. We sign its certificate with its own private key (hence self-signed). We then use that certificate and private key to sign certificates we create for any purpose we need.

We configure our services to trust that self-signed CA certificate in the same way a browser trusts the commercial CAs. Thus when service A talks to Service B, and service B presents its signed certificate (by our self-signed CA cert) for authentication/encryption, service A can inspect its signing authority and trust that it is legit.

We can use many tools to create public/private key pairs, and generate certificates vouching for them. One of the longest running tools is openssl.

I think of understanding openssl commands being similar to understanding a house of mirrors. You know left and right, but it confuses you. Public key encryption and authentication really isn’t a complex topic to grasp. But openssl changes commands, intertwines command names with switches, deprecates commands and switches to replace with entirely new ones that perform the same function, and is documented in ways that make it very confusing (to me at least).

Let’s look at the three things we need from openssl to create a self-signed service certificate:

1. Self-signed CA cert
3. Create service private key and certificate signing request
3: Generate corresponding public key  signed by our self-signed CA, creating service certificate

Create self-signed CA cert

For number one, we create CA key and self-signed CA certificate. The following is an example openssl command to accomplish this:

openssl req -x509 -noenc -newkey rsa:4096 -subj '/CN=labCA' -keyout ca_key.pem -out ca_cert.pem -days 36500

openssl req:  This command primarily creates and processes certificate requests (CSRs) in PKCS#10 format. It can additionally create self-signed certificates for use as root CAs for example. In this case, it’s creating a self-signed certificate for use as a root CA (self-signing).

-x509: This option outputs a certificate instead of a certificate request. This is what tells openssl req to create a self-signed CA cert vs. processing a CSR.

-noenc:  This flag replaces  nodes as of openssl v3. If this option is specified, the private key will not be encrypted with a passphrase. In other words, don’t require a password to decrypt the private key. Keep it secure either way.

-newkey rsa:4096: This option is used to generate a new private key  This option implies the -new flag to create a new  certificate when -x509 is used. [rsa:]nbits generates an RSA key nbits in size.

-subj:  Sets subject name for new request or supersedes the subject name when processing a certificate request. Basically, just need a subject name for a self-signed cert, so provide it without a config file.

-keyout: This gives the filename to write the private key to that has been newly created.

-out: Output file to write self-signed cert to.

-days: When -x509 is in use this specifies the number of days to certify the certificate for, otherwise it is ignored. n should be a positive integer. The default is 30 days. Make it enormous for lab use, who cares.

Create Service Private Key and Certificate Signing Request

For number two, we create a service private key and a certificate signing request (CSR) at the same time.. We also use a custom config file to create the CSR so that we can specify any parameters we need the signed certificate to contain.  The following is an example openssl command to accomplish this:

openssl req -noenc -newkey rsa:4096 -keyout etcd_key.pem -out etcd_cert.csr -config etcd_cert.cnf

Many of the above are the same as the step one. You’ll notice that -x509 is omitted, meaning that we are creating a CSR rather than a self-signed cert (this is the confusing stuff I mentioned earlier). So the -out option will create a CSR rather the certificate as in step one.

The other difference is the -config option. Openssl uses a config file for creating CSRs. We will nearly always use a custom config per CSR type. So we create the file we need, and then point to it with this option. See my next post on configuring ETCD certs to see how we use this option for ETCD.

Create service cert/public key, signed by self-signed CA

For number three, We use the self-signed CA cert from step one to read-in the CSR from step two, sign the service key, and incorporate a public key at the same time.. The following is an example openssl command to accomplish this:

openssl x509 -req -days 36500 -in etcd_cert.csr -CA ca_cert.pem -CAkey ca_key.pem -CAcreateserial -out etcd_cert.pem

Now  we’re using openssl x509 command, not openssl req command (did I already talk about the confusing part of openssl?).

Let’s go through the openssl x509 command options used:

openssl x509:  Certificate display and signing command

-req:  By default a certificate is expected on input. With this option a PKCS#10 certificate request is expected instead, which must be correctly self-signed.

-days:  Specifies the number of days until a newly generated certificate expires. The default is 30. Cannot be used together with the -preserve_dates option.

-in:  This specifies the input to read a certificate from or the input file for reading a certificate request if the -req flag is used. In both cases this defaults to standard input. This option cannot be combined with the -new flag.

-CA filename|uri:  Specifies the “CA” certificate to be used for signing. When present, this behaves like a “micro CA” as follows: The subject name of the “CA” certificate is placed as issuer name in the new certificate, which is then signed using the “CA” key given as detailed below. This option cannot be used in conjunction with -key (or -signkey). This option is normally combined with the -req option referencing a CSR. Without the -req option the input must be an existing certificate unless the -new option is given, which generates a certificate from scratch.

-CAkey filename|uri:  Sets the CA private key to sign a certificate with. The private key must match the public key of the certificate given with -CA.

-CAcreateserial:  With this option and the -CA option the CA serial number file is created if it does not exist. A random number is generated, used for the certificate, and saved into the serial number file (I have no idea if this is still used, but the docs call for it).

-out filename:  This specifies the output filename to write to or standard output by default (The cert file).

Ok, so there are the three commands with explanation (I hope). Openssl is a super confusing bin and I think a lot of people just keep copying and pasting it forward. With a little investigation, its workings become clearer.

The config file for the CSR generation is very important, and is very dependent on the needs of your final certificate. I will cover the ETCD values for it in my next post.