目次


OpenSSL API によるセキュア・プログラミング

第 2 回 セキュアなハンドシェーク

中間者 (MITM) 攻撃を回避する

Comments

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: OpenSSL API によるセキュア・プログラミング

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:OpenSSL API によるセキュア・プログラミング

このシリーズの続きに乞うご期待。

それほど遠くない昔、力強い握手 (handshake) は、二者間のビジネスがしっかりした基盤の上に築かれることを示す証しであり、握手はつまるところ、潜在的なパートナーを面と向かって品定めする機会でした。力強く確信を持った握手は、取引を行う双方が、その取引が最終的に利益をもたらすと確信していることを意味します。心もとない握手は、どちらか一方がその取引について懸念している証しです。

握手 (handshake) つまりハンドシェークは、オンライン・トランザクションでも同じような働きをします。

developerWorks の前回の記事「OpenSSL API によるセキュア・プログラミング 第 1 回: API の概要」では、OpenSSL を使用して、基本となる単純なセキュア接続を作成する方法を説明しました。しかし第 1 回ではデフォルト設定に関連する基礎に焦点を絞り、カスタマイズについては取り上げませんでした。そうは言っても、この記事を完全に理解するには第 1 回を読むことをお勧めします。デジタル証明書の概念、そして OpenSSL による証明書の内部検証が成功したか失敗したかを判別する方法については、第 1 回で説明しているためです。

今回の記事では OpenSSL をさらに掘り下げて検討し、「中間者 (Man In The Middle: MITM)」攻撃からハンドシェークを保護する方法を説明します。

デジタル証明書について

この記事の後半で、デジタル証明書の取得および検証について取り上げるので、デジタル証明書の概要とその機能の仕組みについて簡単に説明しておきます。データ暗号化と SSL について十分に理解している方は、このセクションを読み飛ばしても構いません。暗号化および SSL の課題について詳しく学ぶには、記事の終わりにある「参考文献」セクションに記載されている記事やチュートリアルを参照してください。

デジタル証明書の最も単純な形は、「非対称暗号鍵」です。デジタル証明書に関する現行の標準では、非対称暗号鍵と一緒に含める情報を特定しています。標準的なデジタル証明書には、その証明書の所有者の名前 (証明書を Web サーバーで使用する場合は、完全ドメイン名) と連絡先情報が含まれます。そして、証明書の有効期限と、その証明書が改ざんされていないことを検証するために使用するセキュリティー署名があります。

デジタル証明書は、コマンドラインの OpenSSL ツールや、その目的で作成された他のあらゆるツールを使って簡単に作成することができますが、誰もが作成できるデジタル証明書では、信頼基盤が問題になってきます。デジタル証明書は、単なる暗号鍵ではありません。これは、オンライン証明書です。証明書によって、通信相手に対して自分の身元が証明されます。証明書そのものが信頼できるものであることを明らかにするために、デジタル証明書には認証局 (Certificate Authority: CA) による署名が付けられます。

デジタル・セキュリティーの世界では、信頼できる第三者としての役割を認証局が果たします。オンラインの世界で身元を確認するのは困難であるため、その課題を認証局が引き受けるというわけです。認証局は、証明書を購入した者の身元や、証明書への署名料金を支払った者の身元を証明します。したがって、証明書を信頼できるものにするには、ユーザーが認証局を信頼すればよいだけのことです。ユーザーは認証局からの信頼できる証明書を所有して使用することによって、その認証局を信頼していることを示します。有名な認証局には、Verisign と Thawte があります。

証明書のセキュリティーが 1 回でも侵害されると、その証明書は取り消されます。つまり、証明書が失効し、無効であることが宣言されます。証明書が無効であると宣言する際に、認証局がその証明書のコピーを所有している可能性のあるあらゆるユーザーに通知することは現実的ではありません。そこで、認証局は証明書失効リスト (Certificate Revocation List: CRL) を発行します。これにより、デジタル証明書を使用するブラウザーやその他のセキュリティー・アプリケーションは、証明書がその所有者または認証局のいずれかによって取り消されていないことを確認することができます。

