CertificateGeneratorコンポーネント - ha1t/php-gm-server GitHub Wiki

CertificateGeneratorコンポーネント

CertificateGenerator クラスは、Gemini Protocol サーバーの TLS 通信を実現するための自己署名証明書を自動生成・管理するコンポーネントです。OpenSSL 拡張を使用して RSA 2048 ビット秘密鍵を生成し、365日間有効な自己署名 X.509 証明書を作成します。このページでは、CertificateGenerator の実装詳細、処理フロー、セキュリティ上の考慮事項について説明します。

概要と役割

CertificateGenerator は GeminiServerコンポーネント が TLS/SSL 通信を行うために必要な証明書を生成します。Gemini Protocol は TLS 必須となっており、サーバー起動時にこのコンポーネントにより証明書が自動生成されます。既存の証明書がある場合は再利用され、新規生成時のみ OpenSSL 処理が実行されます。

主な特徴:

  • RSA 2048 ビット暗号化強度
  • 365日間の有効期限
  • PEM 形式での保存(証明書と秘密鍵を同一ファイルに格納)
  • 既存証明書の自動検出と再利用
  • ディレクトリ自動作成機能

クラス構成

CertificateGenerator は単一のクラスで構成され、サーバー起動時に GeminiServer へ証明書パスを供給します。

class CertificateGenerator
{
    private string $certDir;

    public function __construct(string $certDir)
    {
        $this->certDir = $certDir;
    }

    public function generate(string $hostname): string
    {
        // 証明書生成または再利用ロジック
    }
}
  • $certDir (string): 証明書を保存するディレクトリパス。環境変数 GEMINI_CERT_DIR で指定可能
  • generate($hostname) (string): 与えられたホスト名に対する証明書パスを返す。新規生成または既存ファイルの確認を行う

Sources: php-gm-server/src/CertificateGenerator.php:7-14

証明書生成フロー

flowchart TD
    Start[生成処理開始] --> Check{証明書ファイル<br/>存在確認}
    Check -->|存在| Return1[パスを返す<br/>既存証明書再利用]
    Check -->|未作成| DirCheck{certDir<br/>存在確認}
    DirCheck -->|存在| KeyGen[RSA 2048秘密鍵<br/>生成]
    DirCheck -->|未作成| MkDir[ディレクトリ<br/>作成]
    MkDir --> KeyGen
    KeyGen --> DN[識別名DN作成<br/>commonName=ホスト名]
    DN --> CSR[証明書署名要求<br/>CSR生成]
    CSR --> Sign[CSRに署名<br/>365日有効な<br/>自己署名証明書作成]
    Sign --> Export[PEM形式で<br/>エクスポート]
    Export --> Combine[証明書+秘密鍵を<br/>同一ファイルに結合]
    Combine --> Save[PEM ファイル<br/>保存 権限0600]
    Save --> Return2[パスを返す<br/>新規生成]
    Return1 --> End[処理完了]
    Return2 --> End
Loading

Sources: php-gm-server/src/CertificateGenerator.php:16-50

RSA 2048 秘密鍵生成

OpenSSL の openssl_pkey_new() 関数を使用して、2048 ビット長の RSA 秘密鍵を生成します。これにより、現在のセキュリティ標準に基づいた十分な暗号強度が実現されます。

$privateKey = openssl_pkey_new([
    'private_key_bits' => 2048,
    'private_key_type' => OPENSSL_KEYTYPE_RSA,
]);

生成パラメータ:

  • private_key_bits: 2048 ビット
  • private_key_type: OPENSSL_KEYTYPE_RSA(RSA 形式指定)

Sources: php-gm-server/src/CertificateGenerator.php:28-31

証明書署名要求と自己署名証明書生成

識別名(DN)の構成

証明書の識別名は、署名要求および証明書に含まれる識別情報です。このコンポーネントでは、最小限の情報として commonName(一般名)にホスト名を指定します。

$dn = [
    'commonName' => $hostname,
];

Sources: php-gm-server/src/CertificateGenerator.php:33-35

証明書署名要求(CSR)の作成

識別名と秘密鍵から、OpenSSL の openssl_csr_new() 関数で CSR を生成します。

$csr = openssl_csr_new($dn, $privateKey);

自己署名証明書の作成

CSR を秘密鍵で署名し、365日間有効な自己署名 X.509 証明書を生成します。openssl_csr_sign() の第 3 引数に null を指定することで、自己署名(CA 署名ではなく秘密鍵で直接署名)が実現されます。

$cert = openssl_csr_sign($csr, null, $privateKey, 365);

パラメータ:

  • $csr: 証明書署名要求
  • null: CA 証明書不使用(自己署名)
  • $privateKey: 署名に使用する秘密鍵
  • 365: 有効期限(日数)

Sources: php-gm-server/src/CertificateGenerator.php:37-38

PEM形式での保存

OpenSSL エクスポート関数により、証明書と秘密鍵を PEM(Privacy Enhanced Mail)形式でテキスト化します。これらを同一ファイルに結合して保存することで、TLS サーバー設定で単一の証明書ファイルを指定できるようにします。

$certPem = '';
openssl_x509_export($cert, $certPem);

$keyPem = '';
openssl_pkey_export($privateKey, $keyPem);

file_put_contents($pemPath, $certPem . $keyPem);
chmod($pemPath, 0600);

