付録D. 実装に関する注意事項 (Implementation Notes)
本付録では、TLS 1.2を実装するための実用的なアドバイスとベストプラクティスを提供します。
D.1. 乱数生成とシード (Random Number Generation and Seeding)
TLSのセキュリティは、乱数生成の品質に大きく依存します。実装は暗号学的に安全な疑似乱数生成器 (CSPRNG) を使用しなければなりません (MUST)。
D.1.1. 乱数生成要件
- エントロピー源: オペレーティングシステムが提供する暗号品質のエントロピー源を使用する (Unixシステムでは
/dev/urandomなど) - シード: 使用前にPRNGが適切にシードされていることを確認する
- 再シード: エントロピープールを維持するために定期的に再シードする
- 弱い源を避ける: タイムスタンプ、プロセスIDなどの予測可能な値を唯一のエントロピー源として使用しない
D.1.2. 実装推奨事項
/* 悪い実践 - これをしてはいけません */
srand(time(NULL));
random_value = rand();
/* 良い実践 - システム提供のCSPRNGを使用する */
#ifdef _WIN32
CryptGenRandom(...); /* Windows */
#else
/* Unix/Linux */
int fd = open("/dev/urandom", O_RDONLY);
read(fd, random_buffer, length);
close(fd);
#endif
D.2. 証明書と認証 (Certificates and Authentication)
D.2.1. 証明書検証
実装は証明書チェーンを適切に検証しなければなりません (MUST):
-
証明書チェーン検証:
- 各証明書の署名を検証する
- 証明書チェーンが信頼されたルートCAに到達することを確認する
- 証明書の有効期限を確認する
-
ホスト名検証:
- 証明書のCommon Name (CN) またはSubject Alternative Name (SAN) を検証する
- ワイルドカード証明書をサポートする (
*.example.comなど)
-
失効確認:
- 実装は証明書の失効ステータスを確認すべきです (SHOULD)
- CRL (Certificate Revocation List) および/またはOCSP (Online Certificate Status Protocol) をサポートする
D.2.2. 証明書選択
サーバーは以下を行うべきです:
- 複数の証明書をサポートする (RSA、ECDSAなど)
- クライアントの能力に基づいて最適な証明書を選択する
- パフォーマンスとセキュリティのバランスを考慮する
D.3. 暗号スイート (Cipher Suites)
D.3.1. 暗号スイート選択
実装は以下を行うべきです (SHOULD):
- デフォルトで強力な暗号スイートを有効にする
- 既知の弱点がある暗号スイートを無効にする (RC4、DES、エクスポートグレード暗号)
- 優先度順に暗号スイートリストをソートする
D.3.2. 推奨設定
優先順位 (高から低):
- AEAD暗号スイート (GCMモード)
- 前方秘匿性を提供するスイート (DHE/ECDHE)
- AES-256よりAES-128を優先
- SHA-256以上のMAC
- CBCモードを避ける (可能であれば)
設定例:
1. TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
2. TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
3. TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
4. TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
5. TLS_RSA_WITH_AES_256_CBC_SHA256
6. TLS_RSA_WITH_AES_128_CBC_SHA256
D.4. 実装の落とし穴 (Implementation Pitfalls)
D.4.1. タイミング攻撃
問題: パスワード比較とパディング検証のタイミング差が情報を漏らす可能性があります。
解決策: 定数時間比較を使用する:
/* 安全でない比較 */
int compare(const unsigned char *a, const unsigned char *b, size_t len) {
for (size_t i = 0; i < len; i++) {
if (a[i] != b[i])
return 0; /* 早期終了 - タイミング情報を漏らす */
}
return 1;
}
/* 定数時間比較 */
int constant_time_compare(const unsigned char *a, const unsigned char *b,
size_t len) {
unsigned char result = 0;
for (size_t i = 0; i < len; i++) {
result |= a[i] ^ b[i];
}
return result == 0;
}
D.4.2. パディングオラクル攻撃
問題: CBCモードでのパディング検証エラーが悪用される可能性があります。
解決策:
- 定数時間パディング検証を使用する
- すべての復号失敗に対して同じエラーを返す
- AEADモード (GCM) を使用してこの問題を回避することを検討する
D.4.3. バージョンロールバック攻撃
問題: 攻撃者がより弱いプロトコルバージョンの使用を強制しようとする可能性があります。
解決策:
- Finishedメッセージに交渉されたバージョンを含める
- versionフィールドの一貫性を検証する
- SCSV (Signaling Cipher Suite Value) 保護を実装する
D.4.4. 再交渉攻撃
問題: 再交渉がコマンドの挿入やその他の攻撃に使用される可能性があります。
解決策:
- RFC 5746 (再交渉表示拡張) を実装する
- 機密操作中は再交渉を無効にする
- クライアント証明書の変更を適切に処理する
D.4.5. 圧縮攻撃 (CRIME)
問題: TLS圧縮が秘密情報を漏らす可能性があります。
解決策:
- TLSレベルの圧縮を無効にする
- 圧縮が必要な場合は、アプリケーション層で実装する
- 秘密を含むデータの圧縮を避ける
D.4.6. バッファ管理
問題: 不適切なバッファ管理がオーバーフローや情報漏えいにつながる可能性があります。
解決策:
- 常に長さフィールドを確認する
- 安全なメモリ関数を使用する (
memcpy_sなど) - 解放後に機密データをゼロ化する
/* 機密データをクリア */
void secure_zero(void *ptr, size_t len) {
volatile unsigned char *p = ptr;
while (len--)
*p++ = 0;
}
/* 使用後に鍵をクリア */
unsigned char key[32];
/* ... 鍵を使用 ... */
secure_zero(key, sizeof(key));
D.4.7. エラー処理
問題: 詳細なエラーメッセージが実装の詳細を漏らす可能性があります。
解決策:
- 外部エンティティには一般的なエラーを返す
- 詳細なエラーは内部ログにのみ記録する
- タイミング情報の漏えいを避ける
D.5. パフォーマンス最適化
D.5.1. セッション再開
- セッションキャッシュまたはセッションチケットを実装する
- フルハンドシェイクの回数を減らす
- セキュリティとパフォーマンスのバランスを取る
D.5.2. バルク操作
- 暗号化/復号操作をバッチ処理する
- ハードウェアアクセラレーションを使用する (AES-NI、PCLMULQDQなど)
- 非同期I/Oの使用を検討する
D.5.3. 接続プール
- TLS接続を再利用する
- 接続プール管理を実装する
- 合理的なタイムアウト値を設定する
注記: 完全な実装推奨事項と詳細な説明については、RFC 5246付録Dの完全なテキストを参照してください。