C ++アプリケーションに電子署名を埋め込むためのライブラリ





当社は、さまざまなタイプの情報システムでロシアの暗号化アルゴリズムを使用して電子署名を埋め込むことができるライブラリのラインを開発し続けています。



少し前に、 opensslでRutoken EDSサポートしてから、クロスプラットフォームブラウザプラグインをリリースし、C ++アプリケーションに埋め込むための高レベルの暗号化ライブラリを作成しました。



概念的には、これらのソリューションは同じように実装されます。ロシアの暗号化アルゴリズムのハードウェア実装がRutoken EDSチップで使用され、X.509デジタル証明書のサポート、PKCS#10証明書要求、署名および暗号化されたCMSメッセージです。



新しいライブラリは、「シッククライアント」、デスクトップアプリケーション、ブラウザプラグインなどを作成するユーザーに役立ちます。



サポートされているデバイス:





catの下のコード例でライブラリを使用する主なシナリオ。



基本的なデバイス操作



ライブラリは、CryptoCoreクラスの形式で作成されたインターフェイスを提供します。



class CryptoCore { public: CryptoCore(const std::string& pkcs11path); ~CryptoCore(); ... }
      
      







クラスコンストラクターは、パスをPKCS#11ライブラリに渡す必要があります。



ユーザーシナリオはすべて、コンピューターに接続されているRootokenデバイスの検索から始まります。 この目的のために、メソッドが使用されます:



 std::vector<unsigned long> enumerateDevices();
      
      





このメソッドは、Rootokenデバイスが接続されているスロットの識別子のベクトルを返します。



このメソッドには次の機能があります。





