6

ブラウザでテキスト文字列に署名する Javascript があります。Internet Explorer では CAPICOM を使用し、Mozilla ブラウザでは window.crypto を使用します。署名プロセスの後、BASE64 でエンコードされた署名を受け取ります。

HTTPS を使用して、署名とテキスト文字列を PHP アプリケーションで Web サーバーにアップロードします。SSL (HTTPS) からユーザーの証明書を受け取ります。この証明書から、ユーザーの公開鍵を抽出できます。

ここで、署名されたテキスト文字列とユーザーの証明書および公開鍵に対する署名を検証したいと思います。openssl_verify PHP 関数を試してみましたが、成功しませんでした。

私はいつもエラーを受け取ります:

エラー:0408D077:RSA ルーチン:FIPS_RSA_VERIFY:署名の長さが間違っています

  • 私は証明書を持っています。問題ありません。
  • 証明書から公開鍵を抽出しましたが、これも検証済みで問題ありません。
  • 署名があります (BASE64 デコード済み)。

残念ながら、署名を確認できませんか? 現時点ではローカル ネットワークのみにあるため、デモやサンプルを提供することはできません。

4

2 に答える 2

2

OK、ついに回避策を見つけました。

  1. UTF-8 でエンコードされた Web ページがあります (これは、次のいくつかのステップで非常に重要になります)。
  2. 署名を生成します。
    • ユーザーが Mozilla/Firefox/Chrome を使用している場合 - 署名はwindow.crypto. 詳細については、これをお読みください
    • ユーザーが Internet Explorer を使用している場合、署名は CAPICOM (Crypot Application Interface COM オブジェクト) を使用して生成されます。詳細については、MSDNをお読みください -window.cryptoと CAPICOM の両方は、Web を使用して十分に文書化されていません (CAPICOM は Web だけではありません!)
  3. テキスト文字列と署名は、POST リクエストによって Web サーバーに送信されています。
  4. サーバー (Linux、Apache、および PHP) は、署名を検証する必要があります。

今問題:

  1. PHPのopenssl_verify()関数も十分に文書化されておらず、常にゼロを返します - 署名が無効です。
  2. CMD ツール openssl も署名を検証していません。
  3. 私のWebサーバーはSSL証明書認証を必要とするため、ユーザーがログインしているのと同じ証明書でテキスト文字列に署名することを望みました.

それで、回避策は何ですか:

  1. CAPICOM が署名する前に、署名された文字列を UTF-16LE に変換していることがわかりました。残念ながら、Web ブラウザーはテキスト文字列を UTF-8 でエンコードされた Web サーバーに送信します。これは、署名を検証する前に文字列を UTF-8 から UTF-16LE に変換する必要があることを意味します。ただし、これは署名が CAPICOM によって生成された場合にのみ有効です。
  2. openssl CMD ツールは現在正常に動作しています。CMD ツールと PHP 関数の違いは次のとおりです。
    • 署名とソースは文字列として PHP 関数に送信されます。CMD ツールを使用する場合、署名とソースはファイル パスとして送信されます。したがって、PHP 関数の代わりに CMD を使用している場合は、最初に署名とソースをファイルとして保存する必要があります。必要に応じてエンコーディングを変換することを忘れないでください。
    • PHP 関数は、パラメーターとして署名者の公開鍵を受け取ることを想定しています。したがって、この前に、証明書が信頼できる認証局 (CA) によって発行されているかどうかを確認する必要があります。代わりに、CMD ツールは、信頼できる認証局のルート証明書のリストを含むファイルをパラメーターとして受け取ることを想定しています。つまり、検証する前に署名者を確認する必要があります。
  3. 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 つの文字列で区切る方法がわかりません。
  4. Internet Explorer では、CAPICOM を使用して 1 つの証明書オブジェクトのみを署名オブジェクトに送信できるため、リストから証明書を選択するためのダイアログは開きません。ルート証明書のフィンガープリント (ヘッダーとフッターを除く BASE64 65 chr/line エンコード証明書の SHA1 ハッシュ) と証明書のシリアル番号を比較することで、適切な証明書を見つけることができます。よく文書化されているMSDNを読んでください。

今。私のソースコードは次のようになります:

  1. 署名が CAPICOM によって生成された場合window.crypto(署名がどのように生成されたかを Web ブラウザーから追加のパラメーターを受け取ります) CAPICOM が使用されている場合、ソース データを次のように変換します。$_POST['source'] = iconv('UTF-8', 'UTF-16LE', $_POST['source'])
  2. 2 つの一時ファイルを生成します。ファイルの名前は、PHP 関数uniqid()を使用して生成できます。デフォルトの一時ディレクトリは、 sys_get_temp_dir()を使用して見つけることができます。
  3. ソース ファイルは、POST 配列から受け取ったとおりに保存されます。署名が CAPICOM によって生成された場合は、UTF-16LE に変換する必要があります。
  4. 署名は、BASE64 でエンコードされた Web ブラウザーから取得されます。デコードしないでください。新しいファイルに保存し、次のように特別なヘッダーとフッターを追加するだけです:"-----BEGIN PKCS7-----\n".$_POST['signature']."\n-----END PKSC7-----"ヘッダーとフッターは別の行にある必要があることに注意してください。行区切りは、\r\n (ASCII #13#10) の \r (ASCII #13) ではなく、\n (ASCII #10) のみにする必要があります。
  5. すべての信頼できる CA ルート証明書を含むファイルが必要です。このファイルの形式は次のとおりです。
    • ヘッダー「-----BEGIN CERTIFICATE-----」
    • BASE64 でエンコードされた証明書
    • フッター「-----END CERTIFICATE-----」
    • 空行
    • 信頼できる CA ルートが複数ある場合、各証明書はヘッダーで始まり、フッター文字列で終わる必要があります。すべての証明書は単一のファイルに保存されます
  6. 次のパラメーターを使用して CMD ツール openssl を実行します。
    • 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
  7. shell_exec()関数 を使用して PHP からこれを呼び出し、出力を読み取ります。
    • 出力文字列が - で始まる場合、Verification successful署名は OK です
    • 出力文字列が - で始まる場合、Verification failure署名は OK ではありません
    • 出力文字列が異なる場合 - なんらかのエラーが発生しています。エラーの説明は、出力文字列に格納されます。

上記は私にとってはうまくいきます。残念ながら、openssl や CAPICOM などwindow.cryptoは非常に扱いにくく、常に問題が発生する可能性があります。これが誰かを助けることを願っています。

よろしくお願いします

于 2012-10-01T08:51:28.987 に答える
0

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 では、CAPICOM1 つの証明書オブジェクトのみを署名オブジェクトに送信できるため、リストから証明書を選択するためのダイアログは開きません。ルート証明書のフィンガープリント (ヘッダーとフッターを除く BASE64 65 chr/line エンコード証明書の SHA1 ハッシュ) と証明書のシリアル番号を比較することで、適切な証明書を見つけることができます。よく文書化されているMSDNを読んでください。今。私のソースコードは次のようになります:

署名がCAPICOMofによって生成された場合window.crypto(Web ブラウザーから署名の生成方法に関する追加のパラメーターを受け取ります)CAPICOMが使用されている場合、ソース データを次のように変換します。

$_POST['source'] = iconv('UTF-8', 'UTF-16LE', $_POST['source'])

2 つの一時ファイルを生成します。ファイルの名前は、PHP 関数を使用して生成できますuniqid()。デフォルトの一時ディレクトリは、sys_get_temp_dir(). POSTソース ファイルは、アレイから受信したとおりに保存されます。署名が生成された場合は、CAPICOMUTF-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 ではありません。出力文字列が異なる場合 - なんらかのエラーが発生しています。エラーの説明は、出力文字列に格納されます。

上記は私にとってはうまくいきます。残念ながらopensslCAPICOMwindow.cryptoは非常に扱いにくく、常に問題が発生する可能性があります。これが誰かを助けることを願っています。

于 2013-08-12T02:35:19.050 に答える