OpenSSLコマンドの使い方、 TLS/証明書関連ファイルの意味や使い道

# はじめに

TLSを利用した暗号化通信に関わるOpenSSL, 証明書等は現代のITシステムにとっては極めて基礎的な知識であるわけですが、どっこい一度設定してしまうと（誰かにやってもらうと）しばらくそのまま使い続ければ良いので、すぐ忘れてしまいがち、というか正直いまだにどうしても苦手です。そこで実用的なメモを残しておくようにしたいと思います。

# 用語

- 証明書 : Certificate
    - 「私はこういうものです」という身元を明らかにするための証明書
    - 認証局によって署名(Sign)されている
    - 通常公開情報（少なくとも通信相手には提供される）
- 秘密鍵 : Private key
    - 証明書と対になって作られる鍵ファイル
    - 漏洩NGの秘密情報
    - 証明書を公開鍵として暗号化したデータは、この秘密鍵を使って復号できる
- 認証局 : Certificate Authority, CA
    - 証明書を発行する人（組織）
    - 実態は証明書であり、その証明書は上位の認証局によって署名されている
    - 最上位の認証局は自己署名（Self sign）されている（オレオレ証明書）
        - 証明書内のIssuerとSubjectが同一
    - TLSのクライアントはこのCA証明書を持っている必要がある
        - 一般的なCAはOSやブラウザにバンドルされている
        - 組織内部のCAを利用する場合は、そのCA証明書をクライアントに配布する必要がある
- 証明書署名要求 : CSR
    - 認証局に証明書を発行してもらうためのリクエストをファイルにしたもの
        - 証明書の身元情報や、発行対象のドメイン名などの情報を含む
        - 自己証明でない証明書は認証局によって発行される必要がある
- クライアント証明、mTLS (mutual-TLS)
    - クライアントがサーバーの証明書を検証するだけでなく、サーバーもクライアントの証明書を検証する暗号化通信
    - クライアント側でも証明書と秘密鍵のペアが必要
    - サーバー側でもクライアントの証明書を検証するためのCAが必要
- X.509証明書
    - 公開鍵インフラストラクチャ（PKI）において使用される標準的なデジタル証明書の形式
    - *.cer, *.crtなど
- PKCS#12
    - 署名書と秘密鍵を合わせてパッケージングしたファイル
    - *.p12など


# OpenSSL コマンドリファレンス

## 自己証明書を発行する

自己証明書を発行するには`openssl req`コマンド + `-x509`オプションを使います。

```
openssl req \
    -new -x509 -nodes \
    -days 365 \
    -subj '/CN={証明書の持ち主を表すCommon Name}' \
    -keyout {出力秘密鍵ファイル名} \
    -out {出力証明書ファイル名}
```

- openssl req: OpenSSLの証明書署名要求（CSR）を生成するコマンド
- -new: 新しい証明書署名要求（CSR）を生成
- -x509: X.509証明書を生成する。通常、reqコマンドはCSRを生成するために使用されるが、このオプションを使用すると自己署名証明書を直接生成する。
- -nodes: 秘密鍵を暗号化しない（パスフレーズを設定しない）
- -days 365: 証明書の有効期間を365日間に設定
- -subj '/CN={証明書の持ち主を表すCommon Name}': 証明書のサブジェクト（所有者情報）を指定
- -keyout {出力秘密鍵ファイル名}: 生成された秘密鍵を{出力秘密鍵ファイル名}というファイルに出力
- -out {出力証明書ファイル名}: 生成された証明書を{出力証明書ファイル名}というファイルに出力

## 秘密鍵の生成

秘密鍵は他の情報に依存せず、単独で生成できます。

```
openssl genrsa -out {出力秘密鍵ファイル名}
```

## CSRを発行する

証明書を外部の認証局に発行してもらうには、CSRを作成し、それを認証局に渡す必要があります。CSRを作成するには以下のコマンドを使います。

```
openssl req -new -x509 -key {秘密鍵ファイル名} -out {出力CSRファイル名}
```

## CSRを元に証明書を発行する

発行されたCSRに対して、CA証明書およびCAの秘密鍵を使って証明したX.509証明書を作成します。

```
openssl x509 -req \
    -in {CSRファイル名} \
    -CA {CAの証明書} \
    -CAkey {CAの秘密鍵} \
    -CAcreateserial \
    -days 365 \
    -out {出力証明書}
```

## 証明書の内容をデコードする

```
openssl x509 -in {証明書} -text -noout
```

元の証明書は以下のような形式をしています。

```
-----BEGIN CERTIFICATE-----
MIIDGTCCAgGgAwIBAgIURTyhQqQVARCN+H2TPPr4bstXEGIwDQYJKoZIhvcNAQEL
(...略...)
82i0AjxbB7nVeu2BJs4KlV7sXCJ2getlrwGZZFg=
-----END CERTIFICATE-----

```