証明書が失効しているかどうかは、OCSP プロトコルを使ってチェックすることもできます。OCSP とは Online Certificate Status Protocol の略で、RFC 2560 で定義されています。OpenSSL には OCSP と CRL の両方の機能がありますが、その機能についてはこの記事では取り上げません。デジタル証明書の現行の標準は、RFC 3280 で定義されている X.509 です。

ビジネスを開始するためのハンドシェーク

この記事で焦点とするのは、ハンドシェークにおけるサーバーのデジタル証明書の処理なので、ハンドシェークがどのような動作をするかを詳しく探っていきましょう。SSL の手順について十分に理解している方は、このセクションを読み飛ばしても構いません。

接続での最初のハンドシェークは、基本的に、クライアントがサーバーに「Hello」とメッセージを送ることによって開始されます。「hello」メッセージ (仕様での名称) には、クライアントの以下のセキュリティー・パラメーターが含まれます。

  • SSL バージョン番号
  • ランダムに生成されたデータ
  • 暗号化に関する設定
  • 通信に必要なその他の情報

これに対し、サーバーは独自の「hello」メッセージで応答します。サーバーからの「hello」メッセージに含まれるサーバーのセキュリティー・パラメーターは、クライアントが提供したのと同じタイプの情報です。サーバーは「hello」メッセージで応答すると同時に、サーバーのデジタル証明書を送信します。接続にクライアント認証が適用される場合、サーバーはクライアントの証明書に対する要求も送信します。

サーバーからの「hello」メッセージが受信されると、送信されたデジタル証明書が検証されます。この検証には、証明書が改ざんされていないことを確認するために証明書の各種パラメーターをチェックする作業、そして証明書がその有効期間中に使用されていることをチェックする作業が含まれます。

この段階で行う必要のあるもう 1 つのステップは、証明書に設定されている名前を、接続に使用されたホスト名と照合することです。このステップは SSL 標準には含まれていませんが、中間者 (MITM) 攻撃を防ぐために強く推奨されています。このステップで、証明書が、想定していた送信元から送信されたものであることを検証します。証明書に設定された名前と接続に使用されたホスト名が一致しない場合、証明書には疑いがかかるだけで、無効にはなりません。

クライアントとサーバーによって共有されたランダム・データは、「プリマスター・シークレット」を作成するために使用されます。プリマスター・シークレットは、サーバーとクライアントのみが知っている共有シークレット値であり、そのセッションでのみ使用されます。このシークレット値は、サーバーのデジタル証明書内の公開鍵で暗号化されてサーバーに送信され、すべてが本来あるべき内容であることが検証されます。

サーバーがクライアント認証を要求した場合、クライアントは、ハンドシェーク中にランダムに生成された、サーバーとクライアントしか知らないデータから一方向ハッシュを作成します。そしてそのハッシュにクライアントの秘密鍵を使って署名を付けて、その署名付きデータとデジタル証明書をサーバーに送信します。サーバーはその情報を使用して、クライアントを認証します。

認証に成功した場合、サーバーとクライアントの両方が、互いに共有するランダム・データを何らかのアルゴリズムで実行して、「マスター・シークレット」を作成します。このマスター・シークレットから、クライアントとサーバーは「セッション鍵」を作成します。セッション鍵とは、選択された対称鍵暗号の範囲内でセッションのデータを暗号化するために使用される対称鍵のことです。

クライアントは、サーバーに対してハンドシェークは終了したというメッセージ送信することで、ハンドシェークを終了します。このメッセージは、暗号化された一方向ハッシュ値のセットです。サーバーはこれらの値を検証する一方、クライアントにも同様のメッセージを送信します。クライアントとサーバーの両方が正しいデータであることを検証すると、ハンドシェークが終了されて通信が開始されます。

