“Safe, secure and all scrambled up”
Jay explains how to create encryption keys and TLS certificates
Reckless Guide
Part 7
Using OpenBSD in a networked environment, there are plenty of scenarios where you'll need to deal with TLS certificates and their associated keys.
This week Jay will walk us through some of the more common situations you might find yourself in, including generating new keys, creating your own self-signed certificates, and even getting CA signed certificates from a publicly-recognised signing authority.
This article is part of a series - check out the index.
Website themes
The Exotic Silicon website is available in ten themes, but you haven't chosen one yet!
Does it seem too complicated?
Despite the apparent complexity of the TLS subsystem, most common requirements can be met with a small subset of the available functionality.
But it's still worth learning the rest of it at some point!
Self-signed certificates verses ‘real’ certificates
Whilst anybody can easily create their own TLS certificate using a few, (relatively), simple commands, such a certificate won't be of much use outside of testing purposes on your own machine. You'll be the proud owner of a certificate of the infamous ‘self-signed’ variety, and by default, warnings will pop up all over the place whenever you try to use it.
The current de-fato system for establishing trust with TLS certificates relies on a finite number of globally-recognised signing authorities being trusted by default by the operating system in it's pre-loaded certificate bundle. Effectively, the only difference between a self-signed certificate and a ‘real’ one, is that the latter has been digitally signed with another certificate which has itself been flagged as CA certificate, and listed in this bundle. For many years, getting a real certificate was a cumbersome and expensive process, which acted as a barrier for many individuals and small businesses.
Of course, TLS certificates don't have to be used with this particular trust model. In purely mathematical terms, a self-signed certificate is no weaker cryptographically speaking than it's ‘officially’ signed counterpart. Other trust models such as Trust On First Use, (TOFU), exist, and in a few cases are recommended over the more common setup. One example of such a recommendation would be in the current gemini protocol specification, in which use of a self-signed certificate is recommended.
Within a company or organisation, it's also perfectly possible to create your own CA and use it to sign the certificates you use internally. You are then free to choose whatever parameters you desire for the certificates, instead of having them constrained by a third party. The main advantage here is that you can create certificates for use on an internal network that have expiration dates much further in the future than would be permitted by a globally recognised CA.
In any case, regardless of whether you use a self-signed certificate or a ‘real’ one, the basic principles are the same.
The manual pages can be daunting, but most practical use-cases are actually quite straightforward.
The three specific applications for TLS that we'll be looking at are SMTP, HTTPS, and IPSEC, but the procedures for creating the necessary keys and certificates are almost identical for any other case.
File locations and directory paths
Obviously, each program expects to find it's own keys and certificates in a specific location. These are documented in the manual pages, but we'll list them here for easy reference:
File locations
Main openssl configuration file:/etc/ssl/openssl.cnf
Default key for httpd:/etc/ssl/private/server.key
Default cert for httpd:/etc/ssl/server.crt
Default local public key for iked:/etc/iked/local.pub
Default local private key for iked:/etc/iked/private/local.key
Remote public keys for iked:/etc/iked/pubkeys/
CA certificates for iked:/etc/iked/ca/
Local certificate for iked:/etc/iked/certs/[hostname].crt
Certificates and keys for smtpd:/etc/iked/certs/[hostname].crt
Handy hint!
Updating the certificate bundle from CVS
The default certificate bundle on an OpenBSD machine is in /etc/ssl/cert.pem. It's part of libcrypto, and within the sourcecode it can be found in /usr/src/lib/libcrypto/cert.pem.
If you're running a release version of OpenBSD as opposed to running -current, then it can be useful to watch for changes to this file in CVS and update it manually as required. This is especially useful if you are running the previous release of OpenBSD, because although it will still be receiving errata patches, your root certificate bundle could be a year out of date!
# ( umask 0333 ; ftp -o /etc/ssl/cert.pem.new https://cvsweb.openbsd.org/src/lib/libcrypto/cert.pem?rev )
# mv /etc/ssl/cert.pem /etc/ssl/cert.pem.old
# mv /etc/ssl/cert.pem.new /etc/ssl/cert.pem
Updating cert.pem from CVS
Even if you don't use the CVS command line utility, it's easy to obtain the file via the CVS-on-web service as shown above. The download is via https, and the ftp utility will check the certificate on the webserver before downloading it.
On servers that are hosting multiple websites where you want to use a different certificate for each one, it's probably easiest to name the files after the corresponding hostname. So you might have /etc/ssl/example.com.crt and /etc/ssl/private/example.com.key.
The /etc/ssl location is also convenient for certificates and keys that you'll be using with smtpd.
Remember that private keys, as well as the directories containing them, should usually have fairly restricted file permissions. In most cases you will want them to be owned by root, with group wheel, and access permissions no greater than 0700.
Typically, we might be dealing with the following files when setting up a new key pair and certificate:
example.key The private key, (also known as the secret key).
example.pub The matching public key, (derived from the private key).
example.ssc A self-signed certificate.
example.crt A CA-signed certificate.
example.csr A certificate signing request.
example.ext A list of additional options for the openssl program.
example.srl A serial number in hex.
Files we'll be using
The use of .ssc for a self-signed certificate is not standardised.
Most guides will just use either .crt or .pem for all certificates, both self-signed and CA-signed. However, I think it makes things much clearer to differentiate between the two types of certificate by using a different file extension.
The serial number file is not required for basic key and certificate generation, but we've listed it here as it's necessary if you want to sign a certificate yourself using another CA certificate that you've previously created.
The certificates that we generate will be in pem format, and the ‘.pem’ extension is also commonly used for such certificates.
Elsewhere, you might see references to certificates in der format, or less commonly net format. Don't worry, it's trivial to convert between these three formats, and even to print the certificate out in a plain text, human-readable format. Whilst the pem format is base64 encoded, and so only uses printable characters, der and net formats are binary, so avoid cat'ing them to the terminal.
Fun fact!
Origin of the term PEM
PEM actually stands for Privacy Enhanced Mail, as this file format was originally developed for use in an email encryption standard.
ECDSA key generation
Unless you have a very good reason to use an RSA key instead, you'll probably want to use ECDSA for new keys.
By default, the private key will be written with 0644 file permissions.
Either run these commands in a directory that is only readable by root, or set the shell's umask to a more conservative value.
First, we create a file containing some key parameters.
This file doesn't contain any secret data, and can easily be re-generated whenever it's needed.
# openssl ecparam -out ec-secp384r1.pem -name secp384r1
Generating a 'curve' parameters file
Next, we generate a new private key:
# openssl genpkey -paramfile ec-secp384r1.pem -out example.key
# rm ec-secp384r1.pem
Generating a private key and deleting the 'curve' parameters file
Alternatively, the two steps above can also be combined into a single command.
# openssl ecparam -name secp384r1 -genkey -noout -out example.key
Single command version of the above steps.
Now that we have our private key, we can create a matching public key from it, (the public key is derived from the private key). The public key is not required for use with HTTPS or SMTP, but we'll need it for some IPSEC applications. It can be created from the private key at any time in the future, though, if the need arises.
# openssl ec -in example.key -pubout -out example.pub
Generating the corresponding public key.
Now we have our ECDSA key pair, example.key, and example.pub.
RSA key generation
If for some reason you decide to use an RSA key instead of an ECDSA key, you'll need to choose the size of key that you want to generate.
The default key size is currently 2048 bits. As of 2022, it's plausible that this might not be sufficient for your particular security needs.
Keys of 4096 bits, 8192 bits, and beyond can easily be created, but be aware that keys larger than 8192 bits may take a prohibitively long time to generate. Generating three 16384-bit keys on my workstation took 109 seconds, 17 seconds, and 276 seconds respectively, whereas a 8192-bit key typically takes between 5 and 15. Additionally, although you can easily create self-signed certificates using a key larger than 4096 bits, you might have difficulty getting a certificate authority to accept a CSR created with such a large key.
Generate an 8192-bit RSA key:
# openssl genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:8192 -out example.key
Generating an RSA key
Out of date information
Elsewhere, in guides from other sources, you might see a command like this used instead:
# openssl genrsa -out example.key 8192
This is an older way to invoke the RSA key generation, using a protocol-specific command. The invocation in the example above is now the preferred way.
As with ECDSA keys, the public key is created from the private key:
# openssl pkey -pubout -in example.key -out example.pub
Generating the public key
Creating a self-signed certificate - preamble
To create any kind of certificate, we first need to create a Certificate Signing Request, or CSR. This is true even if we're going to be signing our own certificate, in which case we simply use the CSR ourselves.
A CSR is essentially a certificate in which the signature algorithm is set to NULL, and the information in it is used a template for creating the actual certificate, which is, of course, signed with the private key of the issuer.
It is therefore during this process of creating a CSR that we specify the identify for the new certificate. A certificate needs to identify it's subject, that is the machine or entity that will be presenting it, as well as the issuer.
The collection of fields that comprise each of these identities is known as a distinguished name, or DN, and contains one or more of the following:
Country name C
State or province ST
Locality, typically region or city L
Organization or company O
Organizational unit, department OU
Common name CN
Email address /emailAddress=
Fields in a distinguished name
Any of these fields can be omited, (even the common name field can be omited), as long as at least one is present.
Handy hint!
Consistency looks more professional
If you create multiple certificates for different domains within your organisation, it usually looks more professional if the details on each certificate match in terms of upper and lower case, spacing, abbreviations, and so on.
For certificates used on the internet, the common name field was traditionally used to store the canonical domain name for the entity represented. The word ‘common’ is used here in the sense of ‘most commonly used’, in other words the primary or canonical domain name.
This created limitations as the common name field only permitted one entry, so certificates were basically limited to representing a single domain name or IP address. Wildcard entries were permitted, so it was also possible to represent a domain and all of it's subdomains using a common name such as *.example.com. Eventually, however, even this proved too limited, and it became clear that a better solution was required.
To overcome the limitations of using the common name field for the domain name, a new Subject Alternative Name field was created, which can store multiple distinct domain names, and IP addresses. It's also possible to store an email address in the SAN field, although this won't be necessary for the specific applications that we're looking at here.
Handy hint!
The Common Name field is now widely ignored in favour of just parsing the SANs
For a long time after the introduction of Subject Alternative Names in the year 2000, the standard practice for a certificate referencing only one domain name was to put that domain name in both the CN and SAN fields. However this is no longer strictly necessary or even particularly useful, as the CN field is now widely ignored by modern software in favour of just parsing the SANs.
Remember that the details you enter for the distinguished name will be visible to clients connecting to your server if they check the certificate.
For this reason alone, you should ensure that the information entered is accurate, but furthermore, some signing CAs might actually place their own requirements on the distinguished name information before they are prepared to issue a certificate. Typically, they might require that details such as the organizational name exactly match your registered business name.
Creating your own certificate authority
Creating a CA signing certificate for internal use
Your own CA!
There is almost no technical difference between a self-signed certificate and a CA certificate
From a technical perspective, there is almost no difference between a self-signed certificate and a CA-signed certificate. In fact, we can easily create a CA signing certificate ourselves with a single additional option in example.ext, which we can then use to sign other certificates.
Obviously, the resulting signed certificates won't be of much use on a public internet server since it won't be present in the bundle of root certificates on any of the client machines. However, creating your own CA certificate does have at least three significant use-cases:
Firstly, if we were creating numerous self-signed certificates for use on internal networks, it would quickly become tedious to add each one directly to the root certificate bundle on every client machine. By creating our own local CA certificate and using it to sign the other non-CA certificates that we create, we can avoid this extra work and just add the single local CA certificate to each client.
Secondly, at least some versions of Android will not use any locally installed self-signed certificates which are not also flagged as CA certificates. We could solve this problem too by creating our own CA certificate, using it to sign any other local certificates that we want to use, and then installing both certificates on the Android device. If our requirements are fairly simple though, such as HTTPS communication between a local server and the device, then we could just create our own self-signed CA certificate and use it directly on the webserver. From a technical viewpoint, this works fine. It's not particularly elegant and the preferred way is to separate out the roles of CA certificates, (signing others), and non-CA certificates, (non-signing applications). For direct communications between two machines, though, a single self-signed CA certificate is a quick and practical solution.
Thirdly, if we want to use certificates in conjunction with iked to form a VPN, then we also usually want to separate out the role of the CA certificate from that of the regular certificates.
Creating a regular self-signed certificate
First, we need to set any additional options that we want to use for this certificate. This is done by creating the file example.ext.
One additional option that you will almost certainly want is the SAN field that we mentioned in the preamble section above.
# echo "subjectAltName=DNS:example.com,DNS:www.example.com,IP:,IP:2001:db8::1" > example.ext
Storing any additional options in the external options file
Here were are including two DNS names, example.com, and www.example.com, in the SAN. We're also including one literal IPv4 address, and one literal IPv6 address.
When the completed certificate is viewed in human-readable form, these SAN entries will appear under the heading of ‘X509v3 extensions’.
X509v3 extensions:
X509v3 Subject Alternative Name:
DNS:example.com, DNS:www.example.com, IP Address:, IP Address:2001:DB8:0:0:0:0:0:1
The above SAN entries as they will appear in the final certificate
If you intend to use your new certificate directly as a self-signed certificate and not convert it into a public CA signed certificate, then you might want to make it a CA certificate itself as we discussed above, by using the following option:
# echo "basicConstraints=CA:true" >> example.ext
Setting the option to create a CA signing certificate
This will show up as follows when the generated certificate is viewed:
X509v3 Basic Constraints:
CA entry in the final certificate
We could also use the same option if we were creating a separate CA certificate to use exclusively for signing our other regular certificates.
Of course, if we submit such a CSR with CA:true in it to a globally recognised CA they will either strip out this flag or just refuse to sign a certificate at all. So unfortunately we can't just create our own globally recognised CA on a whim and start issuing certificates to other people for fun and profit.
IKED certificates
If you are creating certificates for use with iked, you'll need to enable some additional options, which we will discuss in more detail in the section about using certificates with iked.
The next step is to generate a certificate signing request:
# openssl req -key example.key -new -out example.csr
Create a CSR
This command will prompt you interactively to enter the details for the distinguished name. It will also prompt you to enter a password to protect the new certificate. This is optional, so if we don't want to add a password, we can just hit enter at the password prompt and no password will be set. Remember that if you set a password on the certificate, it will be required whenever the certificate is re-loaded by any server processes that will be using it.
After this we create the actual self-signed certificate using that certificate signing request, together with our external options file:
# openssl x509 -sha256 -req -days 365 -in example.csr -signkey example.key -extfile example.ext -out example.ssc
Creating the actual certificate
We specify the validity time of the new certificate at this stage. In the example above, it's set to 365 days.
Note that we've also included the -sha256 option to set the signature algorithm to ecdsa with sha256.
The default signature algorithm is ecdsa with sha1, which has long been considered cryptographically weak, (in this application). We could also have specified -sha512, but as of 2022, ecdsa with sha512 is not widely used.
At this point, we have our newly created self-signed certificate in example.ssc.
Using a CA certificate to sign others
If we create our own CA certificate called ca_example.ssc, and want to use it to sign a regular certificate example.ssc, we can do this with just a few commands.
First, we initialize the file containing the serial number.
The number contained in this file will be incremented each time we use it to sign a certificate.
# echo 00 > ca_example.srl
Initialize the serial number counter
Next, we can either sign the self-signed certificate example.ssc that we previously created...
# openssl x509 -sha256 -CA ca_example.ssc -CAkey ca_example.key -CAserial ca_example.srl -in example.ssc -out example.crt
Creating the certificate by signing the self-signed certificate with our CA signing certificate.
...or alternatively we can work from the original CSR, in which case we will also need to include the required extensions in example.ext as well in the same way that we included them when we created the self-signed certificate:
# openssl x509 -sha256 -CA ca_example.ssc -CAkey ca_example.key -CAserial ca_example.srl -req -in example.csr -extfile example.ext -out example.crt
Creating the certificate from the original certificate signing request.
Either way, we end up with a regular certificate in example.crt which has been issued by ca_example.
Converting between formats
We can convert a certificate in pem format to der or net format with the following commands:
# openssl x509 -in example.ssc -outform der -out example.der
# openssl x509 -in example.ssc -outform net -out example.net
Converting a pem certificate to der or net format
We can also print it out in human-readable text format:
# openssl x509 -in example.ssc -noout -text
Converting a certificate to text format
If we want the human-readable form, followed by the base64 pem format output, we just leave out the -noout option:
# openssl x509 -in example.ssc -text
Human-readable text, followed by the certificate in pem format
To add a certificate to the bundle of root certificates on an OpenBSD machine, it's only necessary to add it in pem format to /etc/ssl/cert.pem.
However, by convention the human-readable text format is included just before it, which is what the above command produces.
Using our new certificate with httpd
To use our new certificate with httpd, we only need to add three directives to the relevant server section of httpd.conf.
Assuming that we already have a server section declared in our httpd.conf for a server ‘example.com’ listening on port 80 of the IPv6 address 2001:db8::1, we just need to add an extra listen directive to enable listening on port 443 with tls enabled, as well as specify the path for the certificate and key using ‘tls certificate’ and ‘tls key’ directives:
server "example.com" {
listen on 2001:db8::1 port 80
listen on 2001:db8::1 tls port 443
tls certificate "/etc/ssl/example.ssc"
tls key "/etc/ssl/private/example.key"
Extra lines to add to httpd.conf to enable TLS in our example configuration
If we created a single self-signed certificate, with or without the CA flag set, then that is what we reference in the tls certificate directive.
However, if we created a separate CA certificate to sign our regular certificate with, then we should serve the full chain of certificates.
In our case, we can create this full chain by simply concatenating the CA certificate and our regular certificate.
# cat example.crt ca_example.ssc > full_chain_example.crt
Creating a full certificate chain for a certificate signed by our own CA signing certificate
To serve this new full chain, our httpd.conf changes slightly:
server "example.com" {
listen on 2001:db8::1 port 80
listen on 2001:db8::1 tls port 443
tls certificate "/etc/ssl/full_chain_example.crt"
tls key "/etc/ssl/private/example.key"
Serving a full certificate chain with httpd
Don't forget!
Restart httpd
Remember that we either need to restart httpd or send the httpd parent process a HUP signal for any changes to the configuration file to take effect:
# /etc/rc.d/httpd -f restart
Using our new certificate with smtpd
Using our new certificate with smtpd is also fairly straightforward...
There are no default locations for certificates and keys in smtpd, so we first specify the relevant paths for them using pki directives:
pki example.com key "/etc/ssl/private/example.key"
pki example.com cert "/etc/ssl/example.ssc"
Specifying certificate and key paths using pki directives in smtpd.conf
Next, we need to enable the use of TLS by adding options to the listen directive.
Multiple choices!
At this point, we have a choice of possible configurations
The three main choices are:
  • STARTTLS, (optional)
  • STARTTLS, (forced)
In most cases, the intended use of the server and who it needs to communicate with will dictate the option we use. For public internet-facing servers, optional STARTTLS offers the most compatibility with other systems. Forced STARTTLS will cause mail from servers that refuse to use TLS to be dropped, (which may be a desired effect). SMTPS is not widely supported or used on the public internet at large, but can be useful for private mail relays.
For a mailserver receiving inbound external mail from the public internet, we almost certainly want to use STARTTLS on port 25. We can do that with the following listen directive, obviously substituting if0 for the correct network interface:
listen on if0 tls pki example.com
Listening for connections using TLS
The previous example will enable STARTTLS, but it's use will be optional for any mailservers that connect to us. If we want to refuse connections that don't negotiate TLS, and reject unencrypted SMTP sessions, we can do this by replacing the tls option with tls-require:
listen on if0 tls-require pki example.com
Configuring the server to enforce obligatory use of STARTTLS for inbound connections
Slapdash SMTP servers
Servers that don't support STARTTLS
Amazingly, as of the time of writing in 2022, there are still active internet mailservers that do not support the use of STARTTLS.
Shame on those organisations who refuse to follow good industry practices!
Configuring our server to reject plain text mail submission will cause inbound mail from such servers to be rejected, and most likely bounced back to the sender. This seems like a useful feature to us here at Exotic Silicon, and we have run our own inbound mail servers with this configuration for some time now.
We can also add the verify option to require a valid client certificate:
listen on if0 tls-require verify pki example.com
Requiring a client certificate
This will break almost all inbound mail delivery from public internet servers, as most of them won't present a client certificate at all. It's mostly useful on private networks where all of the inbound mail is coming from known servers, or on ‘smarthost’ mail relays where you want an additional layer of authentication beyond IP address filtering and SMTPAUTH.
Note that by default, the option just described will allow any valid client certificate. If we want to restrict the client certificates that will be accepted to those that have been signed by our internal CA, we can do this using the ca directive:
listen on if0 tls-require verify ca our_ca pki example.com
Only accept certificates that have been signed by our own CA signing certificate
To present a client certificate during outbound mail sessions using TLS, we need to specify the pki option to the action directive:
action "outbound" relay pki example.com
Present a client certificate on outbound connections
Obviously, this would usually be used when relaying outbound mail via a specific dedicated ‘smarthost’ mailserver:
action "outbound" relay host smtp+tls://smtp.example.com pki example.com
Present a client certificate on outbound connections that use the smarthost smtp.example.com
Of course, the communication with the smarthost could also be configured to use SMTPS on port 465 instead of STARTTLS, as could any SMTP delivery on private networks.
Handy hint!
Using verify with SMTPS
In the manual page for smtpd.conf, the use of the verify option within a listen directive is only discussed in conjunction with the tls-require option. However, verify can also be used in conjunction with SMTPS, so the following example works as expected:
action "outbound" relay host smtps://smtps.example.com verify pki example.com
Using our new keypair with iked
Although iked can be set up to use certificates for authentication, we can also use the public and private keypairs directly...
For simple applications and testing purposes, using the keys directly is quite convenient.
The path for the private key is hard-coded into iked as /etc/iked/private/local.key, so we need to copy our private key to this location:
# cp example.key /etc/iked/private/local.key
Installing the local private key for use with iked
Alternatively, we could change the define IKED_PRIVKEY in /usr/src/sbin/iked/types.h and re-compile iked, if we really wanted to store the key elsewhere.
The public keys for the peers are stored in subdirectories of /etc/iked/pubkeys/, (again a hard-coded path in iked). The keys that we place here are the ones that were generated on the corresponding peer, using a command such as:
# openssl ec -in example.key -pubout -out example.pub For ECDSA keys
# openssl pkey -pubout -in example.key -out example.pub For RSA keys
Deriving a public key from it's corresponding private key
The keys should be named according to the identification of the remote peer, which by default is it's hostname, without including a trailing .pub extension.
Using certificates instead of keys with iked
Of course, just because we can configure iked to use keys instead of certificates doesn't mean that we want to...
Using certificates has various advantages from an administrative point of view - it's often somewhat easier to simply generate a new certificate as and when required rather than create a new key pair and then have to manually copy the public key to the remote peer.
Although OpenBSD includes a command ikectl that can be used to create the necessary keys and certificates for use with iked, we'll learn a lot more by going through the process manually.
If you do want to use ikectl, the necessary commands are documented in it's manual page. Be aware, though, that by default it generates 2048-bit RSA keys. By following the manual steps below, we can create and use ECDSA keys instead.
For a VPN with two peers, we need to create three certificates. First, we generate a CA certificate, and then we generate a regular non-CA certificate for each peer, which is signed by the new CA certificate.
The CA certificate for the VPN needs to have the critical basic constraint set, as well as CA:true. It also requires some key usage options to be set:
# echo "basicConstraints=critical,CA:true" > ca.ext
# echo "keyUsage=digitalSignature,keyCertSign,cRLSign" >> ca.ext
Setting options for the CA certificate in it's external options file
The CA certificate does not require the SAN field to be present.
The distinguished name doesn't really matter either, so we could, for example, just define the common name as ‘our_certificate_authority’.
With these options in place, we can create the CA certificate in the usual way:
# openssl x509 -sha256 -req -days 365 -in ca.csr -signkey ca.key -extfile ca.ext -out ca.ssc
Creating our CA signing certificate
This certificate needs to be copied to /etc/iked/ca/ on each peer. The filename doesn't matter, as iked will load and parse all of the certificates in that directory.
We can also check that the required options are present by viewing the certificate in text form:
# cp ca.ssc /etc/iked/ca/
# openssl x509 -noout -text -in /etc/iked/ca/ca.ssc
X509v3 extensions:
X509v3 Basic Constraints: critical
X509v3 Key Usage:
Digital Signature, Certificate Sign, CRL Sign
Options that should be set in our CA signing certificate
For each peer's regular certificate, we include a SAN field, but also key usage and extended key usage fields:
# echo "subjectAltName=DNS:example.com" > example.com.ext
# echo "basicConstraints=CA:false" >> example.com.ext
# echo "keyUsage=digitalSignature,keyCertSign,cRLSign" >> example.com.ext
# echo "extendedKeyUsage=serverAuth,clientAuth" >> example.com.ext
Setting options for the regular certificates in their external options files
The certificates are then generated in the usual way, and copied to /etc/iked/certs/ on the corresponding peer.
Using iked with certificates - a specific example
Let's imagine that we have two machines, called one.lan and two.lan, between which we want to create a VPN using iked, authenticated using certificates.
The following simple script will create all of the keys and certificates on one.lan, install them locally, and then copy the required ones to two.lan:
# Create and install a CA certificate along with two regular certificates, for use with iked
# Create a temporary working directory under /root to ensure 700 permissions:
rm -rf /root/iked_certs
mkdir /root/iked_certs
cd /root/iked_certs
# Write elliptic curve parameters for key generation to a temporary file:
openssl ecparam -out ec-secp384r1.pem -name secp384r1
# Create public and private keypair for CA:
openssl genpkey -paramfile ec-secp384r1.pem -out ca.key
openssl ec -in ca.key -pubout -out ca.pub
# Interactively prompt for Distinguished Name parameters to create CA certificate signing request:
echo CSR for CA:
openssl req -key ca.key -new -out ca.csr
# Write extension options to a temporary file:
echo "basicConstraints=critical,CA:true" > ca.ext
echo "keyUsage=digitalSignature,keyCertSign,cRLSign" >> ca.ext
# Create the CA certificate in ca.ssc:
openssl x509 -sha256 -req -days 365 -in ca.csr -signkey ca.key -extfile ca.ext -out ca.ssc
# Display the generated certificate in text format:
openssl x509 -text -noout -in ca.ssc
# Create public and private keypair for one.lan, (reusing the EC parameter file):
openssl genpkey -paramfile ec-secp384r1.pem -out one.lan.key
openssl ec -in one.lan.key -pubout -out one.lan.pub
# Interactively prompt for DN parameters to create CSR for one.lan:
echo CSR for one.lan
openssl req -key one.lan.key -new -out one.lan.csr
# Write extension options to a temporary file:
echo "subjectAltName=DNS:one.lan" > one.lan.ext
echo "basicConstraints=CA:false" >> one.lan.ext
echo "keyUsage=digitalSignature,keyCertSign,cRLSign" >> one.lan.ext
echo "extendedKeyUsage=serverAuth,clientAuth" >> one.lan.ext
# Create public and private keypair for two.lan, (reusing the EC parameter file):
openssl genpkey -paramfile ec-secp384r1.pem -out two.lan.key
openssl ec -in two.lan.key -pubout -out two.lan.pub
# Interactively prompt for DN parameters to create CSR for one.lan:
echo CSR for two.lan
openssl req -key two.lan.key -new -out two.lan.csr
# Write extension options to a temporary file:
echo "subjectAltName=DNS:two.lan" > two.lan.ext
echo "basicConstraints=CA:false" >> two.lan.ext
echo "keyUsage=digitalSignature,keyCertSign,cRLSign" >> two.lan.ext
echo "extendedKeyUsage=serverAuth,clientAuth" >> two.lan.ext
# Initialize a file containing the certificate serial number to zero:
echo "00" > ca.srl
# Generate CA signed certificates for one.lan and two.lan from the corresponding CSRs:
openssl x509 -CA ca.ssc -CAkey ca.key -CAserial ca.srl -req -in one.lan.csr -extfile one.lan.ext -out one.lan.crt
openssl x509 -CA ca.ssc -CAkey ca.key -CAserial ca.srl -req -in two.lan.csr -extfile two.lan.ext -out two.lan.crt
# Copy the CA certificate to /etc/iked/ca/:
cp ca.ssc /etc/iked/ca/ca.crt
scp ca.ssc root@two.lan:/etc/iked/ca/ca.crt
# Install each machine's local certificate to /etc/iked/certs/:
cp one.lan.crt /etc/iked/certs/
scp two.lan.crt root@two.lan:/etc/iked/certs/
# Install each machine's local private key to /etc/iked/private/local.key:
cp one.lan.key /etc/iked/private/local.key
scp two.lan.key root@two.lan:/etc/iked/private/local.key
# Install each machine's public key to /etc/iked/local.pub:
cp one.lan.pub /etc/iked/local.pub
scp two.lan.pub root@two.lan:/etc/iked/local.pub
Simple shell script
In the example above, we install the public keys for each machine in /etc/iked/local.pub.
This is not strictly necessary when using certificates for authentication. However, having the public key available in the standard location of /etc/iked/local.pub is useful if we ever want to switch to a key-based configuration.
Assuming that we want to configure tunnel mode rather than transport mode, a suitable /etc/iked.conf to allow remote access to a locally running X server might look something like this:
ikev2 active esp proto tcp from host1.lan port 6000 to host2.lan peer two.lan ecdsa384
Sample iked.conf file for use with the certificates generated above
Testing TLS connections with netcat and s_client
If we want to connect to a server using TLS for testing and diagnostic purposes, we can use the s_client option of the openssl command line utility:
# openssl s_client -connect ::1:443
Connecting to a TLS-based server interactively
By default, this will only display the server certificate before leaving us communicating in interactive mode. If we want to see the whole certificate chain, we can use the -showcerts option:
# openssl s_client -showcerts -connect ::1:443
Viewing the full certificate chain
Be aware that s_client interprets certain characters at the beginning of lines as in-band control characters. If you need clean communication with the remote server, one option is to use netcat with the -c parameter:
# nc -c -v ::1 443
Connecting to a TLS-based server interactively using netcat
We can save a copy of the remote certificate locally using the -Z option:
# nc -c -v -Z remote_cert.pem ::1 443
Saving a copy of the remote certificate
This can then be examined using the commands we saw earlier in the ‘converting between formats’ section.
# openssl x509 -in example.ssc -noout -text
Viewing the saved certificate in text format
If we're connecting to a server that is using a self-signed certificate which isn't in our certificate bundle, we'll need to invoke netcat with -Tnoverify, and if we're connecting to a hostname or IP address that is not in the DN of the certificate, we'll need -Tnoname:
# nc -c -v -Tnoverify -Tnoname ::1 443
Bypassing certificate checks for testing and diagnostic purposes
Getting a certificate which has been signed by a recognised CA
The exact procedure for getting a certificate from a recognised CA depends somewhat on the individual CA.
Several popular certificate authorities who issue certificates at no cost make use of the acme protocol. The acme protocol allows us to go through the whole process of making a request, then proving domain ownership, or at least control of a domain, by serving specific content via HTTP for certain paths beginning with /.well-known/acme-challenge/, through to receiving the final certificate.
If we request a certificate using the acme protocol, we don't need to manually create a CSR and send it to the CA.
An acme protocol client is included in the base installation of OpenBSD in the form of /usr/sbin/acme-client, and setting it up is fairly trivial.
First, we create a configuration file /etc/acme-client.conf. This will contain an authority section specifying the parameters for the CA that we want to use, and one or more domain sections specifying the common names of the certificates that we want to have signed.
The authority section is very simple, and in most cases can be copied exactly from one of the entries in the example configuration file found in /etc/examples/acme-client.conf.
If we're only using a single CA and using the default path for the challenge, then we only need to specify two options in each domain section:
domain example.com {
domain key "/etc/ssl/private/example.com.key"
domain full chain certificate "/etc/ssl/example.com.crt"
A very simplistic configuration for acme-client.conf
We also need to configure httpd to serve the contents of /var/www/acme at /.well-known/acme-challenge/, as documented in the manual page for acme-client.
To ensure maximum compatibility with the implementations of the acme protocol by diverse certificate authorities, we need to serve the challenge directory via both plain text HTTP and HTTPS. Using a self-signed certificate for HTTPS communication with an acme server before you have a CA signed certificate is generally fine, but serving exclusively over just one of either HTTP or HTTPS is not guaranteed to work. Even if such a setup works for the initial certificate request, it might not be successful when the time comes to renew the certificate.
Once both acme-client and httpd are correctly configured, and any firewall rules that would have been blocking access to the local webserver have been adjusted, we can invoke acme-client to request a certificate for the newly configured domain:
# acme-client -v example.com
Requesting a certificate from the configured recognised CA
Assuming that there are no errors, the new certificate will be written to the specified location of /etc/ssl/example.com.crt.
If we were previously using a self-signed certificate in httpd, such as example.com.ssc, we'll need to change the path in the ‘tls certificate’ directive to point to example.com.crt instead.
At this point, the CA-signed certificate is ready to be use, and we just need to restart httpd, as well as any other servers such as smtpd that we have configured to use it.
This week we saw how to generate new ECDSA and RSA key pairs, and how to create a certificate signing request, which we could then use to issue a self-signed certificate from our own private certificate authority. Next, we looked at how to use such certificates in httpd, smtpd, and iked. As well as all that, we noted how we could use netcat and s_client to debug connection problems and save a local copy of a remote server's certificate. Finally, we learnt how to get a certificate issued by a globally trusted CA using the acme protocol, so that we could successfully offer encrypted connections to a web or mail server on the public internet.
In part eight, we'll be exploring remote X sessions and explaining how to set them up, as well as having a look at using sndiod to send audio data over the network in real time.