はじめに
「SSL_get_peer_cert_chain」という名前を聞いて、何をすればよいか迷っていませんか?この記事では、OpenSSLに含まれるこのAPI関数について、専門用語をできるだけ抑えつつ丁寧に解説します。実装者や運用担当、セキュリティに関心のある方を想定し、具体例を交えながらわかりやすく説明します。
本章では本記事の目的と構成をお伝えします。主な内容は次のとおりです。関数の役割と返すデータの意味、返却される証明書チェーンの挙動、未検証と検証済みチェーンの違い、関連APIとの比較、実際の利用シーンと実装時の注意点、バージョン差異や類似実装の紹介です。
証明書チェーンは通信の安全性に直結します。誤った扱いは接続エラーや脆弱性につながるため、知っておくと実装で役立ちます。次章以降で順を追って詳しく見ていきましょう。
SSL_get_peer_cert_chainとは
概要
SSL_get_peer_cert_chainは、OpenSSLを使ったSSL/TLS接続で相手(ピア)が送ってきた証明書の並び(チェーン)を取得するための関数です。戻り値はSTACK_OF(X509)型で、送信された順に証明書が並びます。
返されるものの意味
返されるチェーンは、ピアがハンドシェイク時に送った生のチェーンです。多くの場合、先頭に相手のサーバ証明書(あるいはクライアント証明書)、続いて中間証明書が入ります。NULLが返ることもあり、その場合はチェーンが送られていないか取得できなかったことを示します。
主な使いどころ
ログや独自検査のために、ピアが提示した証明書を確認したいときに使います。例えば、証明書のフィンガープリントを記録したり、カスタムの検証ロジックを適用したりする場面です。
注意点
返されるチェーンはSSL内部が管理する領域を指します。しばらく保存したい場合はコピーを作る必要があります。チェーンが検証済みかどうかはこれだけではわからないため、検証結果は別途確認してください。
返される証明書チェーンの内容と挙動
概要
SSL_get_peer_cert_chainが返すのは、接続相手が送信した証明書チェーン(中間証明書を含む)の集合です。呼び出す側によって実際に返る内容が異なります。
取得される内容と例
- クライアント側で呼び出した場合
-
サーバが送った証明書チェーン全体が取得できます。具体的にはサーバ証明書(葉)と中間CAが含まれることが一般的です。ルートCAは省略される場合があります。
-
サーバ側で呼び出した場合
- クライアントが送信した証明書チェーン(中間証明書群)が取得できますが、クライアント証明書自身は含まれません。このためクライアントの実際の証明書は別途取得する必要があります(例: SSL_get_peer_certificateを使用)。
NULLが返るケース
- クライアント証明書をそもそも送信していない場合
- セッション再利用(セッション復帰)で証明書メッセージが再送されない場合
これらではNULLが返されるので、呼び出し側でその可能性を考慮してください。
補足(実務的な注意)
受け取るチェーンはピアが送信したそのままです。中間が欠けることや順序の揺れがあり得ます。検証や表示を行う前に、チェーンの有無と内容を必ず確認してください。
未検証チェーンと検証済みチェーンの違い
何が返されるか
SSL_get_peer_cert_chain()は、ピア(相手)がTLSハンドシェイクで送ってきた証明書列をそのまま返します。送られてきた順(通常はエンドエンティティ=サーバ証明書が先)で、検証処理は行われていません。ルート証明書が省略されることも多く、信頼性は保証されません。
検証済みチェーンの取得
検証済みのチェーンが必要な場合はSSL_get0_verified_chain()を使います。これはTLSハンドシェイクの検証が成功した後にのみ有効で、ライブラリが実際に検証して受け入れたチェーンを返します。検証に基づく情報なので、信頼判断に使えます。
実務上の注意点
未検証チェーンはデバッグや表示に便利ですが、認可や機密アクセスの判断には使わないでください。検証済みチェーンでも、取得後はSSLオブジェクトの寿命に依存する参照になっていることが多く、長期保存するならコピーを作成してください。メモリ解放はライブラリ側が行うため、アプリ側でfreeしないでください。
使い分けの目安
- ログや診断:未検証チェーン(SSL_get_peer_cert_chain)
- 認可や信頼確認:検証済みチェーン(SSL_get0_verified_chain)、または独自に検証した結果を使用
このように目的に応じて使い分けると安全で確実に扱えます。
返却値と証明書管理時の注意点
戻り値の所有権と参照カウント
SSL_get_peer_cert_chain()はSTACK_OF(X509)へのポインタを返します。返されたポインタはSSLオブジェクトが管理する内部データを指しており、関数呼び出しで参照カウントは増加しません。長期間保持する場合は、X509_up_ref()で個々のX509の参照カウントを増やすか、X509_dup()や独自のコピーを作成してください。
解放と安全な扱い
返されたスタック自体を直接freeしないでください。元のSSLオブジェクトが解放されると、その内部データも無効になります。参照を増やした場合は、対応するX509_free()で参照を減らして解放してください。コピーを作った場合はコピーの破棄も忘れないでください。
セッション再利用時の挙動
セッション再利用(セッションチケットやセッションID)では証明書チェーンを再送しないことがあり、この場合はNULLが返ります。NULLかどうかで判定するより、SSL_session_reused()を使ってセッションが再利用されたか確認するほうが確実です。
実務上の注意点
- 長期保存するなら参照カウントを増やすかコピーを推奨します。
- スレッド間で共有する際は参照管理に注意してください。
- NULLを受け取った場合は、接続の状態や再利用フラグを確認してから処理を分岐してください。
関連APIとの違い
概要
SSL_get_peer_certificate(), SSL_get_peer_cert_chain(), SSL_get0_verified_chain() の違いを分かりやすく示します。用途に応じて使い分けると安全で扱いやすくなります。
返却内容の違い
- SSL_get_peer_certificate(): ピアのエンドエンティティ証明書(サーバ証明書など)だけを返します。チェーンの上位は含みません。簡単に証明書本体だけ確認したいとき向けです。
- SSL_get_peer_cert_chain(): ピアが送ってきた証明書チェーン全体を返します。中間CAまで含むため、チェーン構成を確認したいときに便利です。
- SSL_get0_verified_chain(): 証明書検証が済んだ後に、検証済みのチェーンを返します。検証プロセスで不要な証明書が除外される点が特徴です。
参照管理と所有権
返されるオブジェクトの所有権や参照カウントはAPIで異なります。SSL_get_peer_certificate() は新しい参照を返す実装が多く、解放が必要です。一方、SSL_get_peer_cert_chain() や SSL_get0_verified_chain() は内部構造への参照を返す場合があり、そのまま長く保持すると安全性やメモリ管理で問題が起きます。
使い分けと注意点
- 証明書本体だけ要るときは SSL_get_peer_certificate() を使います。
- 送られたチェーン全体を調べたいときは SSL_get_peer_cert_chain() を使います。
- 実際に検証済みの結果を扱うなら SSL_get0_verified_chain() を優先してください。
参照の寿命と解放タイミングを必ず確認し、ライブラリのドキュメントに従ってください。
主な利用シーンと実装時のポイント
主な利用シーン
- 証明書の内容をログや画面に表示する検査機能(例:組織名や有効期限の確認)。
- 独自の検証ロジックを入れる場合(例:証明書ピンニングで公開鍵を比較)。
- 相互TLSでクライアント証明書を確認する処理。
実装時のポイント
- 取得したチェーンはそのままでは信頼できません。必ず別途検証処理を行ってください。具体的には、信頼できるルートと照合し、有効期間、署名チェーン、ホスト名検証、CRL/OCSPの確認を行います。
- 返されるチェーンの所有権に注意してください。SSLオブジェクトに紐づく参照を返す実装が多く、長期間保持するなら証明書を増参照(X509_up_ref)するかコピーしてください。そうしないとSSL解放時に無効になります。
- サーバ側とクライアント側で挙動が異なります。例えば再接続やセッション再開でチェーンが省略される場合があります。実装ではNULLチェックと要素数チェックを必ず行ってください。
- 順序や含まれる証明書は環境で変わります。必須のチェック(署名継承や有効期限)を実施することで依存を減らせます。
実用例
- 接続時にピンニングで公開鍵を比較して許可/拒否を判断。
- 管理画面で証明書の有効期限を表示し、期限切れ前に通知する。
これらを踏まえて実装すると、安全で堅牢な証明書処理が可能です。
バージョンや類似実装について
概要
OpenSSL以外にも BoringSSL、LibreSSL、GnuTLS などのTLS実装があります。外見上は似たAPIを持つことが多いですが、内部設計やメモリ管理、戻り値の扱いが異なることがあります。
主要な違いの例
- APIの有無や非推奨化:あるバージョンで使えた関数が別実装や新しいバージョンで削除・非推奨になることがあります。
- 所有権とライフサイクル:返された証明書オブジェクトを呼び出し側で解放すべきか、実装側が管理するかで挙動が変わります。
- TLSバージョン依存:TLS1.3での証明書処理は以前と違う場合があり、チェーン取得のタイミングや内容に影響します。
実務上の注意点
- ドキュメントを確認する:使用する実装とバージョンの公式ドキュメントを必ず参照してください。
- 明示的にコピーする:返り値の所有権が不確かな場合は深コピーして管理すると安全です。
- テストを行う:TLS1.2/1.3や異なる実装間で挙動を確認するテストを用意してください。
対応方法(実装のヒント)
- 抽象化レイヤーを作り、実装ごとの差分を隠す。
- 条件付きコンパイルでバージョン差を吸収する。
- 検証API(証明書検証)を優先して使い、未検証チェーンに依存しない設計にする。