Man In The Middle (MITM)

Man In The Middle は、米国の子供たちが遊ぶゲームの 1 つですが、一方では公開鍵インフラストラクチャー (Public Key Infrastructures: PKI) で起こり得る深刻な攻撃の名前でもあります。デジタル証明書では、Man In The Middle (MITM) 攻撃 (中間者攻撃) のことを考慮しなければなりません。SSL 接続の背後にあるセキュリティー・パラメーターが何であろうと、この攻撃によって、そのセキュリティー対策の価値がまったくなくなる可能性があるためです。

例えば、Casey と Samantha が SSL を使用して通信するときに、第三者である Isabel がこの 2 人の間のプロキシーとして立ち回り、接続の試行をインターセプトするとします。Isabel は SSL 接続がセットアップされたことを知ると、Samantha に対しては Casey になりすまし、Casey に対しては Samantha になりすまします。中間にいる Isabel は、二人の間の会話のどちら側でもインターセプトすることができます。会話の中で口座番号や個人情報が話されたとすると、Isabel がその情報を使って、なりすまし犯罪を働くことが可能になります。

この問題を防止する手段となるのが、トラスト・チェーンおよび証明書のコモン・ネームです。ハンドシェーク中には証明書が交換されます。証明書の有効性が分析される際には、署名の信頼性もチェックされます。サーバー証明書のコモン・ネームが、証明書の他の部分と併せて検証されるとしたら、中間者攻撃を撃退できるはずです。ただし、完全に撃退できるわけではありません。

例えば、Isabel が Samantha の名前が記載された証明書を持っているとしたらどうでしょうか。Casey のトラスト・モデル内では、この証明書に認証局による署名も付いています。この場合、コモン・ネームをチェックしても、中間者攻撃を防ぐことはできません。証明書とその信頼性は有効であり、名前の裏付けは取れています。そうなると、大きな問題に直面することになります。

けれども、認証局について検討する際には、この問題は棚上げにされます。ほとんどの認証局は、個人の名前を記載したデジタル証明書を発行する前に、全力を尽くしてしてその個人の身元を確認します。このことから、Isabel が有名で評判の高い認証局から Samantha の名前が記載されたデジタル証明書を入手できる可能性はありそうにないためです。

認証局と共謀すれば、このセキュリティー対策は打破しやすくなります。例えば、認証局が存在する会社に Isabel が勤務している場合です (つまり、「内部犯行」の場合です)。このような場合、認証局を担当する部署の何者かが署名鍵を盗んで、ほとんどあらゆる人の名前を勝手に選んで証明書を偽造することができます。署名を作成するには証明書の秘密の部分が使用されますが、ソーシャル・エンジニアリングや他の同様の手口でパスワードが盗まれる可能性も考えられます。

特に「プロキシー・サーバー」に関しては、中間者攻撃が深刻な問題になってきます。目的の宛先へのセキュアな接続が、プロキシー・サーバーという「トンネル」を介さなければならなくなるため、悪意のあるプロキシー・サーバーがいとも簡単に、あらゆる会話を盗聴できてしまいます。悪意のあるプロキシーは、実際にはトンネルを介していない接続でも、トンネルを介しているように見せ掛けることができます。インターネットで利用可能な「匿名プロキシー」サービスを使用する場合には、この点を念頭に置いておくことが重要です。このようなシステムでユーザー名とパスワードを送信するときに、匿名プロキシーをどれだけ信頼できるでしょうか。

その一方、コンピューターおよびデジタル・セキュリティーの領域では、この攻撃に対する厳重な対策はとられていません。中間者攻撃と同様の手口によって、大金を盗まれた家族の例もあります (「参考文献」を参照)。

OpenSSL とデジタル証明書

OpenSSL には、デジタル署名だけに焦点を絞った完全な内部ライブラリーがあります。OpenSSL のソース・コードを入手した場合、このライブラリーのソース・コードは crypto/x509 および crypto/x509v3 ディレクトリーの配下にあります。このソース・コードでは、デジタル証明書を処理するための複数の構造を定義しています。表 1 に、これらの構造を記載します。