デバイスのリストを受け取った後、特定の各デバイスに関する情報を取得する必要があります。 この目的のために、メソッドが提供されます:



 DeviceInfo getDeviceInfo(unsigned long deviceId);
      
      







 struct DeviceInfo { //    std::string label; //    (    ) std::string serial; //   std::string model; //   unsigned int type; //       bool isLoggedIn; //   PIN-        bool isPinCached; ... };
      
      





Rutokenデバイスの可能なタイプは、次のように定義されます。



 class CryptoCore { ... public: enum DeviceType { UNKNOWN, RUTOKEN_ECP, RUTOKEN_WEB, RUTOKEN_PINPAD_IN, KAZTOKEN, RUTOKEN_PINPAD_2 }; ... }
      
      







デバイスの基本的な作業の例:



 std::auto_ptr<CryptoCore> cp(new CryptoCore(pkcs11path)); std::vector<unsigned long> devices = cp->enumerateDevices(); std::cout <<"Found " << devices.size() << " devices" << std::endl; for (std::vector<unsigned long>::const_iterator it = devices.begin(); it != devices.end(); it++) { unsigned int id = *it; DeviceInfo info = cp->getDeviceInfo(id); std::cout << "Device ID: " << id << std::endl; std::cout << "\tLabel: " << info.label << std::endl; std::cout << "\tSerial: " << info.serial << std::endl; std::cout << "\tModel: " << info.model << std::endl; std::cout << "\tType: "; switch (info.type) { case 0: std::cout << "Unknown"; break; case 1: std::cout << "Rutoken ECP"; break; case 2: std::cout << "Rutoken WEB"; break; case 3: std::cout << "Rutoken PINPAD IN"; break; case 4: std::cout << "KAZTOKEN"; break; case 5: std::cout << "Rutoken PINPAD2"; break; } std::cout << std::endl; }
      
      





USBトークンのRutoken EDSとスマートカードのRutoken EDSのタイプはRUTOKEN_ECPであることに注意してください。 より正確に識別するには、デバイスモデルを明確にする必要があります。 スマートカードの場合、文字列「Rutoken ECP SC」が返されます。



タイプRUTOKEN_PINPAD_2は、シリアルデバイスRutoken PINPadに対応しています。 タイプRUTOKEN_PINPAD_INは非推奨であり、下位互換性のためにのみ使用されます。



デバイスでの承認



デバイスで認証するには、PINコードを入力する必要があります。



メソッドはこれを目的としています。

 void login(unsigned long deviceId, const std::string& pin);
      
      







デバイスのロックを解除するには、それぞれ:

 void logout(unsigned long deviceId);
      
      







デバイスへのログインが行われたかどうかに関する情報を取得するには、次のメソッドを使用します。

 DeviceInfo getDeviceInfo(unsigned long deviceId);
      
      







オプションisLoggedInを使用。



オブジェクトの種類、オブジェクトで可能な操作



ライブラリは、キーペアGOST R 34.10-2001およびX.509形式の公開キーGOST R 34.10-2001のデジタル証明書の使用をサポートしています。



これらのオブジェクトを使用した特定の操作では、デバイスでの認証が必要ですが、その他では許可されていません。 ライブラリを使用すると、オブジェクトを使用した次の操作が可能です。

  1. デバイスに保存されている証明書のリストを取得します
  2. デバイスに証明書を書き込む
  3. デバイスから証明書を読み取る
  4. デバイスから証明書を削除する
  5. デバイスに保存されているキーペアのリストを取得する
  6. デバイスでキーペアを作成する
  7. デバイスからキーペアを削除する




操作1および3はデバイスでの承認を必要とせず、操作2、4、5、6、7が必要です。



キーペアを操作する



トークンで使用可能なキーペアのリストを取得するには、次のメソッドを使用します。

 std::vector<std::string> enumerateKeys( unsigned long deviceId, const std::string& marker);
      
      







返されるキーペアハンドルは、このキーペアに対して一意で永続的です。



デバイスに保存されているキーペアを取得する例:

 cp->login(id, "12345678"); std::vector<std::string> keys = cp->enumerateKeys(id, "Test marker"); if (keys.empty()) { std::cerr << "No keys were found on token" << std::endl; } else { std::cerr << "Found " << keys.size() << " key(s) on token" << std::endl; for (size_t i = 0; i < keys.size(); i++) { std::string kId = keys[i]; std::cerr << "Key with id: " << kId << " on token with label: " << cp->getKeyLabel(id, kId) << std::endl; } }
      
      







次の機能を使用して、デバイス上にキーペアを作成できます。

 std::string generateKeyPair( unsigned long deviceId, const std::string& params, const std::string& marker, const std::map<std::string, bool>& options);
      
      







パラメータは、生成されたキーに使用されるRFC 4357に従ってパラメータを設定します。



次のオプションが可能です。





デバイスでキーペアを生成する例:

 cp->login(id, "12345678"); std::map<std::string, bool> keyOptions; keyOptions["needPin"] = false; std::string keyId = cp->generateKeyPair(id, "A", "Test marker", keyOptions); std::string keyLabel; std::cerr << "Please, enter new key label: "; std::cin >> keyLabel;cp->setKeyLabel(id, keyId, keyLabel);
      
      







キーペアを削除するには、次の方法を使用します。

 void deleteKeyPair(unsigned long deviceId, const std::string& keyId);
      
      







証明書を使用する



証明書は、PKCS#10の要求に応じてCAによって発行され、デバイスに記録できます。



デバイスで証明書を記録(インポート)するには、次の方法が使用されます。

 std::string importCertificate(unsigned long deviceId, const std::string& certificate, unsigned long category);
      
      





categoryパラメーターは、証明書をデバイスに保存する際の属性を設定します。





デバイスに保存されている証明書のリストは、次の機能を使用して取得されます。

 std::vector<std::string> enumerateCertificates( unsigned long deviceId, unsigned long category);
      
      







categoryパラメーターを使用すると、証明書の検索を上記のグループのいずれかに制限できます。



トークンでユーザー証明書を検索する例:

 std::vector<std::string> certs = cp->enumerateCertificates(id, PKCS11_CERT_CATEGORY_USER); // get certificates info by ID if (certs.size() > 0) { std::cout << "Certificates with USER category(" << certs.size() << "): " << std::endl; for (size_t i = 0; i < certs.size(); i++) { printCertInfo(cp.get(), id, certs.at(i)); } }
      
      







上記の例では、printCertInfo関数は次のメソッドを使用して証明書をレンダリングします。

 CertFields parseCertificate( unsigned long deviceId, const std::string& certId);
      
      





最初に、証明書の発行者(発行者)に関する情報が視覚化され、次に所有者(対象)に関する情報が視覚化されます。 さらに、証明書の有効期限とそのシリアル番号が表示されます。 このメソッドを使用すると、keyUsage、extendedKeyUsage、certificatePoliciesなどの証明書拡張機能を解析することもできます。



証明書のレンダリングの例:



 typedef std::vector<std::map<std::string, std::string> > DnList; typedef std::map<std::string, std::vector<std::string> > ExtensionsMap; struct CertFields { DnList issuer; DnList subject; std::string serialNumber; std::string validNotBefore; std::string validNotAfter; ExtensionsMap extensions; std::string certificateText; }; void printCertInfo(CryptoCore* cp, unsigned int tokenId, std::string certId) { CertFields info = cp->parseCertificate(tokenId, certId); DnList& dn = info.issuer; std::cout << "Certificate ID: " << certId << std::endl << "\tIssuer: "; for (DnList::iterator it = dn.begin(); it != dn.end(); it++) { std::map<std::string, std::string>& rdn = *it; if (it != dn.begin()) std::cout << ", ";std::cout << rdn[ "rdn"] << "=" << rdn["value"]; } std::cout << std::endl;dn = info.subject;std::cout << "\tSubject: "; for (DnList::iterator it = dn.begin(); it != dn.end(); it++) { std::map<std::string, std::string>& rdn = *it; if (it != dn.begin()) std::cout << ", ";std::cout << rdn[ "rdn"] << "=" << rdn["value"]; } std::cout << std::endl; std::cout << "\tSerialNumber: " << info.serialNumber << std::endl; std::cout << "\tValid Not Before: " << info.validNotBefore << std::endl; std::cout << "\tValid Not After: " << info.validNotAfter << std::endl; }
      
      





証明書は、次の方法を使用して、PEM形式でデバイスから読み取られます(エクスポート)。

 std::string getCertificate(unsigned long deviceId, const std::string& certId);
      
      







次のような行が得られます:



 -----BEGIN CERTIFICATE----- MIIBmjCCAUegAwIBAgIBATAKBgYqhQMCAgMFADBUMQswCQYDVQQGEwJSVTEPMA0G A1UEBxMGTW9zY293MSIwIAYDVQQKFBlPT08gIkdhcmFudC1QYXJrLVRlbGVjb20i MRAwDgYDVQQDEwdUZXN0IENBMB4XDTE0MTIyMjE2NTEyNVoXDTE1MTIyMjE2NTEy NVowEDEOMAwGA1UEAxMFZmZmZmYwYzAcBgYqhQMCAhMwEgYHKoUDAgIjAQYHKoUD AgIeAQNDAARADKA/O1Zw50PzMpcNkWnW39mAJcTehAhkQ2Vg7bHkIwIdf7zPe2Px HyAr6lH+stqdACK6sFYmkZ58cBjzL0WBwaNEMEIwJQYDVR0lBB4wHAYIKwYBBQUH AwIGCCsGAQUFBwMEBgYpAQEBAQIwCwYDVR0PBAQDAgKkMAwGA1UdEwEB/wQCMAAw CgYGKoUDAgIDBQADQQD5TY55KbwADGKJRK+bwCGZw24sdIyayIX5dn9hrKkNrZsW detWY3KJFylSulykS/dfJ871IT+8dXPU5A7WqG4+ -----END CERTIFICATE-----
      
      





デバイスから証明書を削除するには、次の方法があります。

 void deleteCertificate(unsigned long deviceId, const std::string& certId);
      
      







次に、システムで作業するための完成したユーザースクリプトを使用します。



システムへの登録



ユーザーの認証に使用される証明書は、システムで直接(CAがある場合)、または外部CAによって発行できます。



デジタル証明書がシステムで直接発行された場合、提示されたスキームに従って登録が行われます。







キーを生成し、PKCS#10リクエストを生成する例:

 std::string pkcs11path = "./"; cp = new CryptoCore(pkcs11path); std::vector<unsigned long> devices = cp->enumerateDevices(); std::cerr << "Found " << devices.size() << " devices" << std::endl; if (devices.empty()) { std::cerr << "Can't find any device" << std::endl; return 1; } unsigned long id = devices.front(); DeviceInfo info = cp->getDeviceInfo(id); std::cerr << "Device ID: " << id << std::endl; std::cerr << "\tLabel: " << info.label << std::endl; std::cerr << "\tSerial: " << info.serial << std::endl; std::cerr << std::endl; cp->login(id, "12345678"); std::vector<std::string> keys = cp->enumerateKeys(id, "Test marker"); if (keys.empty()) { std::cerr << "No keys were found on token" << std::endl; } else { std::cerr << "Found " << keys.size() << " key(s) on token" << std::endl; for (size_t i = 0; i < keys.size(); i++) { std::string kId = keys[i]; std::cerr << "Key with id: " << kId << " on token with label: " << cp->getKeyLabel(id, kId) << std::endl; } } std::map<std::string, bool> keyOptions; keyOptions["needPin"] = false; std::string keyId; // key generation keyId = cp->generateKeyPair(id, "A", "Test marker", keyOptions); std::string keyLabel; std::cerr << "Please, enter new key label: "; std::cin >> keyLabel; cp->setKeyLabel(id, keyId, keyLabel); std::cerr << "Creating PKCS#10 request on key with ID: " << keyId << std::endl; std::string str; std::vector<std::map<std::string, std::string> > subject; typedef std::map<std::string, std::string> RdnType; RdnType rdn; // country name for (;; ) { std::cerr << "Please, enter new request country name (2 symbol): "; std::cin >> str; if (str.length() != 2) { std::cerr << "try again" << std::endl; continue; } else { rdn["rdn"] = "countryName"; rdn["value"] = str; subject.push_back(rdn); break; } } // commonName std::cerr << "Please, enter new request commonName: "; std::cin >> str; rdn.clear(); rdn["rdn"] = "commonName"; rdn["value"] = str; subject.push_back(rdn); // stateOrProvince std::cerr << "Please, enter new request stateOrProvinceName: "; std::cin >> str; rdn.clear(); rdn["rdn"] = "stateOrProvinceName"; rdn["value"] = str; subject.push_back(rdn); // locality std::cerr << "Please, enter new request localityName: "; std::cin >> str; rdn.clear(); rdn["rdn"] = "localityName"; rdn["value"] = str; subject.push_back(rdn); // organization std::cerr << "Please, enter new request organizationName: "; std::cin >> str; rdn.clear(); rdn["rdn"] = "organizationName"; rdn["value"] = str; subject.push_back(rdn); std::cerr << "Please, enter new request organizationalUnitName: "; std::cin >> str; rdn.clear(); // organizationalUnit rdn["rdn"] = "organizationalUnitName"; rdn["value"] = str; subject.push_back(rdn); std::map<std::string, std::vector<std::string> > extensions; std::cout << "PKCS10 request: "<< std::endl<< cp->createPkcs10(id, keyId, subject, extensions, true); cp->logout(id);
      
      







ユーザー証明書をインポートする例:

 std::string pkcs11path = "./"; std::auto_ptr<CryptoCore> cp(new CryptoCore(pkcs11path)); std::vector<unsigned long> devices = cp->enumerateDevices(); if (devices.empty()) { std::cout << "Can't find any device" << std::endl; return 1; } std::cout << "Found " << devices.size() << " devices" << std::endl; unsigned long id = devices.front(); DeviceInfo info = cp->getDeviceInfo(id); std::cout << "Device ID: " << id << std::endl; std::cout << "\tLabel: " << info.label << std::endl; std::cout << "\tSerial: " << info.serial << std::endl; std::cout << "\tModel: " << info.model << std::endl; cp->login(id, "12345678"); std::ifstream certFile(file, std::ios::in | std::ios::binary); std::string certBody((std::istreambuf_iterator<char>(certFile)), std::istreambuf_iterator<char>()); CertFields certInfo = cp->parseCertificateFromString(certBody); DnList& dn = certInfo.subject; std::cout << "Importing certificate: " << std::endl << "\tSubject: "; for (DnList::iterator it = dn.begin(); it != dn.end(); it++) { std::map<std::string, std::string>& rdn = *it; if (it != dn.begin()) std::cout << ", "; std::cout << rdn["rdn"] << "=" << rdn["value"]; } std::cout << std::endl; std::string certId = cp->importCertificate(id, certBody, PKCS11_CERT_CATEGORY_USER)); std::cout << "Certificate imported with ID: " << certId << std::endl;
      
      







ユーザーがすでに利用可能な証明書に従って登録が行われる場合、次のスキームが適用されます。







認証署名を生成する例:

 std::string pkcs11path = "./"; cp = new CryptoCore(pkcs11path); std::vector<unsigned long> devices = cp->enumerateDevices(); std::cerr << "Found " << devices.size() << " devices" << std::endl; if (devices.empty()) { std::cerr << "Can't find any device" << std::endl; return 1; } unsigned long id = devices.front(); DeviceInfo info = cp->getDeviceInfo(id); std::cerr << "Device ID: " << id << std::endl; std::cerr << "\tLabel: " << info.label << std::endl; std::cerr << "\tSerial: " << info.serial << std::endl; std::cerr << std::endl; std::vector<std::string> certs = cp->enumerateCertificates(id, PKCS11_CERT_CATEGORY_USER); // get certificates info by ID if (certs.size() > 0) { std::cout << "Certificates with USER category(" << certs.size() << "): " << std::endl; for (size_t i = 0; i < certs.size(); i++) { printCertInfo(cp.get(), id, certs.at(i)); } } cp->login(id, "12345678"); // serverSalt - random string from server std::string authSignature = cp->authenticate(id, certs.front(), serverSalt);
      
      







メソッド内のサーバーから受信したランダムデータの文字列:

 std::string authenticate(unsigned long deviceId, const std::string& certId, const std::string& salt);
      
      





32文字のランダムデータで補完され、CMS添付形式で署名されています。



出力は、次の内容のメッセージです。







メッセージ内の証明書を使用して、サーバー上の署名を確認した後、データを抽出し、serverSaltを切断して検証する必要があります。



その後、ユーザーを証明書でシステムに登録する必要があります。



電子署名



ライブラリは、2種類の署名をサポートしています。





CMS形式でサインインするには、次のメソッドを使用します:

 std::string sign(unsigned long deviceId, const std::string& certId, const std::string& data, const std::map<std::string, bool>& options);
      
      







CMS形式の署名オプション:





生の署名の場合、次のメソッドを使用します。

 std::string rawSign(unsigned long deviceId, const std::string& keyId, const std::string& data, const std::map<std::string, bool>& options);
      
      





データパラメータは、16進数表現のデータ、またはデータからGOST R 34.11-94の事前に計算されたハッシュのいずれかです。 2番目のケースでは、calculateHashオプションをfalseに設定する必要があります。



useHardwareHashオプションは両方の関数で使用され、GOST R 34.11-94に従ってハッシュを計算する必要があります。 このオプションがfalseに設定されている場合、GOST R 34.11-94に基づくハッシュ関数の計算の高速ソフトウェア実装が適用されます。



サーバー上の「生の」署名を検証するには、公開鍵が使用されます。 メソッドを使用して、キーペアのオープン部分を取得できます。



 std::string getPublicKeyValue(unsigned long deviceId, const std::string& keyId, const std::map<std::string, bool>& options);
      
      





