Create a multiple domains (SAN) self-signed SSL certificate

Create a multiple domains (SAN) self-signed SSL certificate

This post includes the following content:

  • Create a certificate signing request (CSR)
  • Create your own certificate authority (CA) certificate to self-sign the CSR
  • Test the signed certificate with chrome and curl

Part 1: Create a certificate signing request (CSR)

Step 1: Create a private RSA key

openssl genrsa -out server.key 4096
This command generates server.key

Step 2: create a certificate signing request including SAN

openssl req \
	-new \
	-key server.key \
	-nodes \
	-out server.csr \
	-subj "/C=JP/ST=Tokyo/L=Suginami/O=Example Company, Inc./OU=IT/CN=example.com"
This command reads server.key, generates server.csr

If you want to include Subject Alternate Name (SAN) in the CSR, use the -addext flag. TLDR; SAN is used when you want to sign multiple domain names in the same certificate.

openssl req \
	-new \
	-key server.key \
	-nodes \
	-out server.csr \
	-subj "/C=JP/ST=Tokyo/L=Suginami/O=Example Company, Inc./OU=IT/CN=example.com" \
	-addext "subjectAltName=DNS:example.com\
,DNS:www.example.com\
,DNS:example1.com\
,DNS:www.example1.com"
This command reads server.key, generates server.csr

Where:

  • In C=JP, JP is the country code.
  • In ST=Tokyo, Tokyo is the state name.
  • In L=Suginami, Suginami is the locale/region.
  • In O=Example Company, Inc., Example Company, Inc. is the organization's name.
  • In OU=IT, IT is the organization unit (department) name.
  • In CN=example.com, example.com is the common name, the primary domain name.

You should replace these fields with the appropriate values for your use.

If you want to issue a SAN certificate (certificate shared by multiple domain names), it is worth noting that the SAN extension is not required in the CSR. The certificate issuer takes responsibility for including the requested domain names in the final certificate. Some certificate issuers require CSR to include SAN to avoid additional communication.

Step 3 (optional): verify the CSR

To verify the content of the CSR before sending it to the certificate issuer, use the following command:

# openssl req -in server.csr  -noout -text
Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: C = JP, ST = Tokyo, L = Tokyo, O = "Example Company, Inc.", OU = IT, CN = example.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (4096 bit)
                Modulus:
                    00:9a:4f:02:cb:70:7b:fe:71:bd:c9:54:e2:b7:d0:
                    e7:c4:78:fa:a4:95:41:78:b3:13:d2:d6:c3:8c:82:
                    14:19:35:2a:74:d5:70:5c:cd:c1:93:a4:9e:37:7f:
                    6c:d2:c7:fa:78:fc:79:4d:a2:26:8f:90:c4:22:1e:
                    57:c6:1b:a1:65:b9:98:53:1d:fe:54:40:a7:bd:00:
                    2e:86:30:10:db:bd:ec:2f:ba:21:0c:a9:4e:30:ad:
                    a9:08:fa:81:65:68:e8:e2:42:17:32:a0:8b:8e:5f:
                    3b:d4:41:71:f8:d1:bb:45:fd:f8:5f:e5:30:29:d2:
                    92:e4:e3:73:7b:42:65:6c:51:16:8c:42:ee:26:24:
                    dd:d7:fc:a9:92:81:dd:22:92:77:dd:97:88:9a:40:
                    de:28:31:cb:64:dd:e3:72:6c:53:f4:16:bb:3e:b0:
                    85:cc:9d:4b:b1:49:50:c6:00:b8:e7:c3:ec:82:1d:
                    04:f1:0e:e4:ef:2b:0e:ef:ac:85:85:fa:94:6c:00:
                    c0:7d:1f:f0:b7:7b:ca:dc:d1:a7:86:df:55:53:59:
                    73:b3:18:20:3c:4d:9d:54:cf:3c:89:e4:50:d2:cf:
                    8e:26:8f:76:55:41:8e:93:9a:e4:9e:63:c4:d2:57:
                    87:64:73:52:7d:f9:29:4a:17:9f:70:23:54:a4:3d:
                    85:45:41:b3:62:fc:d0:ac:24:83:f2:d4:06:ed:c4:
                    f8:de:18:7a:39:af:00:c1:cf:f0:b3:d2:19:c9:92:
                    3a:0d:9c:16:8c:f1:d3:79:89:04:75:02:b9:2a:31:
                    0d:9f:59:33:8f:02:dd:f0:b0:66:57:58:1a:a2:4e:
                    a0:33:11:4f:48:07:e3:40:18:86:f4:86:0e:d4:7a:
                    88:53:38:4b:c3:82:88:89:90:ca:1f:08:5f:30:bc:
                    be:12:e5:b0:28:fb:a5:a7:e1:9d:f6:ad:8e:da:13:
                    ce:f7:be:f8:ce:17:ca:f4:a3:3a:5d:dc:49:10:de:
                    bd:7f:cb:f6:3d:60:0b:6d:7b:93:83:5e:b6:2b:62:
                    0b:e1:94:5e:29:f5:e3:b1:1c:2f:ca:0b:b6:ab:86:
                    9e:20:b6:77:4e:27:a3:e0:e8:13:4d:87:df:8a:95:
                    8e:6a:5b:75:4a:c3:09:fa:48:f9:a0:97:ac:bd:71:
                    58:2e:26:07:98:bc:63:58:2a:a1:90:83:e1:a7:d9:
                    df:94:07:d0:be:b3:df:ce:8a:68:11:a7:a3:7c:30:
                    31:bc:32:0d:38:2c:84:f2:a8:1f:a3:85:0c:38:c1:
                    ee:5b:4a:fe:4c:e6:4d:90:5f:81:33:83:c3:91:e6:
                    62:fa:5d:2a:0a:3f:bb:1c:26:06:f5:86:ef:53:13:
                    d2:34:e3
                Exponent: 65537 (0x10001)
        Attributes:
            Requested Extensions:
                X509v3 Subject Alternative Name:
                    DNS:example.com, DNS:www.example.com, DNS:example1.com, DNS:www.example1.com
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        12:de:7d:63:ab:09:1d:5d:30:24:5a:8f:01:f5:99:0e:3d:62:
        34:01:54:5b:91:01:2e:8c:f1:f6:af:d0:5d:7a:5f:40:c4:7f:
        a3:96:ec:99:fe:95:96:f7:74:ee:5a:82:19:23:24:c2:ff:a9:
        28:ac:5f:d1:68:fa:67:af:a3:4a:0b:45:52:79:a2:4c:7b:a6:
        1f:2d:ba:df:8c:e5:5a:d3:90:43:a4:3e:e5:3d:c0:b3:3a:46:
        47:8d:32:82:db:4a:83:0c:07:d9:48:3a:0f:e6:cd:52:b7:ee:
        c8:d7:d4:17:d6:57:7a:0d:71:86:96:ec:7a:99:83:8e:e0:2f:
        e3:0c:28:35:ec:8d:73:9d:82:f2:39:93:5e:5d:c5:97:46:7f:
        fe:29:7d:06:77:a4:fd:bd:b8:95:4c:ab:cc:de:6f:d7:98:75:
        12:f6:5a:91:ba:8c:d3:3a:35:63:22:14:1e:80:a1:8b:b2:a9:
        68:30:7b:08:09:28:ae:9b:63:7c:23:11:f0:b5:2c:ad:f5:b6:
        d5:ca:2f:ba:30:1f:ae:ae:38:3c:89:1f:0d:3c:fc:58:08:b3:
        6c:3e:a6:0a:5c:29:9d:1f:ce:87:ae:10:be:ea:1a:30:ac:58:
        07:d9:74:20:63:61:05:f2:64:9a:dd:90:8b:e9:bc:c2:0c:0f:
        7f:41:e8:7e:a5:eb:b2:04:31:83:46:f7:8b:8a:a9:b9:b4:5b:
        45:23:5c:4f:f8:d0:78:27:08:09:8d:87:54:9e:e3:0b:b0:d5:
        07:4e:19:a7:c9:7e:ca:f8:ce:af:59:e5:48:a4:e4:e9:12:ec:
        9b:c5:6b:58:e9:81:5a:bc:50:24:23:a8:b3:cd:79:4b:19:46:
        94:39:47:90:8f:23:cc:a6:ec:3e:f0:16:83:70:f4:5a:04:d7:
        99:36:9a:f1:36:96:fa:61:fc:a6:6a:eb:02:12:4f:8f:de:1e:
        48:36:54:97:8f:90:fa:b2:76:6c:a2:62:f8:96:eb:e7:01:63:
        6b:65:c2:63:2f:c9:b0:8b:0d:fe:e7:61:65:77:99:15:6b:82:
        e1:23:07:e4:45:b1:5b:8e:d2:cf:2f:6e:9d:f1:0b:8e:84:11:
        ac:89:65:9e:b4:5f:bd:d0:13:9f:d4:74:0b:20:ad:84:18:29:
        69:d3:30:54:00:df:3d:ad:20:5e:dc:42:26:08:ac:f3:3f:bd:
        dc:cc:95:4b:99:cc:98:1e:a0:1a:ec:f2:3b:e6:a7:32:3b:91:
        5d:56:1b:f0:97:22:e7:7a:83:c6:de:75:cb:82:a7:41:56:5d:
        fc:a1:7f:8c:fc:03:21:73:79:26:a6:a1:af:55:f7:c5:3c:ab:
        f7:80:3d:4d:6e:96:52:a8

Part 2: Create your own certificate authority (CA) certificate to self-sign the CSR

Step 1: Generate the CA key

openssl genrsa -out ca.key 4096
This command generates ca.key 

Step 2: Generate the CA certificate

openssl req \
	-new \
	-x509 \
	-nodes \
	-days 36500 \
	-key ca.key \
	-out ca.crt \
	-subj "/C=JP/ST=Tokyo/L=Tokyo/O=Example Company CA/OU=Certificate Authority/CN=ca.example-ca.com"
This command reads ca.key, generates ca.crt

Where C=JP, ST=Tokyo, L=Tokyo, O=Example Company CA, OU=Certificate Authority, CN=ca.example-ca.com have a similar meaning explained in part 1. You should use the appropriate values for your use or omit them if needed.

Step 3: Self-sign the CSR using the created CA

If you do not want to include SAN in the signed certificate (the certificate is used for a single domain name):

openssl x509 \
	-req \
	-in server.csr \
	-CAkey ca.key \
	-CA ca.crt \
	-set_serial -01 \
	-out server.crt \
	-days 36500 \
	-sha256
This command reads ca.key, ca.crt, server.csr, generates server.crt

If you want to include SAN in the signed certificate:

openssl x509 \
	-req \
	-in server.csr \
	-CAkey ca.key \
	-CA ca.crt \
	-set_serial -01 \
	-out server.crt \
	-days 36500 \
	-sha256 \
	-extfile <(printf "subjectAltName=DNS:example.com\
,DNS:www.example.com\
,DNS:example1.com\
,DNS:www.example1.com")
This command reads ca.key, ca.crt, server.csr, generates server.crt

Note that, the being used x509 module requires you explicitly re-include the SAN information in the command line. It completely ignores the SAN information included in the CSR.

Step 4 (optional): simply verify the signed certificate.

openssl verify -CAfile ca.crt server.crt

Part 3: Verify the generated certificate with chrome and curl

Now, you can include server.crt (public) and server.key (private) in your https server configuration, such as nginx. In this post, I skip this step and suppose that you already have a server that serves the issued domain (e.g., www.example.com) via SSL using the signed certificate.

Firstly, for the client to correctly resolve the domain name, edit /etc/hosts to manually assign DNS entries.

# 127.0.0.1 = localhost
127.0.0.1 www.example.com

# or remote server IP address
1.2.3.4 www.example.com
cat /etc/hosts

Test with curl

  • Without specifying the CA cert, curl command should fail.
# curl https://www.example.com/
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.
  • If we specify the CA cert, curl command will succeed.
# curl https://www.example.com/ --cacert ca.crt
<!doctype html>
<html>
  <head>
    <title>Hello nginx</title>
    <meta charset="utf-8" />
  </head>
  <body>
    <h1>
      Hello World!
    </h1>
  </body>
</html>
  • To debug the curl command, include the -iv flag:
# curl https://www.example.com/ --cacert ca.crt -iv
*   Trying 127.0.0.1:443...
* Connected to www.example.com (127.0.0.1) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: ca.crt
*  CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305
* ALPN, server accepted to use h2
* Server certificate:
*  subject: C=JP; ST=Tokyo; L=Tokyo; O=Example Company, Inc.; OU=IT; CN=example.com
*  start date: Aug 31 08:50:59 2022 GMT
*  expire date: Aug  7 08:50:59 2122 GMT
*  subjectAltName: host "www.example.com" matched cert's "www.example.com"
*  issuer: C=JP; ST=Tokyo; L=Tokyo; O=Example Company CA; OU=Certificate Authority; CN=ca.example-ca.com
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fe3c000ca00)
> GET / HTTP/2
> Host: www.example.com
> user-agent: curl/7.79.1
> accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
< HTTP/2 200
HTTP/2 200
< server: nginx
server: nginx
< date: Wed, 31 Aug 2022 09:37:17 GMT
date: Wed, 31 Aug 2022 09:37:17 GMT
< content-type: text/html; charset=utf-8
content-type: text/html; charset=utf-8
< content-length: 3031
content-length: 3031
< vary: Accept-Encoding
vary: Accept-Encoding
< last-modified: Tue, 30 Aug 2022 20:02:00 GMT
last-modified: Tue, 30 Aug 2022 20:02:00 GMT
< etag: "630e6cb8-bd7"
etag: "630e6cb8-bd7"
< accept-ranges: bytes
accept-ranges: bytes

<
* Connection #0 to host wwww.example.com left intact
<!doctype html>
<html>
  <head>
    <title>Hello nginx</title>
    <meta charset="utf-8" />
  </head>
  <body>
    <h1>
      Hello World!
    </h1>
  </body>
</html>

Test with chrome

From chrome, access the URL https://www.example.com. The following error screen will appear.

Click at Certificate is not valid to see the certificate detail.

In the showing dialog, you can choose to trust this certificate in the "When using this certificate" menu.

Source:

Buy Me A Coffee