処理詳細:

  1. openssl_x509_export(): X.509 証明書を PEM 形式でエクスポート
  2. openssl_pkey_export(): RSA 秘密鍵を PEM 形式でエクスポート
  3. 文字列結合: 証明書の後に秘密鍵を追記
  4. ファイル保存: file_put_contents() で server.pem に一括保存
  5. 権限設定: chmod(0600) により、所有者のみが読み取り可能に制限

Sources: php-gm-server/src/CertificateGenerator.php:40-47

PEM ファイル形式

server.pem ファイルは以下の構造となります:

-----BEGIN CERTIFICATE-----
[Base64 encoded certificate data]
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
[Base64 encoded private key data]
-----END PRIVATE KEY-----

既存証明書の再利用機構

サーバー再起動時に毎回新しい証明書を生成することを避けるため、既存の server.pem ファイルが存在する場合は、それを再利用します。これにより、TOFU(Trust On First Use)モデルにおいてクライアント側で既に信頼された証明書を保持し続けることができます。

$pemPath = $this->certDir . '/server.pem';

if (file_exists($pemPath)) {
    return $pemPath;
}

再利用の効果:

  • クライアント側での信頼ストアが無効化されない
  • 証明書変更に伴う警告表示の削減
  • 性能向上(OpenSSL 処理の実行回避)

Sources: php-gm-server/src/CertificateGenerator.php:18-22

サーバー起動時の統合

bin/server.php において、CertificateGenerator はサーバー起動直前に呼び出され、TLS 設定に必要な証明書パスを用意します。

$certGenerator = new CertificateGenerator($certDir);
$certPath = $certGenerator->generate($hostname);
echo "Certificate: {$certPath}\n";

$fileHandler = new StaticFileHandler($docRoot);
$server = new GeminiServer($host, $port, $fileHandler, $certPath);
$server->run();

起動フロー:

  1. 環境変数から設定値を取得(デフォルト値あり)
  2. CertificateGenerator インスタンス化
  3. generate() メソッド実行(証明書パス取得)
  4. GeminiServer へ証明書パスを供給
  5. サーバー実行

Sources: php-gm-server/bin/server.php:17-23

環境変数による制御

CertificateGenerator の動作は以下の環境変数で制御されます:

環境変数 デフォルト値 説明
GEMINI_CERT_DIR ../certs 証明書保存ディレクトリ
GEMINI_HOSTNAME localhost 証明書の CN(commonName)

sources: php-gm-server/bin/server.php:14-15

テスト戦略

CertificateGeneratorTest は 2 つのテストケースで証明書生成機能を検証します:

テスト 1: 証明書ファイルの作成確認

public function testGenerateCreatesCertAndKey(): void
{
    $generator = new CertificateGenerator($this->certDir);
    $pemPath = $generator->generate('localhost');

    $this->assertFileExists($pemPath);
    $content = file_get_contents($pemPath);
    $this->assertStringContainsString('-----BEGIN CERTIFICATE-----', $content);
    $this->assertStringContainsString('-----BEGIN PRIVATE KEY-----', $content);
}

検証項目:

  • ファイルの実在確認
  • PEM 形式の証明書マーカー存在
  • PEM 形式の秘密鍵マーカー存在

テスト 2: 既存証明書の再利用確認

public function testGenerateReusesExisting(): void
{
    $generator = new CertificateGenerator($this->certDir);
    $path1 = $generator->generate('localhost');
    $content1 = file_get_contents($path1);

    $path2 = $generator->generate('localhost');
    $content2 = file_get_contents($path2);

    $this->assertSame($content1, $content2);
}

検証項目:

  • 2 回目の呼び出しで同一ファイルが返される
  • ファイル内容が変化しない(再生成されていない)

Sources: php-gm-server/tests/CertificateGeneratorTest.php:28-49

セキュリティ考慮事項

秘密鍵の保護

PEM ファイルは chmod(0600) により、所有者のみが読み取り可能な権限で保存されます。これにより、他のユーザーやプロセスからの秘密鍵アクセスを防止します。

自己署名証明書と TOFU モデル

Gemini Protocol では TOFU(Trust On First Use)と呼ばれる信頼モデルを採用しており、クライアント側で最初に接続したサーバーの証明書をフィンガープリント等で記録します。その後、同じホストに接続する際に証明書が一致していることを確認します。

既存証明書の再利用により、クライアント側のフィンガープリント記録が無効化されず、信頼関係が保持されます。

Sources: php-gm-server/src/CertificateGenerator.php:20-21, 47

TLS サーバーへの統合

GeminiServer クラスは SecureServer を用いて TLS 層を構築し、CertificateGenerator が生成した server.pem ファイルを指定します。

$server = new SecureServer($tcp, $loop, [
    'local_cert' => $this->certPath,
    'allow_self_signed' => true,
    'verify_peer' => false,
]);

設定の意味:

  • local_cert: TLS 通信に使用する証明書ファイルパス
  • allow_self_signed: 自己署名証明書の受け入れ(クライアント側での検証に依存)
  • verify_peer: ピア(クライアント)証明書の検証を実施しない

Sources: php-gm-server/src/GeminiServer.php:36-40

ディレクトリ構成

certs/
  └── server.pem    # 自動生成される証明書+秘密鍵ファイル

証明書ディレクトリは GEMINI_CERT_DIR で指定可能であり、デフォルトはプロジェクト直下の certs/ です。ディレクトリが存在しない場合は、CertificateGenerator が自動作成します。

Sources: php-gm-server/src/CertificateGenerator.php:24-26

Related Pages

⚠️ **GitHub.com Fallback** ⚠️