証明書を作成してmTLSの環境を作ってみた
会社でmTLSの環境を実装する必要があったので、その前段階として証明書の作成(自己証明書)からmTLSの接続確認についての開発環境用メモ
環境
- windows11
- node : v20.12.2
- openssl : OpenSSL 3.3.1 4 Jun 2024 (Library: OpenSSL 3.3.1 4 Jun 2024)
1. 自己CA証明書の作成
下記のコードを実施し親となるCA証明書を作成
ここではサーバー証明書・クライアント証明書それぞれに署名する用
- 認証局(CA)RSA秘密鍵の作成
openssl genrsa -out rootCA.key 4096
- CAキーを使用して自己署名のCA証明書を生成
openssl req -x509 -new -nodes -key rootCA.key -days 3650 -out rootCA.crt
2. クライアント証明書の作成
- クライアント秘密鍵の作成
openssl genrsa -out client.key 4046
- クライアント秘密鍵用の証明書署名要求(CSR)を作成
openssl req -new -key client.key -subj '/CN=localclient' -out client.csr
- クライアント証明書の作成
openssl x509 -req -in client.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -days 365 -out client.crt
clientca.srl は、CA が CSR に署名した際に発行されるシリアル番号が書かれています。このシリアル番号は個々のCAにおいて、署名済み証明書ごとに異なる番号である必要があります。
上記の例では、-CAcreateserial を付与して自動的にシリアル番号を生成しています。任意のシリアル番号を指定するオプションもあります。
3. サーバー証明書の作成
- サーバー秘密鍵の作成
openssl genrsa -out server.key 4046
- サーバー秘密鍵用の証明書署名要求(CSR)を作成
openssl req -new -key client.key -subj '/CN=localhost' -out server.csr
- server.extファイルを作成し下記を保存
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
- サーバー証明書の作成
openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -in server.csr -out server.crt -days 365 -CAcreateserial -extfile server.ext
4. Webサーバーの準備
今回はNode.js + Express でWebサーバーを用意して試してみた。
※インストールの方法は省く
const express = require('express');
const app = express();
const https = require('https');
const fs = require('fs');
const path = require('path');
const SERVER_CERT = path.join('certs', 'server.crt');
const SERVER_KEY = path.join('certs', 'server.key');
const CLIENT_CA = path.join('certs', 'rootCA.crt');
const options = {
cert: fs.readFileSync(SERVER_CERT), // サーバの証明書
key: fs.readFileSync(SERVER_KEY), // サーバの秘密鍵
ca: fs.readFileSync(CLIENT_CA), // クライアント証明書に署名したCAの証明書
rejectUnauthorized: false, // クライアント認証に失敗するとリジェクト
requestCert: true, // クライアント認証を実施
};
app.get('/', (req, res) => {
console.log("Hello")
res.writeHead(200);
res.end('Hello world');
});
app.get('/auth', (req, res) => {
console.log('request get auth');
if (req.client.authorized) {
res.send('Hello, secure world!');
} else {
res.status(401).send('Client authentication failed');
}
});
https.createServer(options, app).listen(8443, () => {
console.log('HTTPS listening on 8443....');
})
rejectUnauthorized: false
としているが、/auth
へのアクセスを確認するためなので実際はrejectUnauthorized: true
が望ましい。
│ server.js
├─certs
│ server.crt
│ server.key
│ rootCA.crt
下記で実行
node server.js
HTTPS listening on 8443....
5.Opensslコマンドを利用したmTLSの確認
サーバーへの接続と証明書の送信
サーバーに対してクライアント証明書を使用して接続し、その結果を確認する
openssl s_client -connect localhost:8443 -cert client.crt -key client.key -CAfile rootCA.crt
各オプションの説明:
- connect localhost:8443: サーバーのホスト名とポート番号を指定します。
- cert client.crt: クライアント証明書を指定します。
- key client.key: クライアント証明書に対応する秘密鍵を指定します。
- CAfile rootCA.crt: サーバーとクライアント証明書を検証するためのCA証明書を指定します。
検証結果の確認
サーバーとの接続が成功し、クライアント証明書の検証が正常に行われた場合、以下のような出力が表示されます。
Verify return code: 0 (ok) が表示されると、クライアント証明書の検証が成功したことを意味します。
CONNECTED(00000003) が表示され、サーバーとの接続が確立されたことを示します。
証明書の詳細情報を確認
接続時に、クライアント証明書やサーバー証明書の詳細情報が出力されます。特に以下の部分に注意してください。
Server certificate: サーバー側の証明書の情報が表示されます。証明書のサブジェクト、発行者、公開鍵の詳細が確認できます。
Client certificate: クライアント証明書の情報が表示されます。サーバーがクライアント証明書を受け取った場合、詳細情報が出力されます。
Handshake: SSL/TLS ハンドシェイクの状況が表示されます。
Verify return code: これは、証明書の検証結果です。0 (ok) であれば、クライアント証明書の検証が正常に行われたことを示します。
証明書チェーンの確認
openssl s_client の出力には、証明書チェーンの検証結果も含まれます。例えば、depth フィールドで証明書チェーンの各段階が表示され、verify return:1 で各段階の検証が成功したことを示します。
CONNECTED(00000003)
depth=1 C = US, O = Example CA, CN = Example Root CA
verify return:1
depth=0 C = US, ST = Example State, L = Example City, O = Example Organization, CN = example.com
verify return:1
---
Certificate chain
0 s:C = US, ST = Example State, L = Example City, O = Example Organization, CN = example.com
i:C = US, O = Example CA, CN = Example Root CA
---
SSL handshake has read 1234 bytes and written 567 bytes
Verification: OK
---
New, TLSv1.3, Cipher is ECDHE-RSA-AES256-GCM-SHA384
Server public key is 2048 bit
Verify return code: 0 (ok)
このような出力が表示されれば、クライアント証明書が正しく検証され、サーバーに接続できていることが確認できます。
検証が失敗した場合
もし検証が失敗した場合は、Verify return code に 0 (ok) 以外の値が表示され、失敗の原因が表示されます。例えば、証明書が失効している場合は verify error:num=10:certificate has expired のように表示されます。
この方法を使って、クライアント証明書がサーバー側で正しく検証されているかを確認することができます。
APIへのアクセス
- 検証に成功し接続が確立したら下記が表示される。
---
read R BLOCK
- 接続が確立したら、HTTPリクエストを手動で送信(openssl s_client はインタラクティブなシェルのように動作します。ここで、以下のように手動でHTTPリクエストを送信します)
GET /auth HTTP/1.1
Host: localhost
(Enter キーを2回押して空行を挿入)
- 下記のようなレスポンスが返ってきていれば成功
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 20
ETag: W/"14-NUGGK+cL/3x3xV8yXRJ4dUG9PK8"
Date: Mon, 26 Aug 2024 08:42:39 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Hello, secure world!
まとめ
この流れでとりあえず自己証明書でのmTLSの接続は確認できた。
curlを利用してみたがうまくいかずopenssl s_clientでの確認になってしまったがなんかうまく動いたのでOK
Discussion