脆弱性 CVE-2014-2734 の争点について

CVE-2014-2734 として登録されている脆弱性について、「Ruby でも起こりうるのではないか」という報告を受けました。 結論から書くと、以下に記載する詳細な分析の結果、Ruby に脆弱性があるとは考えていません

この脆弱性により、攻撃者は証明書の署名を変更して任意のルート証明書を偽造し、証明書のオリジナルの秘密鍵を攻撃者が選択した秘密鍵に都合よく置き換える可能性があります。

コンセプトの実証

以下は CVE-2014-2734 の分析です。オリジナルの PoC を縮小させることができました。これはコンセプトの実証の本質を捉えていると考えられます。

require 'openssl'

forge_key = OpenSSL::PKey::RSA.new(2048)
raw_certificate = File.read("arbitrary.cer")
cert = OpenSSL::X509::Certificate.new(raw_certificate)
resigned_cert = cert.sign(spoof, OpenSSL::Digest::SHA1.new)

resigned_cert.verify(key) #=> true

X509Certificate#verifytrue を返してくることに驚くかもしれません。 オリジナルの証明書には forge_key の公開鍵とは異なるオリジナルの公開鍵を指すサブジェクト公開鍵情報が含まれている場合があります。 証明書の再署名に使用された公開鍵と秘密鍵のペアは、サブジェクト公開鍵情報で参照されているオリジナルの公開鍵と明らかに一致しなくなりました。 どうして #verify は ` true` を返すのでしょうか?

鍵の検証方法

X509Certificate#verify は OpenSSL のX509_verify 関数を利用しています(内部的には ASN1_item_verify 関数を呼び出しています)。 これらの関数は、提示された公開鍵を指定して署名の有効性を確立します。 ところが、指定された鍵が証明書で参照されているサブジェクト公開鍵と実際に一致するかどうかは検証されません。 これは、このシナリオでは「X509Certificate#verify の期待する振る舞いは true を返すこと」を意味します。 このチェックを省略しても、総体的に X.509 信頼モデルのセキュリティに大きな影響はありません。

RFC 5280 の 4.1.1.3 項は、CA が証明書に含まれる情報の正確さを「証明書の署名を計算すること」で確認すると明記しています。 上記のサンプルコードはこの原則に違反していますが、セキュリティを脅かすものではありません。

潜在的なリスク

2 通り考えられます:

ルート証明書の再署名

ユーザーとして、私たちは無条件にルート証明書を信頼します。 有効なな情報が含まれていない場合でも、公的に認められたルート証明書であるというステータスだけで、それらを元の状態に保つことができます。 たとえば、OpenSSL 自体は同様の理由からデフォルトで自己署名ルート証明書の署名をチェックしません。

参考: X509_V_FLAG_CHECK_SS_SIGNATURE documentation

再署名されたルート証明書は事実上の「自己署名」証明書になります(ただし、サブジェクト公開鍵情報は正しくありません)。 これは正常な自己署名ルート証明書より危険ではありません。 事実、署名がなければ、有効なルート証明書と完全に一致する可能性のある自己署名ルート証明書は誰でも作成できます。 私たちは所有するだけでルート証明書を信頼するため、クライアントの「このルート証明書は信頼する」という積極的な同意がない限り、詐欺まがいな証明書に意味はありません。

中間証明書またはリーフ証明書の再署名

非ルート証明書の再署名もまた X.509 信頼モデルのセキュリティを脅かすものではありません。 通常はこのような種類の証明書をあらかじめ所有していない限り、パス検証手続き中にこのような偽装は検出されます。 ここで、非ルート証明書の署名は、発行する証明書の公開鍵を使用して検証されます。 証明書チェーンのある時点で、偽造は最終的に無効な証明書署名値という形で検出されます。

まとめ

結論として、X509Certificate#verify は期待どおりに動作すると考えています。 私たち以外の誰かも自力で同じ結論に行き着いたため、CVE-2014-2734 に異議を唱え、その取り消しを求めました。 オリジナルのコンセプトの実証は、コメントを含め、完全な分析結果として閲覧することができます。