使用openssl在linux上建立CA

前言

最近打算幫內網的域名上個合格的https證書,之前一直是用自簽名的,每次都會有安全性警告,因此就有了這篇記錄。

設定

在Linux上架設Certificate Authority有兩種方式,比較簡單的方式是使用easy-rsa,另一種就是使用openssl做設定,不過這兩種方式的底層都還是openssl。

easy-rsa

easy-rsa建立CA的方式在這裡[1]已經說明的很詳細了,就不再介紹。

openssl

建立CA

首先要先創建用於存放CA的資料夾結構,在這裡用/root/ca作為存放CA的資料夾做示範,接下來的默認工作目錄都是/root/ca

1
2
3
4
5
6
7
# mkdir /root/ca
# cd /root/ca
# mkdir private/ certs/ newcerts/ crl/ requests/
# cp /usr/lib/ssl/openssl.cnf .
# chmod 600 openssl.cnf
# touch index.txt
# echo '01' > serial
SHELL

這樣創建後的結構大概是這樣

1
2
3
4
5
6
7
8
9
10
# tree
.
├── certs
├── crl
├── index.txt
├── newcerts
├── openssl.cnf
├── private
├── requests
└── serial
SHELL
  • 目錄結構說明
    • certs: 存放證書
    • newcerts: 使用openssl以CA簽署完後會將證書放在這,可以手動移至certs
    • private: 存放私鑰
    • crl: 存放crl的發布證書
    • index.txt: CA的datebase
    • serial: 目前證書發行的編號
    • openssl.cnf: 用於配置CA的設定

產生RootCA私鑰

1
# openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -outform PEM -aes256 -out private/cakey.pem
SHELL
  • 參數說明
    • -algorithm RSA: 使用RSA算法
    • -pkeyopt rsa_keygen_bits:4096: 建立4096bit長度的RSA私鑰
    • -outform PEM: 使用PEM格式而非DER
    • -aes256: 使用aes256加密私鑰
    • -out private/cakey.pem: 存放私鑰在private/cakey.pem,不要更改,這是openssl.cnf中預設的值

也可不必再此時產生私鑰,可以使用openssl req一次產生私鑰和公鑰/CSR,不過我個人習慣先產生私鑰,再按照私鑰產生公鑰。

在這裡我使用openssl genpkey生成私鑰,不過在網路上其他的教程有些會使用openssl genrsa生成rsa私鑰,根據stackoverflow的這篇回答[2]genrsa會生成PKCS #1格式的私鑰,genpkey則是生成PKCS #8格式的私鑰,不過我自己測試目前genrsagenpkey都是生成PKCS #8格式的私鑰,所以結果是相同的。

產生RootCA憑證(公鑰)

接下來就是產生RootCA的憑證,產生完成後要複製到客戶端上讓客戶端信任這個RootCA,linux可以參考這篇archwiki[3]操作。


首先要修改複製過來的openssl.cnf,這份設定預設會影響openssl caopenssl req的預設參數,想要了解openssl的設定可以參考官網的文檔[4]

更正或增加內容到openssl.cnf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[ CA_default ]

dir = /root/ca
copy_extensions = copy

[ policy_match ]
countryName = match
stateOrProvinceName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional

[ v3_ca ]
basicConstraints = critical,CA:true
keyUsage = critical, keyCertSign, cRLSign
CONF
  • 說明
    • dir: 指向儲存CA的目錄,也就是/root/ca
    • copy_extensions: 這樣可以在生成CSR時就將extention包進去
    • [ policy_match ]: 這裡是設定申請證書的csr哪些部分要與CA的憑證相同才允許簽發憑證,這裡我改成只要求countryName相同就允許簽發
    • [ v3_ca ]: 這是一個設定x509憑證擴展選項的部分,在前面的[ req ]區段可以看到x509_extensions = v3_ca,這樣在待會使用openssl req -x509產生RootCA的憑證時會就會自動包含這些選項,不用手動加上-extensions v3_ca
    • keyUsage、basicConstraints: 讓憑證可以簽署其他憑證並且可以簽署CRL憑證

產生RootCA憑證

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# openssl req -x509 -new -config openssl.cnf -key private/cakey.pem -out cacert.pem -set_serial 0 -days 3650
Enter pass phrase for private/cakey.pem:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:TW
State or Province Name (full name) [Some-State]:.Taiwan
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:example company
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:example.com
Email Address []:mail@example.com
SHELL
  • 參數說明
    • req -x509 -new: 直接產生憑證,而不是憑證申請要求(CSR)
    • -config openssl.cnf: 使用剛剛設定的的openssl.cnf做設定檔
    • -key private/cakey.pem: 剛剛產生的私鑰
    • -out cacert.pem:存放憑證在``cacert.pem不要更改,這是openssl.cnf`中預設的值
    • -set_serial 0: 序號0的憑證
    • -days 3650: 憑證有效期限3650天

產生中間憑證

常規的RootCA不會直接簽發憑證,而是簽署中間CA的中間憑證,再由中間憑證簽發憑證,不過演示環境比較小,就沒做這邊,有興趣的可以參考這篇文章[5]

CRL (可選)

產生CRL

1
2
3
4
5
6
7
8
9
# # 記錄crl發行的版本
# echo 01 > crlnumber
# # 生成CRL,格式為PEM
# openssl ca -config openssl.cnf -gencrl -keyfile private/cakey.pem -cert cacert.pem -out crl/root.crl.pem
# # 轉換格式為DER,CRL大多為DER格式
# openssl crl -in crl/root.crl.pem -outform DER -out crl/root.crl
# # 建立一個給ca用的網站站點,並複製root.crl到那裡,使用nginx或apache把他呈現出來
# mkdir /var/www/ca
# cp crl/root.crl /var/www/ca/
SHELL

簽署

更改openssl.cnf,讓CA在簽證書時加上CRL的entry

編輯[ usr_cert ]區段,ca的設定x509_extensions = usr_cert讓openssl在簽署證書時會自動帶上[ usr_cert ]裡面設定的擴展

1
2
3
4
5
6
7
8
9
[ usr_cert ]

basicConstraints=CA:FALSE
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
# 上面的都是預設的

# 雖然是http,但是root.crl也是有被RootCA簽署過,可以不被竄改
crlDistributionPoints = URI:http://ca.example.com/root.crl
CONF

OCSP (可選)

生成給ocsp responder用的私鑰,不要加上密碼,不然ocsp responder每次啟動都要輸入密碼

1
# openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -outform PEM -out private/ocsp.key
SHELL

產生ocsp證書的申請

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# openssl req -new -key private/ocsp.key -addext 'extendedKeyUsage = critical, OCSPSigning' -out requests/ocsp.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:TW
State or Province Name (full name) [Some-State]:Taiwan
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:example company
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:ca.example.com
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
SHELL

由CA簽署申請並發下證書,大概會長下面這樣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# openssl ca -in requests/ocsp.csr -config openssl.cnf -out certs/ocsp.pem
Using configuration from openssl.cnf
Enter pass phrase for /root/ca/private/cakey.pem:
Check that the request matches the signature
Signature ok
Certificate Details:
Serial Number: 1 (0x1)
Validity
Not Before: xxxxx
Not After : xxxxx
Subject:
countryName = TW
stateOrProvinceName = Taiwan
organizationName = example company
commonName = ca.example.com
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
X509v3 Subject Key Identifier:
xxxxx
X509v3 Authority Key Identifier:
xxxxx
Certificate is to be certified until xxxxx (365 days)
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
SHELL

證書會輸出到certs/ocsp.pem

啟動ocsp responder

1
# openssl ocsp -index index.txt -CA cacert.pem -rsigner certs/ocsp.pem -rkey private/ocsp.key -port <the port to listen> -text
SHELL

使用systemd service把ocsp responder變成daemon,編輯/etc/systemd/system/ocsp.service

1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=ocsp Server
StartLimitBurst=100

[Service]
Type=simple
ExecStart=/usr/bin/openssl ocsp -index /root/ca/index.txt -CA /root/ca/cacert.pem -rsigner /root/ca/certs/ocsp.pem -rkey /root/ca/private/ocsp.key -port <the port to listen> -text
Restart=always

[Install]
WantedBy=multi-user.target
SERVICE

啟動服務

1
systemd enable --now ocsp.service
SHELL

更改openssl.cnf,讓CA在簽證書時加上ocsp的entry

1
2
3
4
5
6
7
8
9
[ usr_cert ]

basicConstraints=CA:FALSE
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
# 上面的都是預設的

# 可以使用nginx代理
authorityInfoAccess = OCSP;URI:http://ca.example.com/ocsp/
CONF

產生HTTPS憑證

產生私鑰和證書申請可以在其他電腦上完成,像是準備要用到證書的機器上,這樣不會暴露私鑰,只需要將CSR檔傳到CA的機器上就好

產生私鑰

1
# openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -outform PEM -out example.com.key
SHELL

產生證書申請

申請HTTPS的證書,DNS的部分要填正確的域名,可以包括通配符*,在Common Name的部分最好也填正確的域名

1
# openssl req -new -key example.com.key -addext 'subjectAltName=DNS:example.com,DNS:www.example.com' -out example.com.csr
SHELL

一樣也是輸入對應的資訊,可以參考前面ocsp的部分

CA簽署

將CSR上傳到CA的requests資料夾內

1
# openssl ca -in requests/example.com.csr -config openssl.cnf -out certs/example.com.pem
SHELL

證書會輸出到certs/example.com.pem,將這個憑證複製回申請的機器上就完成了

同場加映

撤銷證書

1
# openssl ca -config openssl.cnf -revoke newcerts/02.pem
SHELL

-revoke後面接憑證就ok了

驗證CRL

由於crl是DER格式要加上-crl_download選項,如果是PEM格式的應該就不用

1
# openssl verify -crl_check -crl_download -CAfile cacert.pem verify.pem
SHELL

cacert.pem是CA的憑證,verify.pem是待驗證的憑證

驗證OCSP

1
# openssl ocsp -issuer cacert.pem -cert verify.pem -text -url <ocsp URL>
SHELL

cacert.pem是CA的憑證,verify.pem是待驗證的憑證

參考


使用openssl在linux上建立CA
https://www.zenwen.eu.org/using-openssl-to-create-ca-on-linux/
作者
Zen Wen
發布於
2024年8月26日
許可協議