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
Step 2: create a certificate signing request including SAN
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.
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
Step 2: Generate the CA certificate
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):
If you want to include SAN in the signed certificate:
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.
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: