self signed certificate with custom CA and apache SSLRequire

建立自簽 CA 憑證

先建立一把 CA key

# openssl genrsa -des3 -out rootCA.key 4096

然後產生 root CA 憑證

# openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 18250 -out rootCA.crt

可以進去 /etc/pki/tls/openssl.cnf 編輯預設值,沒編輯也沒關係。我是會編輯下列幾個項目:countryName_default, localityName_default, 0.organizationName_default, organizationalUnitName_default
也會改下列的設定

[ policy_match ]
countryName = match
stateOrProvinceName = optional

再來把建立好的 CA key 與憑證 放到預設的位置,還有初始設定

# cp rootCA.key /etc/pki/CA/private/cakey.pem
# cp rootCA.crt /etc/pki/CA/cacert.pem
# touch /etc/pki/CA/index.txt
# echo ‘1000’ > /etc/pki/CA/serial
# echo “1000” > /etc/pki/CA/crlnumber

在 client 端產生 csr

$ openssl genrsa -out user1.key 2048
$ openssl req -new -key user1.key -out user1.csr

用下列指令簽發 client 端產生的 csr 然後就可以把憑證給使用者

# openssl ca -out user1.crt -infiles user1.csr
# openssl ca -days 720 -out user2.crt -infiles user2.csr

使用者可以用下列指令可以產生 p12

$ openssl pkcs12 -export -out user1.p12 -inkey user1.key -in user1.crt

補充說明,使用者可以透過下列指令產生 ssh keys:

$ openssl pkcs12 -in user1.p12 -clcerts -nodes -nocerts | openssl rsa > ~/.ssh/id_rsa
$ chmod 0600 ~/.ssh/id_rsa
$ ssh-keygen -y -f ~/.ssh/id_rsa >> ~/.ssh/authorized_keys

伺服器端 可以把建立好的 rootCA.crt 加入 /etc/pki/tls/certs/ca-bundle.crt 檔案

# cat rootCA.crt >> /etc/pki/tls/certs/ca-bundle.crt

然後編輯 apache ssl 設定檔案

# vi /etc/httpd/conf.d/ssl.conf


        SSLVerifyClient require
        SSLVerifyDepth 10
        SSLOptions           +FakeBasicAuth
        SSLRequireSSL
        AuthName             "Private Authentication"
        AuthType             Basic
        SSLRequire       %{SSL_CLIENT_S_DN_O}  eq "XXXX" \
                     and %{SSL_CLIENT_S_DN_OU} in {"YYYY", "ZZZZ"}

然後重新啟動網頁伺服器

在 client 端匯入 p12 檔後 開瀏覽器瀏覽 https://xxx.xx/private 就可以用憑證認證使用者了 在網頁端 可以用下列變數取得使用者 CN

$user_cn = $_SERVER[“SSL_CLIENT_S_DN_CN”];

假設日後要 revoke 那要產生 crl.pem

# openssl ca -revoke user1.crt
# openssl ca -gencrl -out /etc/pki/CA/crl.pem
# openssl crl -inform PEM -in /etc/pki/CA/crl.pem -outform DER -out root.crl
# ### cp crl.pem and root.crl to your web server

在預設的openssl.cnf 要改變一下你的 crl 主機位址及路徑

# vi /etc/pki/tls/openssl.cnf


[ usr_cert ]
basicConstraints=CA:FALSE

# This is OK for an SSL server.
# nsCertType                    = server

# For an object signing certificate this would be used.
# nsCertType = objsign

# For normal client use this is typical
# nsCertType = client, email

# and for everything including object signing:
# nsCertType = client, email, objsign

# This is typical in keyUsage for a client certificate.
keyUsage = nonRepudiation, digitalSignature, keyEncipherment

# This will be displayed in Netscape's comment listbox.
nsComment                       = "OpenSSL Generated Certificate"

# PKIX recommendations harmless if included in all certificates.
subjectKeyIdentifier=hash
#authorityKeyIdentifier=keyid,issuer
authorityKeyIdentifier=keyid,issuer:always

crlDistributionPoints = URI:https://crl.liho.tw/root.crl
nsCaRevocationUrl = https://crl.liho.tw/crl.pem

# openssl crl -in crl.pem -text -noout
# openssl crl -inform DER -text -noout -in root.crl

但是crl我不知道要怎麼讓瀏覽器自己知道已經被 revoked 了,後來用下列 php 程式暫時解決了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ssl_client_verify = $_SERVER['SSL_CLIENT_VERIFY'];
 
if ($ssl_client_verify == "SUCCESS") {
    $ssl_serial = $_SERVER['SSL_CLIENT_M_SERIAL'];
    $ssl_crl_contents = file_get_contents("https://crl.liho.tw/root.crl");
    file_put_contents("root.crl", $ssl_crl_contents);
 
    $output = shell_exec('/usr/bin/openssl crl -inform DER -text -noout -in root.crl');
    if (strpos($output, "Serial Number: ".$ssl_serial) !== false) {
        echo "<h1>Your certificate has been revoked!!</h1>\n";
        echo "Please contact your system administrator. Thanks!\n";
        exit();
    } else {
        // do something else XD
    }
}