🐡

証明書を作成してmTLSの環境を作ってみた

2024/08/26に公開

会社で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証明書を作成
ここではサーバー証明書・クライアント証明書それぞれに署名する用

  1. 認証局(CA)RSA秘密鍵の作成
openssl genrsa -out rootCA.key 4096
  1. CAキーを使用して自己署名のCA証明書を生成
openssl req -x509 -new -nodes -key rootCA.key -days 3650 -out rootCA.crt

2. クライアント証明書の作成

  1. クライアント秘密鍵の作成
openssl genrsa -out client.key 4046
  1. クライアント秘密鍵用の証明書署名要求(CSR)を作成
openssl req -new -key client.key -subj '/CN=localclient' -out client.csr
  1. クライアント証明書の作成
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. サーバー証明書の作成

  1. サーバー秘密鍵の作成
openssl genrsa -out server.key 4046
  1. サーバー秘密鍵用の証明書署名要求(CSR)を作成
openssl req -new -key client.key -subj '/CN=localhost' -out server.csr
  1. server.extファイルを作成し下記を保存
server.ext
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
  1. サーバー証明書の作成
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サーバーを用意して試してみた。
※インストールの方法は省く

server.js
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へのアクセス

  1. 検証に成功し接続が確立したら下記が表示される。
---
read R BLOCK
  1. 接続が確立したら、HTTPリクエストを手動で送信(openssl s_client はインタラクティブなシェルのように動作します。ここで、以下のように手動でHTTPリクエストを送信します)
GET /auth HTTP/1.1
Host: localhost

(Enter キーを2回押して空行を挿入)
  1. 下記のようなレスポンスが返ってきていれば成功
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