
数年前、当社はWebベースのシステムに電子署名を埋め込むように設計されたRutoken Plug-in製品を発売しました 。 製品を実際のプロジェクトに統合することで得られた経験に基づいて、開発者はロシア側の暗号化アルゴリズムをサポートするopensslを使用してサーバー側を実装することを好むことが多いことに注意したいと思います。
この記事では、プラグインを使用するための以下のシナリオに基づいて、このような統合の典型的なスキームについて説明します。
- ポータルへの登録(証明書の発行または既存の証明書を使用)
- ポータルでの強力な認証
- CMS形式のデータやファイルの電子署名
- CMS形式のデータおよび/またはファイルの暗号化
これらのシナリオには、クライアントとサーバーの相互作用、JavaScriptでのクライアントスクリプトの記述、および対応するopensslサーバー呼び出しが含まれます。
カットの下の詳細。
一般的な操作
デバイス操作
接続されたデバイスを検索する
クライアントシナリオはすべて、コンピューターに接続されているRootoken USBデバイスの検索から始まります。 この記事では、Rutoken EDSのデバイスに重点を置いています。
var devices = Array(); try { devices = plugin.enumerateDevices(); } catch (error) { console.log(error); }
これは、接続されたデバイスの識別子のリストを返します。 識別子は、デバイスが接続されているスロット番号に関連付けられた番号です。 再度列挙すると、この番号は同じデバイスで異なる場合があります。
Rutoken Plug-inは、コンピューターに接続されているすべてのUSBデバイス(Rutoken EDS、Rutoken PINPad、Rutoken WEB)を定義します。 したがって、次のステップはデバイスのタイプを判別することです。
デバイス情報を取得する
デバイスのタイプを判別するには、TOKEN_INFO_DEVICE_TYPEパラメーターを指定してgetDeviceInfo関数を使用します。 この定数の値は、プラグインオブジェクトに含まれています。
var type; try { type = plugin.getDeviceInfo(deviceId, plugin.TOKEN_INFO_DEVICE_TYPE); } catch (error) { console.log(error); } switch (type) { case plugin.TOKEN_TYPE_UNKNOWN: message = " "; break; case plugin.TOKEN_TYPE_RUTOKEN_ECP: message = " "; break; case plugin.TOKEN_TYPE_RUTOKEN_WEB: message = " Web"; break; case plugin.TOKEN_TYPE_RUTOKEN_PINPAD_2: message = " PINPad"; break; }
また、 getDeviceInfo関数を使用すると、 次のものを取得できます。
- デバイスモデル
- デバイスラベル
- デバイスのシリアル番号
- ユーザーがデバイスにログオンしているかどうかに関する情報
PINを変更
デバイスのPINコードを変更する例:
var options = {}; try { plugin.changePin(deviceId, "12345678", "12345671", options); } catch (error) { console.log(error); }
ここで、最初のパラメーターは古いPINで、2番目は新しいPINです。
証明書を使用する
1.トークンには、3つのカテゴリの証明書を保存できます。
- カスタム(CERT_CATEGORY_USERプラグインの定数)
これらは、ユーザーの秘密キーに関連付けられた証明書です。 たとえば、TLSプロトコルでのユーザー認証CMS / PKCS#7に署名するために使用されます。
証明書がユーザー証明書としてインポートされる場合、インポートはデバイス内の対応する秘密キーを検索します。 そのようなキーが見つかった場合、証明書はこのキーに「バインド」されます。 キーが見つからない場合、エラーが返されます。
- ルート(CERT_CATEGORY_CAプラグインの定数)
これらは、証明書の署名を検証するために使用される証明書発行者の証明書です。 このような署名の検証(信頼チェーンの構築)により、ユーザーが別のユーザーの署名を信頼しているかどうかを判断できます。 たとえば、 検証プラグインの機能には、メッセージが署名された証明書検証モードがあります。 これは、トークンのルート証明書をインポートして作成されたトークンルート証明書ストアを使用します。
- その他(プラグインCERT_CATEGORY_OTHERに定数)
これらは、秘密鍵に関連付けられておらず、ルートではない証明書です。
2.デバイスに保存されている証明書を読み取るには、デバイスでの承認は必要ありません。
デバイスからユーザー証明書を読み取る例:
var certs = Array(); try { certs = plugin.enumerateCertificates(deviceId, plugin.CERT_CATEGORY_USER); } catch (error) { console.log(error); }
3.証明書はPEM形式でエクスポートできます。
var certpem; try { certpem = plugin.getCertificate(deviceId, certId); } catch (error) { console.log(error); }
次のような行が得られます:
-----BEGIN CERTIFICATE----- MIIBmjCCAUegAwIBAgIBATAKBgYqhQMCAgMFADBUMQswCQYDVQQGEwJSVTEPMA0G A1UEBxMGTW9zY293MSIwIAYDVQQKFBlPT08gIkdhcmFudC1QYXJrLVRlbGVjb20i MRAwDgYDVQQDEwdUZXN0IENBMB4XDTE0MTIyMjE2NTEyNVoXDTE1MTIyMjE2NTEy NVowEDEOMAwGA1UEAxMFZmZmZmYwYzAcBgYqhQMCAhMwEgYHKoUDAgIjAQYHKoUD AgIeAQNDAARADKA/O1Zw50PzMpcNkWnW39mAJcTehAhkQ2Vg7bHkIwIdf7zPe2Px HyAr6lH+stqdACK6sFYmkZ58cBjzL0WBwaNEMEIwJQYDVR0lBB4wHAYIKwYBBQUH AwIGCCsGAQUFBwMEBgYpAQEBAQIwCwYDVR0PBAQDAgKkMAwGA1UdEwEB/wQCMAAw CgYGKoUDAgIDBQADQQD5TY55KbwADGKJRK+bwCGZw24sdIyayIX5dn9hrKkNrZsW detWY3KJFylSulykS/dfJ871IT+8dXPU5A7WqG4+ -----END CERTIFICATE-----
4. parseCertificateを呼び出して証明書を解析し、DNサブジェクト、DN発行者、拡張機能、公開キー値、署名、シリアル番号、有効期限などを取得できます。
5.証明書をデバイスに書き込むことができます。
ユーザー証明書としてデバイスに証明書を書き込む例:
var certpem = "-----BEGIN CERTIFICATE----- MIIBmjCCAUegAwIBAgIBATAKBgYqhQMCAgMFADBUMQswCQYDVQQGEwJSVTEPMA0G A1UEBxMGTW9zY293MSIwIAYDVQQKFBlPT08gIkdhcmFudC1QYXJrLVRlbGVjb20i MRAwDgYDVQQDEwdUZXN0IENBMB4XDTE0MTIyMjE2NTEyNVoXDTE1MTIyMjE2NTEy NVowEDEOMAwGA1UEAxMFZmZmZmYwYzAcBgYqhQMCAhMwEgYHKoUDAgIjAQYHKoUD AgIeAQNDAARADKA/O1Zw50PzMpcNkWnW39mAJcTehAhkQ2Vg7bHkIwIdf7zPe2Px HyAr6lH+stqdACK6sFYmkZ58cBjzL0WBwaNEMEIwJQYDVR0lBB4wHAYIKwYBBQUH AwIGCCsGAQUFBwMEBgYpAQEBAQIwCwYDVR0PBAQDAgKkMAwGA1UdEwEB/wQCMAAw CgYGKoUDAgIDBQADQQD5TY55KbwADGKJRK+bwCGZw24sdIyayIX5dn9hrKkNrZsW detWY3KJFylSulykS/dfJ871IT+8dXPU5A7WqG4+ -----END CERTIFICATE-----"; try { plugin.importCertificate(deviceId, certpem, plugin.CERT_CATEGORY_USER); } catch (error) { console.log(error); }
6. deleteCertificate関数を呼び出すことにより 、トークンから証明書を削除できます。
キーペアGOST R 34.10-2001を使用する
1.デバイスに保存されているキーペアデクリプターを受信するには、PINコードを入力する必要があります。 キーは回復できないため、秘密キーの意味そのものを取得できないことを理解しておく必要があります。
var keys = Array(); try { plugin.login(deviceId, "12345678"); keys = plugin.enumerateKeys(deviceId, null); } catch (error) { console.log(error); }
2.キーペアを生成するには、PINコードが必要です。 キーを生成するとき、パラメーターはセットから選択できます。
- A:id-GostR3410-2001-CryptoPro-A-ParamSet
- B:id-GostR3410-2001-CryptoPro-B-ParamSet
- C:id-GostR3410-2001-CryptoPro-C-ParamSet
- XA:id-GostR3410-2001-CryptoPro-XchA-ParamSet
- XB:id-GostR3410-2001-CryptoPro-XchB-ParamSet
キーペアGOST R 34.10-2001を生成する例:
var options = {}; var keyId; try { keyId = plugin.generateKeyPair(deviceId, "A", null, options); } catch (error) { console.log(error); }
3. deleteKeyPair関数を使用して、 キーペアをトークンから削除できます。
Opensslの設定
Opensslは、バージョン1.0以降のロシアの暗号化アルゴリズムをサポートしています。 それらを使用するには、opensslがエンジンgostをロードする必要があります。 ほとんどのopensslディストリビューションにはこのライブラリがあります。 エンジンをロードするために、openssl構成ファイルに書き込むことができます。
[openssl_def] engines = engine_section [engine_section] gost = gost_section [gost_section] engine_id = gost default_algorithms = ALL
openssl構成ファイルが標準の場所にない場合、そのファイルへのパスはOPENSSL_CONF環境変数を介して設定できます。
エンジンgostをロードする別のオプションは、opensslユーティリティのコマンドラインオプションで渡すことです。
エンジンgostが標準の場所にない場合は、環境変数OPENSSL_ENGINESを使用して、opensslが検索するディレクトリへのパスを設定できます。
opensslユーティリティの呼び出しが成功したかどうかの情報を取得するには、エラーを明確にする可能性があるため、stdoutとstderrorを解析する必要があります。 記事の最後に、このユーティリティを使用するPHPスクリプトへのリンクがあります。
それでは、完成したユーザースクリプトの実装に移りましょう。
ポータルへの登録
証明書は、システムへの登録時に発行されます
- コンピューターに接続されているRootoken EDSデバイスのリストを取得します
- 選択したRutoken EDSでキーペアGOST R 34.10-2001を生成します
- 生成されたキーペアのPKCS#10証明書リクエストを作成する
- サーバーにリクエストを送信します
- サーバーで証明書を作成し、アカウントにバインドします(証明書自体またはその記述子)。 enumerateCertificates関数を呼び出して取得した証明書記述子は一意で不変であることに注意してください
- クライアントに証明書を送信します
- クライアントで、受信した証明書を視覚化します
- 受信した証明書をRutoken EDSにインポートします
クライアントスクリプトでの呼び出しのシーケンスは次のとおりです。

次に、要求がサーバーに送信され、サーバーはそれに基づいて証明書を発行します。
これを行うには、1.0のopensslバージョンをサーバーにインストールして正しく構成し、CA機能を展開する必要があります。
1. CA免除の生成:
openssl genpkey -engine gost -algorithm GOST2001 -pkeyopt paramset:A -out ca.key
その後、秘密鍵がca.keyファイルに作成されます
2.自己署名CA証明書の作成:
openssl req -engine gost -x509 -new -key ca.key -out ca.crt
ca.crtファイルに発行者に関する必要な情報を入力すると、CA証明書が作成されます。
クライアントから受信したリクエストはuser.csrファイルに保存され、それに基づいて証明書を発行します(リクエストのデータを変更することなく):
openssl ca -engine gost -keyfile ca.key -cert ca.crt -in user.csr -out user.crt -outform PEM -batch
その後、PEM形式のユーザー証明書がuser.crtファイルに作成されます。 クライアントに送信する必要があります。
クライアントでの呼び出しのさらなるシーケンス:

証明書は既に外部CAによって発行されたトークン上にあります
キーペアは、Rootoken EDSのrtPKCS11ECPライブラリと互換性のある形式で作成する必要があります。
- コンピューターに接続されているRootoken EDSデバイスのリストを取得します
- 選択したRootoken EDSで利用可能なすべてのユーザー証明書のリストを取得します
- 各証明書を視覚化します
- ユーザーが目的の証明書を選択します
- サーバーはランダムデータ(塩文字列)の初期シーケンスを形成し、それをクライアントに送信します
- クライアントで認証を呼び出します。 ソルトが認証プラグイン関数に転送されると、このシーケンスはサイズが32文字の追加のランダムデータで補完され、最終シーケンスはユーザーが添付したCMS形式で選択した証明書に署名されます
- 署名がサーバーに送信されます
- サーバーは、ルート証明書を使用してCMS添付署名をチェックします
- 結果のランダムシーケンスがCMS添付メッセージから抽出され、ソルトが「切断」され、比較が実行されます
- 比較が成功した場合、CMS添付メッセージに含まれる証明書でユーザーを登録します
クライアントでの呼び出しのシーケンス:

署名はbase64形式で取得されます。 サーバーでopensslを使用してチェックする場合、署名をヘッダーでフレーム化してPEMにする必要があります。 同様の署名は次のようになります。
-----BEGIN CMS----- MIIDUQYJKoZIhvcNAQcCoIIDQjCCAz4CAQExDDAKBgYqhQMCAgkFADCBygYJKoZI hvcNAQcBoIG8BIG5PCFQSU5QQURGSUxFIFVURjg+PFY+0JLRi9C/0L7Qu9C90LjR gtGMINCw0YPRgtC10L3RgtC40YTQuNC60LDRhtC40Y4/PCE+c2VydmVyLXJhbmRv bS1kYXRhZTI6ZGE6MmM6MDU6MGI6MzY6MjU6MzQ6YzM6NDk6Nzk6Mzk6YmI6MmY6 YzU6Mzc6ZGI6MzA6MTQ6NDQ6ODM6NjY6Njk6NmI6OWY6YTU6MDk6MzQ6YmY6YzQ6 NzY6YzmgggGeMIIBmjCCAUegAwIBAgIBATAKBgYqhQMCAgMFADBUMQswCQYDVQQG EwJSVTEPMA0GA1UEBxMGTW9zY293MSIwIAYDVQQKFBlPT08gIkdhcmFudC1QYXJr LVRlbGVjb20iMRAwDgYDVQQDEwdUZXN0IENBMB4XDTE0MTIyMjE2NTEyNVoXDTE1 MTIyMjE2NTEyNVowEDEOMAwGA1UEAxMFZmZmZmYwYzAcBgYqhQMCAhMwEgYHKoUD AgIjAQYHKoUDAgIeAQNDAARADKA/O1Zw50PzMpcNkWnW39mAJcTehAhkQ2Vg7bHk IwIdf7zPe2PxHyAr6lH+stqdACK6sFYmkZ58cBjzL0WBwaNEMEIwJQYDVR0lBB4w HAYIKwYBBQUHAwIGCCsGAQUFBwMEBgYpAQEBAQIwCwYDVR0PBAQDAgKkMAwGA1Ud EwEB/wQCMAAwCgYGKoUDAgIDBQADQQD5TY55KbwADGKJRK+bwCGZw24sdIyayIX5 dn9hrKkNrZsWdetWY3KJFylSulykS/dfJ871IT+8dXPU5A7WqG4+MYG7MIG4AgEB MFkwVDELMAkGA1UEBhMCUlUxDzANBgNVBAcTBk1vc2NvdzEiMCAGA1UEChQZT09P ICJHYXJhbnQtUGFyay1UZWxlY29tIjEQMA4GA1UEAxMHVGVzdCBDQQIBATAKBgYq hQMCAgkFADAKBgYqhQMCAhMFAARAco5PumEfUYVcLMb1cnzETNOuWC8Goda8pdUL W5ASK+tztCwM7wpXgAy+Y6/sLtClO9sh8dKnAaEY2Yavg3altQ== -----END CMS-----
サーバー上の署名の検証:
openssl cms -engine gost -verify -in sign.cms -inform PEM -CAfile ca.crt -out data.file -certsout user.crt
ここで、sign.cmsは署名が配置されているファイル、ca.crtはルート証明書を含むファイル、そのうちの1つは整列されている必要があり、data.fileは署名されたデータが保存されるファイル、user.crtはファイルですユーザー証明書が保存されます。 data.fileからデータを抽出し、最後の32文字を切断し、saltを比較する必要があります。
サーバー上の証明書から情報を取得する必要がある場合は、次のように解析できます。
テキストビューで証明書の内容を表示します。
openssl x509 -in cert.pem -noout -text
証明書のシリアル番号を表示:
openssl x509 -in cert.pem -noout -serial
件名DNを表示:
openssl x509 -in cert.pem -noout -subject
発行者DNを表示:
openssl x509 -in cert.pem -noout -issuer
件名の住所を表示:
openssl x509 -in cert.pem -noout -email
証明書の開始時間を表示:
openssl x509 -in cert.pem -noout -startdate
証明書の有効期限を表示:
openssl x509 -in cert.pem -noout -enddate
ポータルでの強力な認証
Rutokenプラグインで使用される一般的な認証スキームは次のとおりです。
- サーバーはランダムデータ(塩文字列)の初期シーケンスを形成し、それをクライアントに送信します
- ソルトが認証プラグイン機能に転送されると、このシーケンスにはサイズが32文字のランダムデータが追加され、最終シーケンスはCMS添付形式でユーザーが選択した証明書に署名されます。
- 署名がサーバーに送信されます
- 署名はサーバーで検証されています
- 結果のランダムシーケンスがCMS添付メッセージから抽出され、ソルトが「切断」され、比較が実行されます
- 検証に成功すると、ユーザーはCMSメッセージから抽出された証明書に基づいて認証されます
このスキームの実装は、「登録、外部CAによって発行された証明書が既に利用可能」と根本的に違いはありません。
CMS形式のデータやファイルの電子署名
- テキストメッセージが生成されます(文字列)。メッセージの形成は、サーバーとクライアントの両方で発生します。
- 任意の形式のドキュメント(PDFなど)に署名する場合は、base64形式にトランスコードする必要があります
- 署名用のデータを含む文字列がsign関数に渡されます
- 文字列がbase64でエンコードされたデータである場合、isBase64関数のパラメーターをtrueに設定する必要があり、署名の前にbase64からのデータがデコードされます
- GOST R 34.11-94(認定実装、速度60-70 Kb / s)のハッシュ関数のハードウェア計算を使用する場合、オプションでuseHardwareHashオプションをtrueに設定する必要があります。 このオプションがfalseに設定されている場合、GOST R 34.11-94のハッシュ関数の高速ソフトウェア実装が使用されます。
- 「分離された」CMS署名を作成する場合は、分離オプションをtrueに設定する必要があります。そうでない場合は、「添付」署名が生成されます
- 署名済みCMSメッセージにユーザー証明書を含める/含めないために、addUserCertificateオプションが存在します
- addSignTimeオプションをtrueに設定すると、システム時刻が署名された属性として署名されたCMSメッセージに追加されます。
サーバーでの署名の検証については上記で説明しています。
CMS形式のデータおよび/またはファイルの暗号化/復号化
サーバーのクライアントデータ暗号化
クライアントとサーバー間のデータ交換の機密性を確保するために、プラグインはデータの暗号化/復号化を提供します。 データはCMS形式で暗号化されます。 CMS形式でデータを暗号化するには、「宛先」の公開鍵の証明書が必要です。 同時に、秘密鍵の所有者のみがそのようなメッセージを解読できます。 サーバーのデータを暗号化する場合、サーバー証明書をRootoken EDSに保存することをお勧めします。 この証明書は、ポータルでのユーザー登録中にデバイスに書き込むことができます。 これを行うには、 importCertificate関数を使用して、 CERT_CATEGORY_OTHERをカテゴリパラメーターとして渡します。 cmsEncrypt関数を使用するには、 getCertificate関数を使用して、ハンドルで証明書の本文を取得する必要があります。 この場合、記述子は一意で変更されず、サーバー証明書をインポートするときにサーバー上のユーザーアカウントに保存できます。 GOST 28147-89に従ってハードウェア暗号化を使用するには、useHardwareEncryptionオプションをtrueに設定する必要があります。 それ以外の場合は、GOST 28147-89の迅速なソフトウェア実装が使用されます。
呼び出しのシーケンスを図に示します。