これをデコードすると以下のようになります。

```
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            45:3c:a1:42:a4:15:01:10:8d:f8:7d:93:3c:fa:f8:6e:cb:57:10:62
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN={発行者名}
        Validity
            Not Before: Nov  3 04:18:17 2024 GMT
            Not After : Nov  3 04:18:17 2025 GMT
        Subject: CN={所有者名}
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:d0:d4:5e:0b:16:77:31:de:7c:58:17:02:8d:20:
                    (...略...)
                    54:0b
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier: 
                15:EE:25:45:06:33:1A:4A:BC:60:33:46:69:81:B1:A3:E4:8D:79:FC
            X509v3 Authority Key Identifier: 
                15:EE:25:45:06:33:1A:4A:BC:60:33:46:69:81:B1:A3:E4:8D:79:FC
            X509v3 Basic Constraints: critical
                CA:TRUE
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        0a:3a:37:f9:30:27:65:29:5e:35:69:0d:1b:c0:02:f3:c9:ee:
        (...略...)
        01:99:64:58
```

## 証明書と秘密鍵からPKCS#12形式に変換する

証明書と秘密鍵からPKCS#12形式のファイルを作成することができます。

```
openssl pkcs12 -export -out cert.p12 -inkey cert.key -in cert.crt
```

p12形式のファイルを作成するにはパスワードが必要になります。


## PKCS#12形式から証明書を抽出する

```
openssl pkcs12 -in cert.p12 -clcerts -nokeys -out cert.crt
```

p12形式のファイルを作成するときに使用したパスワードが求められます。


## Webサイトの証明書を確認する

s_clientコマンドを使ってWebサーバーに接続し、そのサーバー証明書の情報を出力させることができます。

```
openssl s_client -connect qiita.com:443 < /dev/null
```

通常s_clientコマンドはクライアント（標準入力）からのHTTPコマンド入力待ちの状態になるため、/dev/null (EOF)を送ってすぐにコネクションを切っています。

