# jnsp


Information Security, Software Development and *NIX

Create ED25519 certificates for TLS with OpenSSL

Algorithms designed by Daniel J. Bernstein et al. are currenlty quite popular and were implemented by many applications. X25519 is now the most widely used key exchange mechanism in TLS 1.3 and the curve has been adopted by software packages such as OpenSSH, Signal and many more.

Although ECC is a currently a thing in X.509 / WebPKI, the list of available curves is mostly limited to NIST's P-256, P-384 and P-521 curves. This is because the CA/Browser Forum, an industry consortium of browser vendors and public trust centers, defines only those curves as permitted in their Baseline Requirements. The Baseline Requirements are a set of rules for public trust centers, it is important for the CAs to follow those rules closely, otherwhise they get kicked out of the major root programs and their certificates would no longer be trusted by major browsers. However, private CAs are not subject to those rules and are free to choose whichever curve they want for their certificates.

Since the Snowden revelations in 2013, we know that the NSA has actively manipulated cryptographic standards to incorporate backdoors in crypto algorithms. Since then, cryptographers raised concerns against the NIST curves, mainly because some parameters were chosen without any explanation and due to the fact that the curves were designed by the NSA. Wouldn't it be nice to use ECC with safe curves that everyone trusts?

Fortunately, that is indeed possible. Besides the NIST curves, there are several named curves from different standardization bodies available to choose from. In case you are using OpenSSL, you can output a list of supported curves using the following command:

$ openssl ecparam -list_curves

You might notice that the command won't list any of Bernstein's curves, this is due to the fact that the implementation of ED25519 and ED448 in OpenSSL works slightly different than for other named curves. I won't go into the details here, but both ED25519 and ED448 are supported in OpenSSL 1.1.1 and later versions.

Generate a ED25519 CSR

Alright, let's create a TLS certificate with one of Bernstein's safe curves. We can generate a X.509 certificate using ED25519 (or ED448) as our public-key algorithm by first computing the private key:

$ openssl genpkey -algorithm ED25519 > example.com.key

Then we should create a configuration file for OpenSSL, where we can list all the SANs we want to include in the certificate as well as setting proper key usage bits:

$ cat openssl-25519.cnf
---------------------------------------------------------------
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
C = DE
CN = www.example.com
[v3_req]
keyUsage = keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = www.example.com
DNS.2 = example.com

Afterwards we can create a PKCS#10 (Certificate Signing Request) using the private key and the configuration file:

$ openssl req -new -out example.com.csr -key example.com.key -config openssl-25519.cnf

In my case, the CSR file looks like this:

-----BEGIN CERTIFICATE REQUEST-----
MIIBAzCBtgIBADAnMQswCQYDVQQGEwJERTEYMBYGA1UEAwwPd3d3LmV4YW1wbGUu
Y29tMCowBQYDK2VwAyEAK87g0b8CC1eA5mvKXt9uezZwJYWEyg74Y0xTZEkqCcyg
XDBaBgkqhkiG9w0BCQ4xTTBLMAsGA1UdDwQEAwIEMDATBgNVHSUEDDAKBggrBgEF
BQcDATAnBgNVHREEIDAegg93d3cuZXhhbXBsZS5jb22CC2V4YW1wbGUuY29tMAUG
AytlcANBAHSBX9+RjKgO3MjD72nHdiqmPdotBqF2+0mMxQB2sB3Z9WOCF1M+UvFd
JyTsMetxAQZ2UEYMCqo84oG2CWn6gAY=
-----END CERTIFICATE REQUEST-----

Fortunately, OpenSSL can decode the base64 encoded ASN.1 data structure and output it in a human readable form with the following command:

$ openssl req -in example.com.csr -text -noout
---------------------------------------------------------------
Certificate Request:
  Data:
      Version: 1 (0x0)
      Subject: C = DE, CN = www.example.com
      Subject Public Key Info:
          Public Key Algorithm: ED25519
              ED25519 Public-Key:
              pub:
                  2b:ce:e0:d1:bf:02:0b:57:80:e6:6b:ca:5e:df:6e:
                  7b:36:70:25:85:84:ca:0e:f8:63:4c:53:64:49:2a:
                  09:cc
      Attributes:
      Requested Extensions:
          X509v3 Key Usage:
              Key Encipherment, Data Encipherment
          X509v3 Extended Key Usage:
              TLS Web Server Authentication
          X509v3 Subject Alternative Name:
              DNS:www.example.com, DNS:example.com
  Signature Algorithm: ED25519
       74:81:5f:df:91:8c:a8:0e:dc:c8:c3:ef:69:c7:76:2a:a6:3d:
       da:2d:06:a1:76:fb:49:8c:c5:00:76:b0:1d:d9:f5:63:82:17:
       53:3e:52:f1:5d:27:24:ec:31:eb:71:01:06:76:50:46:0c:0a:
       aa:3c:e2:81:b6:09:69:fa:80:06

Looks good, we successfully created a PKCS#10 CSR with OpenSSL! Now let's get it signed by our CA so we can use it with TLS.

Hint: This obviously won't work with publicly trusted CAs such as Digicert, Sectigo or Let's Encrypt as they have to closely follow the rules defined by the CA/Browser Forum Baseline Requirements which currently only permit P-256, P-384 and P-521. However, when you run your own private CA for home or office use, this should work just fine.

Signing the CSR

For this blog post, we will just issue a self signed certifcate. You can do this with OpenSSL like this:

$ openssl x509 -req -days 700 -in example.com.csr -signkey example.com.key -out example.com.crt

The command will issue a self signed certificate which is valid for 700 days. In my case, the issued certificate looks like this:

-----BEGIN CERTIFICATE-----
MIIBCDCBuwIUGW78zw0OL0GptJi++a91dBa7DsQwBQYDK2VwMCcxCzAJBgNVBAYT
AkRFMRgwFgYDVQQDDA93d3cuZXhhbXBsZS5jb20wHhcNMTkwMzMxMTc1MTIyWhcN
MjEwMjI4MTc1MTIyWjAnMQswCQYDVQQGEwJERTEYMBYGA1UEAwwPd3d3LmV4YW1w
bGUuY29tMCowBQYDK2VwAyEAK87g0b8CC1eA5mvKXt9uezZwJYWEyg74Y0xTZEkq
CcwwBQYDK2VwA0EAIIu/aa3Qtr3IE5to/nvWVY9y3ciwG5DnA70X3ALUhFs+U5aL
tfY8sNT1Ng72ht+UBwByuze20UsL9qMsmknQCA==
-----END CERTIFICATE-----

As with the CSR previously, we can use OpenSSL to pretty-print the information from the certificate:

$ openssl x509 -in example.com.crt -text -noout
---------------------------------------------------------------
Certificate:
  Data:
      Version: 1 (0x0)
      Serial Number:
          19:6e:fc:cf:0d:0e:2f:41:a9:b4:98:be:f9:af:75:74:16:bb:0e:c4
      Signature Algorithm: ED25519
      Issuer: C = DE, CN = www.example.com
      Validity
          Not Before: Mar 31 17:51:22 2019 GMT
          Not After : Feb 28 17:51:22 2021 GMT
      Subject: C = DE, CN = www.example.com
      Subject Public Key Info:
          Public Key Algorithm: ED25519
              ED25519 Public-Key:
              pub:
                  2b:ce:e0:d1:bf:02:0b:57:80:e6:6b:ca:5e:df:6e:
                  7b:36:70:25:85:84:ca:0e:f8:63:4c:53:64:49:2a:
                  09:cc
  Signature Algorithm: ED25519
       20:8b:bf:69:ad:d0:b6:bd:c8:13:9b:68:fe:7b:d6:55:8f:72:
       dd:c8:b0:1b:90:e7:03:bd:17:dc:02:d4:84:5b:3e:53:96:8b:
       b5:f6:3c:b0:d4:f5:36:0e:f6:86:df:94:07:00:72:bb:37:b6:
       d1:4b:0b:f6:a3:2c:9a:49:d0:08

We can now use the certificate with our favorite webserver. If it was built against a recent OpenSSL release, it should be able to work with the certificate just fine.

Unfortunately, none of the major browsers seem to support ED25519 based certificates for TLS as of now. We can however use OpenSSL itself to test the connection and verify that it actually works.

$ openssl s_client -connect example.com:443