クライアントデータの暗号化:
try { var recipientCert = plugin.getCertificate(deviceId, certRecId); } catch (error) { console.log(error); } var options = {}; options.useHardwareEncryption = true; var cms; try { cms = plugin.cmsEncrypt(deviceId, certSenderId, recipientCert, data, options); } catch (error) { console.log(error); }
サーバー上のデータの復号化、復号化の前に、メッセージはPEMヘッダー「----- BEGIN PKCS7 -----」および「----- END PKCS7 -----」でフレーム化する必要があります。
openssl smime -engine gost -decrypt -in message.cms -inform PEM -recip recipient.crt -inkey recipient.key
recipient.crt-メッセージが暗号化された人の証明書、recipient.key-メッセージが暗号化された人のキー。
クライアント上のサーバーから受信したデータの復号化
サーバーから受信したデータを復号化するには、 cmsDecrypt関数を使用します。 サーバーはその証明書を使用してクライアントを暗号化するため、証明書内の公開鍵に対応するクライアント秘密鍵記述子をkeyIdとして渡す必要があります。 この記述子は一意で不変であるため、サーバーのユーザーアカウントに格納できます。 さらに、 getKeyByCertificate関数を呼び出すことにより、ユーザーキー記述子を明示的に取得できます。
クライアントのサーバーデータ暗号化:
openssl smime -encrypt -engine gost -gost89 -binary -outform PEM -in data.file -out message.enc user.crt
クライアント上のデータの復号化:
var data; var options = {}; try { data = plugin.cmsDecrypt(deviceId, keyId, cms, options); } catch (error) { console.log(error); }
便利なリンク
これらのリンクは、Rutokenプラグインとopensslに基づいたデジタル署名をサポートする情報システムの開発者にとって有用です。
Rootoken Demosystemプラグイン
キーの生成、リクエストの生成、証明書の管理、証明書リクエストのテンプレートの生成のためのWEBサービス
Rutokenプラグインのドキュメント
ロシアの暗号化アルゴリズムでのopensslユーティリティの使用に関するドキュメント
opensslユーティリティを使用したPHPスクリプトの例