Create Self-Signed Multi-Domain (SAN) Certificates

TL;DR

The SAN-extension is removed during signing, if not respecified explicitly. To create a private CA with self-signed multi-domain certificats for your development setup, you simply have to:

  1. Run create-ca.sh to generate the root-certificate for your private CA.
  2. Run gencert.sh NAME to generate selfsigned certificates for the CN NAME with an exemplary SAN-extension.

Subject Alternative Name (SAN) And Self-Signed Certificates

Multi-Domain certificates are implemented as a certificate-extension called Subject Alternative Name (SAN). One can simply specify the additional domains (or IP’s) when creating a certificate.

The following example shows the syntax for the keytool-command, that comes with the JDK and is frequently used by Java-programmers to create certificates:

keytool \
 -keystore test.jks -storepass confidential -keypass confidential \
 -genkey -alias test -validity 365 \
 -dname "CN=test,OU=security,O=juplo,L=Juist,ST=Niedersachsen,C=DE" \
 -ext "SAN=DNS:test,DNS:localhost,IP:127.0.0.1"

If you list the content of the newly created keystore with…

keytool -list -v -keystore test.jks

…you should see a section like the following one:

#1: ObjectId: 2.5.29.17 Criticality=false
SubjectAlternativeName [
  DNSName: test
  DNSName: localhost
  IPAddress: 127.0.0.1
]

The certificate is also valid for this additionally specified domains and IP’s.

The problem is, that it is not signed and will not be trusted, unless you publicize it explicitly through a truststore. This is feasible, if you just want to authenticate and encrypt one point-2-point communication. But if more clients and/or servers have to be authenticated to each other, updating and distributing the truststore will soon become hell.

The common solution in this situation is, to create a private CA, that can sign newly created certificates. This way, only the root-certificate of that private CA has to be distributed. Clients, that know the root-certificate of the private CA will automatically trust all certificates, that are signed by that CA.

But unfortunatly, if you sign your certificate, the SAN-extension vanishes: the signed certificate is only valid for the CN. (One may think, that you just have to specify the export of the SAN-extension into the certificate-signing-request – which is not exported by default – but the SAN will still be lost after signing the extended request…)

This removal of the SAN-extension is not a bug, but a feature. A CA has to be in control, which domains and IP’s it signes certificates for. If a client could write arbitrary additional domains in the SAN-extension of his certificate-signing-request, he could fool the CA into signing a certificate for any domain. Hence, all entries in a SAN-extension are removed by default during signing.

This default behavior is very annoying, if you just want to run your own private CA, to authenticate all your services to each other.

In the following sections, I will walk you through a solution to circumvent this pitfall. If you just need a working solution for your development setup, you may skip the explanation and just download the scripts, that combine the presented steps.

Recipe To Create A Private CA With Self-Signed Multi-Domain Certificates

Create And Distribute The Root-Certificate Of The CA

We are using openssl to create the root-certificate of our private CA:

openssl req \
  -new -x509 -subj "/C=DE/ST=Niedersachsen/L=Juist/O=juplo/OU=security/CN=Root-CA" \
  -keyout ca-key -out ca-cert -days 365 -passout pass:extraconfidential

This should create two files:

  • ca-cert, the root-certificate of your CA
  • ca-key, the private key of your CA with the password extraconfidential

Be sure to protect ca-key and its password, because anyone who has access to both of them, can sign certificates in the name of your CA!

To distribute the root-certificate, so that your Java-clients can trust all certificates, that are signed by your CA, you have to import the root-certificate into a truststore and make that truststore available to your Java-clients:

keytool \
  -keystore truststore.jks -storepass confidential \
  -import -alias ca-root -file ca-cert -noprompt

Create A Certificate-Signing-Request For Your Certificat

We are reusing the already created certificate here. If you create a new one, there is no need to specify the SAN-extension, since it will not be exported into the request and this version of the certificate will be overwritten, when the signed certificate is reimported:

keytool \
  -keystore test.jks -storepass confidential \
  -certreq -alias test -file cert-file

This will create the file cert-file, which contains the certificate-signing-request. This file can be deleted, after the certificate is signed (which is done in the next step).