CMS形式でデータに署名する例:



 std::auto_ptr<CryptoCore> cp( new CryptoCore(pkcs11path)); std::vector< unsigned long> devices = cp->enumerateDevices(); std::cerr << "Found " << devices.size() << " devices" << std::endl; unsigned long id = devices.front(); DeviceInfo info = cp->getDeviceInfo(id); std::cerr << "Device ID: " << id << std::endl; std::cerr << "\tLabel: " << info.label << std::endl; std::cerr << "\tSerial: " << info.serial << std::endl; std::cerr << "\tModel: " << info.model << std::endl; std::vector<std::string> certs = cp->enumerateCertificates(id, PKCS11_CERT_CATEGORY_USER); if(certs.size() > 0) { cp->login(id, "12345678"); std::map<std::string, bool> options; options[ "addUserCertificate"] = true; options[ "addSignTime"] = true; options[ "useHardwareHash"] = false; std::string cms = cp->sign(id, certs.front(), data, options); std::cout << "-----BEGIN CMS-----" << std::endl; std::cout << cms; std::cout << "-----END CMS-----" << std::endl; }
      
      





署名を検証するには、次のメソッドを使用します。



 bool verify(unsigned long deviceId, const std::string& cms, const std::string& data, const std::vector<std::string> userCerts, const std::vector<std::string> ca, const std::vector<std::string> crl, const std::map<std::string, bool>& options)
      
      