表 1. X.509 証明書関連の OpenSSL の構造
構造機能
X509デジタル証明書に関するすべてのデータを格納。
X509_ALGOR証明書で意図されているアルゴリズムを指定。
X509_VAL証明書の有効期間。
X509_PUBKEY証明書の公開鍵アルゴリズム (通常、RSA または DSA)。
X509_SIG証明書のハッシュ署名。
X509_NAME_ENTRY証明書に含まれる各種データ・フィールドのエントリー。
X509_NAME名前エントリーのスタックを格納。

上記に記載されているのは、関連する構造のごく一部です。OpenSSL の内部で使用される X.509 構造の大半は、アプリケーションで使用されることはほとんどありません。上記に記載した構造のうち、この記事で使用しているのは X509 と X509_NAME だけです。

これらの構造をベースに、さまざまな関数がデジタル証明書を扱ったり、処理したりするために使用されます。これらの関数には、適用対象の構造にちなんだ名前が付けられています。例えば、名前が「X509_NAME」で始まる関数は、通常、X509_NAME 構造に適用されます。これらの関数については、この後、必要に応じて紹介します。

独自の信頼できる証明書の提供

証明書が信頼できるものかどうかを検証するには、その前に、セキュア接続用に OpenSSL をセットアップする際に作成される SSL_CTX オブジェクトにデフォルトの信頼できる証明書一式を渡さなければなりません。それにはいくつかの方法がありますが、最も簡単な方法は、これらの証明書をまとめて 1 つの PEM ファイルに保管し、SSL_CTX_load_verify_locations(ctx, file, path); を実行して OpenSSL にロードすることです。ここで、file には 1 つ以上の証明書が含まれる PEM 形式のファイルへのパスが指定されます。path に、PEM 形式の 1 つ以上のファイルへのディレクトリー・パスが指定されますが、ファイル名は特定の形式になっていなければなりません。より簡単なのは、信頼できる証明書をまとめて保管した単一の PEM ファイルを指定して、path 引数は NULL にしておくことです (例えば、SSL_CTX_load_verify_locations(ctx, "/path/to/trusted.pem", NULL);)。

信頼できる証明書を追加したり、更新したりするには、それらの証明書がフォルダー内で個々のファイルに分かれているほうが簡単ですが、信頼できる証明書の保証に関して頻繁に証明書を更新することはないはずです。

証明書の検証

接続で通信を続ける前、あるいは証明書を取得する前に、OpenSSL での証明書の内部検証の結果を判別する必要があります。それには、SSL_get_verify_result() を使用します。SSL_get_verify_result() が X509_V_OK 以外のコードを返した場合、それは証明書が無効であることを意味するのかと言えば、必ずしもそうではありません。無効であるかどうかは、戻りコードによります。

通常、戻りコードが X509_V_OK でない場合、証明書に問題があるか、証明書に関連するセキュリティーに問題があります。注意すべき点は、OpenSSL による証明書の検証では行われないセキュリティー・チェックがいくつかあることです。例えば、失効しているかどうかのチェック、証明書のコモン・ネームの検証などは OpenSSL では行われません。

SSL_get_verify_result() の戻りコードについては、OpenSSL マニュアルのアプリケーションのセクションに記載されている verify に説明が記載されています。これらのコードのなかには、未使用として記載されているコードがあり、これらのコードが返されることは決してありません。また、致命的エラーを意味するコードもありますが、それ以外のコードが返された場合は致命的エラーではありません。例えば、信頼できる証明書のストアがまだロードされていないために信頼できるかどうかを検証できないとしたら、通信を続けるかどうかは、完全に開発者次第です。

検証の結果とは関係なく、安全ではない可能性があるセキュリティー・パラメーターで続けるかどうかの決定は、開発者に一任されます。エラーの戻りコードが返される場合は、証明書に潜在的な危険があることを示しています。