Sign The Request, Adding The Additional Domains In A SAN-Extension

We use openssl x509 to sign the request:

openssl x509 \
  -req -CA ca-cert -CAkey ca-key -in cert-file -out test.pem \
  -days 365 -CAcreateserial -passin pass:extraconfidential \
  -extensions SAN -extfile <(printf "\n[SAN]\nsubjectAltName=DNS:test,DNS:localhost,IP:127.0.0.1")

This can also be done with openssl ca, which has a slightly different and little bit more complicated API. openssl ca is ment to manage a real full-blown CA. But we do not need the extra options and complexity for our simple private CA.

The important part here is all that comes after -extensions SAN. It specifies the Subject-Alternative-Name-section, that we want to include additionally into the signed certificate. Because we are in full control of our private CA, we can specify any domains and/or IP’s here, that we want. The other options are ordinary certificate-signing-stuff, that is already better explained elswhere.

We use a special syntax with the option -extfile, that allows us to specify the contents of a virtual file as part of the command. You can as well write your SAN-extension into a file and hand over the name of that file here, as it is done usually. If you want to specify the same SAN-extension in a file, that file would have to contain:

[SAN]
subjectAltName=DNS:test,DNS:localhost,IP:127.0.0.1

Note, that the name that you give the extension on the command-line with -extension SAN has to match the header in the (virtual) file ([SAN]).

As a result of the command, the file test.pem will be created, which contains the signed x509-certificate. You can disply the contents of that certificate in a human readable form with:

openssl x509 -in test.pem -text

It should display something similar to this example-output

Import The Root-Certificate Of The CA And The Signed Certificate Into The Keystore

If you want your clients, that do only know the root-certificate of your CA, to trust your Java-service, you have to build up a Chain-of-Trust, that leads from the known root-certificate to the signed certificate, that your service uses to authenticate itself. (Note: SSL-encryption always includes the authentication of the service a clients connects to through its certificate!) In our case, that chain only has two entries, because our certificate was directly signed by the root-certificate. Therefore, you have to import the root-certificate (ca-cert) and your signed certificate (test.pem) into a keystore and make that keystore available to the Java-service, in order to enable it to authentificate itself using the signed certificate, when a client connects.

Import the root-certificate of the CA:

keytool \
 -keystore test.jks -storepass confidential \
 -import -alias ca-root -file ca-cert -noprompt

Import the signed certificate (this will overwrite the unsigned version):

keytool \
 -keystore test.jks -storepass confidential \
 -import -alias test -file test.pem

That’s it: we are done!

You can validate the contents of the created keystore with:

keytool \
 -keystore test.jks -storepass confidential \
 -list -v

It should display something similar to this example-output

To authenticate service A against client B you will have to:

  • make the keystore test.jks available to the service A
  • make the truststore truststore.jks available to the client B

If you want, that your clients also authentificate themselfs to your services, so that only clients with a trusted certificate can connect (2-Way-Authentication), client B also needs its own signed certificate to authenticate against service A and service A also needs access to the truststore, to be able to trust that certificate.

Simple Example-Scripts To Create A Private CA And Self-Signed Certificates With SAN-Extension

The following two scripts automate the presented steps and may be useful, when setting up a private CA for Java-development:

  • Run create-ca.sh to create the root-certificate for the CA and import it into a truststore (creates ca-cert and ca-key and the truststore truststore.p12)
  • Run gencert.sh CN to create a certificate for the common name CN, sign it using the private CA (also exemplarily adding alternative names) and building up a valid Chain-of-Trust in a keystore (creates CN.pem and the keystore CN.p12)
  • Global options can be set in the configuration file settings.conf

Read the source for more options…

Differing from the steps shown above, these scripts use the keystore-format PKCS12. This is, because otherwise, keytool is nagging about the non-standard default-format JKS in each and every step.

Note: PKCS12 does not distinguish between a store-password and a key-password. Hence, only a store-passwort is specified in the scripts.

Comments / Questions

  1. Cause Chung says:

    when use keytool -genkey

    -keyalg RSA needs to be added

    otherwise a possible cipher suite problem

Leave a Reply

Your email address will not be published. Required fields are marked *