```
openssl s_client -connect qiita.com:443 </dev/null

Connecting to 2600:9000:221b:9600:8:e1de:4d80:93a1
CONNECTED(00000005)
depth=2 C=US, O=Amazon, CN=Amazon Root CA 1
verify return:1
depth=1 C=US, O=Amazon, CN=Amazon RSA 2048 M03
verify return:1
depth=0 CN=qiita.com
verify return:1
---
Certificate chain
 0 s:CN=qiita.com
   i:C=US, O=Amazon, CN=Amazon RSA 2048 M03
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: Oct  3 00:00:00 2024 GMT; NotAfter: Nov  1 23:59:59 2025 GMT
 1 s:C=US, O=Amazon, CN=Amazon RSA 2048 M03
   i:C=US, O=Amazon, CN=Amazon Root CA 1
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: Aug 23 22:26:04 2022 GMT; NotAfter: Aug 23 22:26:04 2030 GMT
 2 s:C=US, O=Amazon, CN=Amazon Root CA 1
   i:C=US, ST=Arizona, L=Scottsdale, O=Starfield Technologies, Inc., CN=Starfield Services Root Certificate Authority - G2
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: May 25 12:00:00 2015 GMT; NotAfter: Dec 31 01:00:00 2037 GMT
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIFyzCCBLOgAwIBAgIQDofstJCHe/M6q04OPz3UmzANBgkqhkiG9w0BAQsFADA8
MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRwwGgYDVQQDExNBbWF6b24g
UlNBIDIwNDggTTAzMB4XDTI0MTAwMzAwMDAwMFoXDTI1MTEwMTIzNTk1OVowFDES
MBAGA1UEAxMJcWlpdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAxw/oUc+orZ8cSrPIg29vRRgNne0LeC/ER6HFeQF+N3f1vh9KZorKWKlmPOEH
aGw7AGJ0cKzfAiE9ljzk3YiNtJMw0UEhpYKJLcw7zPmNklynPeCId3PVc7Uq6KyT
5AWRDr7kljwHeB68EQDsSwgKKWPyQ80254FcTpq/Wo8PqZsat9eGHNZd+3SYFxAT
kyZBgASrdZ2INyvmaYTu7x+EDQVaTQH06jzoR3fOBNrClPjF4GXA+Q7BhEvYQMss
exhY9rp8+Lfwasxl4XB42uLyP0xfcQmGaYnkyG4oB+MMfWoDZN3dLmrl0Rr55HOS
73LawQ1OjlMKv+0rKB6F/oufKwIDAQABo4IC7zCCAuswHwYDVR0jBBgwFoAUVdkY
X9IczAHhWLS+q9lVQgHXLgIwHQYDVR0OBBYEFPntHvaKggaY+1bhid754GO/54u6
MCEGA1UdEQQaMBiCCXFpaXRhLmNvbYILKi5xaWl0YS5jb20wEwYDVR0gBAwwCjAI
BgZngQwBAgEwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr
BgEFBQcDAjA7BgNVHR8ENDAyMDCgLqAshipodHRwOi8vY3JsLnIybTAzLmFtYXpv
bnRydXN0LmNvbS9yMm0wMy5jcmwwdQYIKwYBBQUHAQEEaTBnMC0GCCsGAQUFBzAB
hiFodHRwOi8vb2NzcC5yMm0wMy5hbWF6b250cnVzdC5jb20wNgYIKwYBBQUHMAKG
Kmh0dHA6Ly9jcnQucjJtMDMuYW1hem9udHJ1c3QuY29tL3IybTAzLmNlcjAMBgNV
HRMBAf8EAjAAMIIBfgYKKwYBBAHWeQIEAgSCAW4EggFqAWgAdgAS8U40vVNyTIQG
GcOPP3oT+Oe1YoeInG0wBYTr5YYmOgAAAZJQrdKxAAAEAwBHMEUCIQDAHcj3eg2h
kG2ctfH4xdkrweBDTI7Do11qhdaEistBUwIgEmmEDdMLzLVO8mGcw0H6S1EDmnAN
+tH0Q+uw8+2eRkEAdwB9WR4S4XgqexxhZ3xe/fjQh1wUoE6VnrkDL9kOjC55uAAA
AZJQrdJzAAAEAwBIMEYCIQDvcX0oo5NyeM156T+wfcHVnBTHExixQptSl6TmHB4X
TgIhAISAV2SnPkWC2l16tYIJ1Le/y1sQdAY4HUUYfLlHCkM2AHUA5tIxY0B3jMEQ
QQbXcbnOwdJA9paEhvu6hzId/R43jlAAAAGSUK3ShAAABAMARjBEAiAS3/itPwh8
J4WBYy61hDUPYHksdXdHIkEmoQwrUQvTRQIgVk3+S/v5vEuN5c/SZUP0pePSINw5
AMkIK09Nv+aUHzgwDQYJKoZIhvcNAQELBQADggEBAJvwFGHEKYjSFRTgHe4SexTc
8mQXCZ0dxh39btpsZzDRNCloOzHxyt/hmTmNKYX4eWJ6i2rBR2rkkShqhVUIoc+v
VXepDXuixKy7/4z5kBBN4ITPpNrHWNYFuE39/hy+uKMc43uXeSWtyHs4NDHXJX2F
y61Hz0ck0W+8GQfr8mD+FLG+MQdYPhr80QpC3E0rMy44cuGZDaKUNAuNJGutGFxe
s1RpgPbtqVGhr99ayRl1wE1cNAXVQBaKM2OvtJZ+qU+6HX1xBizjSdf/DF/9SoRw
loao03ROXcASrWW97iG539Aux4G8JMG9gBwRYRHQrcy9jy0s0qVg9fIsFERNSIE=
-----END CERTIFICATE-----
subject=CN=qiita.com
issuer=C=US, O=Amazon, CN=Amazon RSA 2048 M03
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 4337 bytes and written 381 bytes
Verification: OK
---
New, TLSv1.3, Cipher is TLS_AES_128_GCM_SHA256
Server public key is 2048 bit
This TLS version forbids renegotiation.
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---
DONE
```


# 証明書チェーン

証明書ファイル（.crt, .pem）は自分の証明書からIssuerを辿ってルート証明書までを一つのファイル内に列挙していることがあります。通常構造は以下のようになり、

1. サーバーなどの目的の証明書
1. 中間証明書
1. ルート証明書

の順で記載されます。

```
-----BEGIN CERTIFICATE-----
(サーバーなどの目的の証明書)
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
(中間CAの証明書)
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
(ルートCAの証明書)
-----END CERTIFICATE-----
```


# curlコマンドのオプション

証明書をWebサーバーに設定して接続する際、外部から接続確認をする必要があります。curlコマンドで各種証明書を指定できます。

```
curl -v \
    --cacert {CAの証明書} \
    --cert {クライアント証明書(mTLSの場合)} \
    --key {クライアントの秘密鍵(mTLSの場合)} \
    {URL}
```

`-v`オプションを使うことによってTLSのハンドシェイクの様子が表示できます。

# 参考

以下、参考にさせてもらいました。

https://qiita.com/kentarok/items/15c08350274fa5578aaf

https://kazuhira-r.hatenablog.com/entry/2023/09/09/235515

https://qiita.com/TakahikoKawasaki/items/4c35ac38c52978805c69