ピア証明書の取得

ピア証明書を取得する必要があるのは、ピア証明書をユーザーに表示したい場合、またはホスト名や認証局と照合して検証したい場合です。テスト結果を確認した後に証明書を取得するには、SSL_get_peer_certificate() を呼び出します。この関数は、証明書への X509 ポインターを返し、証明書が存在しない場合には、NULL を返します。

リスト 1. ピア証明書の取得
X509 * peerCertificate;


if(SSL_get_verify_result(ssl) == X509_V_OK)
    peerCertificate = SSL_get_peer_certificate(ssl);
else
{
    /* Handle verification error here */
}

ピア証明書の検証

ハンドシェーク中に提供されるサーバーの証明書には、サーバーのホスト名と一致する名前が含まれていなければなりません。そうでない場合、証明書には疑わしい証明書であるというフラグを立てる必要があります。証明書の信頼性と有効期限は、内部検証プロシージャーですでにチェックされています。その際、証明書が失効していたり、信頼できない署名が含まれていたりすれば、証明書は無効としてマークされます。ただし、証明書の名前とホスト名を照合するチェックは、SSL 標準の一部ではないため、OpenSSL では行いません。

証明書の「名前」とは、実際には証明書の「コモン・ネーム」フィールドのことです。このフィールドを証明書から取得して、ホスト名と照合する必要があります。この 2 つが一致しない場合でも、証明書には疑いがかかるだけで、無効な証明書ということにはなりません。一部の企業 (例えば、Yahoo! など) では、証明書のコモン・ネームは 1 つのホストにのみしか対応していませんが、企業が所有するさまざまなホストで同じ証明書を使用していたりもします。証明書が同じ企業から提供されていることを確実にするための詳細なチェックを導入することもできますが、それが必要かどうかは、その特定のプロジェクトのセキュリティー要件によって決まります。

証明書からコモン・ネームを取得するには、2 つのステップが必要です。

  • 証明書の構造から X509_NAME オブジェクトを取得します。
  • 次に、X509_NAME オブジェクトから名前を取得します。

証明書の X509_NAME 構造を取得するには、X509_get_subject_name() を使用します。この関数は、X509_NAME オブジェクトへのポインターを返します。このオブジェクトから、X509_NAME_get_text_by_NID() を使ってコモン・ネームをストリングに取り込みます (リスト 2 を参照)。

リスト 2. コモン・ネームを取得して検証する
char commonName [512];
X509_NAME * name = X509_get_subject_name(peerCertificate);
X509_NAME_get_text_by_NID(name, NID_commonName, commonName, 512);

/* More in-depth checks of the common name can be used if necessary */

if(stricmp(commonName, hostname) != 0)
{
    /* Handle a suspect certificate here */
}

標準の C ストリング関数、または任意のストリング・ライブラリーを使用して、コモン・ネームとホスト名を比較します。結果が不一致の場合の対処方法は、完全にプロジェクトの要件やユーザーの決定に任されます。詳細なチェックを使用するとしたら、別のストリング・ライブラリーを使用して、複雑さを軽減することをお勧めします。

信頼を得ること

この記事では、侵入者が信頼できる発信元になりすます中間者攻撃からSSL ハンドシェークを守る方法を説明しました。また、デジタル証明書の概念、そして OpenSSL API によるデジタル証明書の処理方法についても紹介しました。

接続に関するセキュリティーはほぼすべて、ハンドシェーク内でセットアップされるため、SSL セッション中のハンドシェークをセキュアにすることが極めて重要であることを忘れないでください。この記事で概説した各ステップに従うことがどれほど重要であるかは、プロジェクトのセキュリティー要件、そして開発者の決定によって決まります。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux, Open source
ArticleID=848034
ArticleTitle=OpenSSL API によるセキュア・プログラミング: 第 2 回 セキュアなハンドシェーク
publish-date=12062012