OK、ついに回避策を見つけました。
UTF-8 でエンコードされた Web ページがあります (これは次のステップで非常に重要になります)。署名の生成: ユーザーが Mozilla/Firefox/Chrome を使用している場合 - 署名はwindow.crypto
. 詳細については、これをお読みください。ユーザーが Internet Explorer を使用している場合 - 署名はCAPICOM
(Crypto Application Interface COM オブジェクト) を使用して生成されます。詳細については、MSDN を参照してください。window.crypto
またCAPICOM
、Web の使用については十分に文書化されていCAPICOM
ません ( は Web だけではありません!) テキスト文字列と署名は、POST 要求によって Web サーバーに送信されています。サーバー (Linux、Apache、および PHP) は、署名を検証する必要があります。今問題:
PHPのopenssl_verify()
関数も十分に文書化されておらず、常にゼロを返します - 署名が無効です。CMD ツールopenssl
も署名を検証していません。私のWebサーバーはSSL証明書認証を必要とするため、ユーザーがログインしているのと同じ証明書でテキスト文字列に署名することを望みました。回避策は何ですか:
署名する前に、署名された文字列をUTF-16LECAPICOM
に変換していることがわかりました。残念ながら、Web ブラウザーはテキスト文字列をUTF-8でエンコードされた Web サーバーに送信します。これは、署名を検証する前に文字列を UTF-8 から UTF-16LE に変換する必要があることを意味します。ただし、これは署名が によって生成された場合にのみ有効です。CAPICOM
CMD ツールは現在openssl
正常に動作しています。CMD ツールと PHP 関数の違いは次のとおりです。
署名とソースは文字列として PHP 関数に送信されます。CMD ツールを使用する場合、署名とソースはファイル パスとして送信されます。したがって、PHP 関数の代わりに CMD を使用している場合は、最初に署名とソースをファイルとして保存する必要があります。必要に応じてエンコーディングを変換することを忘れないでください。
PHP 関数は、パラメーターとして署名者の公開鍵を受け取ることを想定しています。したがって、この前に、証明書が信頼できる認証局 (CA) によって発行されているかどうかを確認する必要があります。代わりに、CMD ツールは、信頼できる認証局のルート証明書のリストを含むファイルをパラメーターとして受け取ることを想定しています。つまり、検証する前に署名者を確認する必要があります。
Firefox/Mozilla/Chrome ブラウザーでは、署名に使用する証明書を正確にユーザーに制限することはできないようです。ただし、オプションを制限することは可能です。もちろん、これはまったく文書化されていません (または、これに関する適切な情報が見つかりませんでした)。したがって、このsignText()
関数は、信頼できる認証局名である必要がある 3 番目のパラメーターを想定しています。最初に、これはクライアントの証明書の発行者の CN でなければならないと予想しました。しかし、実際にはそうではありません。次のようなカンマで区切られたすべての発行者文字列である必要があります。
C=Country,ST=State,L=Location,O=Organization,CN=CommonName,STREET=Address
openssl
残念ながら、この文字列は、次のような発行者文字列とは少し異なります。
issuer=/streetAddress=Address/CN=CommonName/O=Organization/L=Location/ST=State/C=Country.
Linux で Firefox を使用している場合、この文字列が同じようにフォーマットされているかどうかを確認することは非常に興味深いことです。複数の CA 名を 1 つの文字列で区切る方法がわかりません。Internet Explorer では、CAPICOM
1 つの証明書オブジェクトのみを署名オブジェクトに送信できるため、リストから証明書を選択するためのダイアログは開きません。ルート証明書のフィンガープリント (ヘッダーとフッターを除く BASE64 65 chr/line エンコード証明書の SHA1 ハッシュ) と証明書のシリアル番号を比較することで、適切な証明書を見つけることができます。よく文書化されているMSDNを読んでください。今。私のソースコードは次のようになります:
署名がCAPICOM
ofによって生成された場合window.crypto
(Web ブラウザーから署名の生成方法に関する追加のパラメーターを受け取ります)CAPICOM
が使用されている場合、ソース データを次のように変換します。
$_POST['source'] = iconv('UTF-8', 'UTF-16LE', $_POST['source'])
2 つの一時ファイルを生成します。ファイルの名前は、PHP 関数を使用して生成できますuniqid()
。デフォルトの一時ディレクトリは、sys_get_temp_dir()
. POST
ソース ファイルは、アレイから受信したとおりに保存されます。署名が生成された場合は、CAPICOM
UTF-16LE に変換する必要があります。
署名は、BASE64 でエンコードされた Web ブラウザーから取得されます。デコードしないでください。新しいファイルに保存し、次のような特別なヘッダーとフッターを追加するだけです:
"-----BEGIN PKCS7-----\n".$_POST['signature']."\n-----END PKSC7-----"
ヘッダーとフッターは別の行に配置する必要があることに注意してください。行区切りは、\r (ASCII #13) または \r\n (ASCII #13#10) ではなく、\n (ASCII #10) のみにする必要があります。すべての信頼できる CA ルート証明書を含むファイルが必要です。このファイルの形式は次のとおりです。
A header "-----BEGIN CERTIFICATE-----"
BASE64 encoded certificate
A footer "-----END CERTIFICATE-----"
empty line
信頼できる CA ルートが複数ある場合、各証明書はヘッダーで始まり、フッター文字列で終わる必要があります。すべての証明書は 1 つのファイルに保存されますopenssl
次のパラメーターを使用して CMD ツールを実行します。
smime
- CMD ツールの SMIME 機能を使用するには
-verify
- 検証を行う
-in filepath
- 手順 4 で作成した署名ファイルへのパス
-inform PEM
- 署名の形式は、ヘッダーとフッターを含む BASE64 でエンコードされたファイルです
-binary
- バイナリからテキストへのソースの変換を防ぎます
-content filepath
- 手順 3 で作成したソース ファイルへのパス
-CAfile filepath
- ステップ 5 で作成された信頼できる CA ルート証明書ファイルへのパス
したがって、最終的なコマンドは次のようになります。
openssl smime -verify -in file.pem -inform PEM -binary -content source.txt -CAfile root.pem
関数を使用して PHP からこれを呼び出しshell_exec()
、出力を読み取ります。出力文字列が検証成功で始まる場合、署名は OK です。出力文字列が検証失敗で始まる場合 - 署名は OK ではありません。出力文字列が異なる場合 - なんらかのエラーが発生しています。エラーの説明は、出力文字列に格納されます。
上記は私にとってはうまくいきます。残念ながらopenssl
、CAPICOM
とwindow.crypto
は非常に扱いにくく、常に問題が発生する可能性があります。これが誰かを助けることを願っています。