このメソッドは、CMS形式の「切断」または「添付」署名を受け入れます。 「切断された」署名の場合、データはデータパラメータで転送する必要があります。 署名の検証は、verifyCertificateオプションをtrueに設定して2段階で実行されます。最初に、データの署名は証明書の公開鍵(CMSにあるか、userCertsパラメーターを介して渡される)を使用して検証され、次に証明書の署名はルート証明書の公開鍵を使用して検証されます。 verifyCertificateオプションがfalseに設定されている場合、データ署名のみが検証され、証明書の下の署名は検証されません。



証明書の下で署名を検証するには、デバイスに保存されているルート証明書(デバイスとしてルートとしてインポートされた)が使用されます。 さらに、caパラメーターで追加のルート証明書の配列(PEM形式)を渡すことにより、ルート証明書のリストを拡張できます。



署名が検証された証明書が失効しているかどうかを確認するために、CRL配列(失効リスト)がそれぞれPEM形式で送信されるcrlパラメーターが提供されます。



useHardwareHashオプションがtrueに設定されている場合、署名検証はGOST R 34.11-94ハッシュ関数のハードウェア計算を使用します。



暗号化



データ交換の機密性を確保するために、ライブラリはGOST 28147-89を使用したデータの暗号化/復号化を提供します。



データはCMS形式で暗号化されます。 データをCMS形式で暗号化するには、「宛先」の公開鍵の証明書が必要です。 同時に、証明書に対応する秘密鍵の所有者のみがそのようなメッセージを解読できます。



「宛先」証明書をデバイスに保存するには、importCertificateメソッドを呼び出してデバイスに書き込み、PKCS11_CERT_CATEGORY_OTHERをカテゴリパラメーターとして渡す必要があります。



データの暗号化は次の方法を使用して実行されます。

 std::string cmsEncrypt(unsigned long deviceId, const std::string& certId, const std::string& recipientCert, const std::string& data, const std::map<std::string, bool>& options);
      
      







メソッドで使用するには、getCertificateメソッドを使用してトークンからそれを読み取るか、情報システムから直接PEM形式で受信することにより、「宛先」証明書の本文を取得する必要があります。 GOST 28147-89に従ってハードウェア暗号化を使用するには、useHardwareEncryptionオプションをtrueに設定する必要があります。 それ以外の場合は、GOST 28147-89の迅速なソフトウェア実装が使用されます。



データを復号化するには、次のメソッドを使用します:

 std::string cmsDecrypt(unsigned long deviceId, const std::string& keyId, const std::string& cmsData, const std::map<std::string, bool>& options);
      
      







keyIdパラメーターは、応答者がメッセージを暗号化した証明書に対応するキーペアの識別子を設定します。



ライブラリの入手先



ライブラリはRutoken SDKの一部として配布されます。 現在、info @ rutoken.ruに手紙を書くことで入手できます。



Windowsオプション:





このライブラリには、最新バージョンのPKCS#11ライブラリが必要です。これは、 www.rutoken.ru / support / download / pkcsにあります。



All Articles