Library for embedding electronic signatures in C ++ applications

  • Tutorial


Our company continues to develop a line of libraries that allow you to embed electronic signatures using Russian cryptographic algorithms in various types of information systems.

Some time ago, we supported Rutoken EDS in openssl, then released a cross-platform plug-in for the browser, and now we made a high-level crypto library for embedding in C ++ applications.

Conceptually, these solutions are implemented identically: they use hardware-based implementation of Russian cryptographic algorithms on the Rutoken EDS chip, support for X.509 digital certificates, PKCS # 10 certificate requests, signed and encrypted CMS messages.

The new library is useful for those who write "thick clients", desktop applications, their browser plug-ins, etc.

Supported Devices:
  • USB Token Rutoken EDS
  • Smart card Rutoken EDS
  • Bluetooth Token Rutoken EDS
  • Trustscreen Rootoken PINPad
  • USB Token Rootoken WEB (HID)


The main scenarios for using the library with code examples under the cat.

Basic device operation


The library provides an interface made in the form of the CryptoCore class:

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


The class constructor requires passing the path to the PKCS # 11 library .

Any user scenario begins with a search for Rootoken devices connected to the computer. For this purpose, the method is used:

std::vector enumerateDevices();

This method returns the vector of the identifiers of the slots into which the Rootoken devices are connected.

The method has the following features:
  • in a subsequent call, it is not guaranteed that the identifiers of the slots with connected devices will be identical to the identifiers obtained in the previous call;
  • calling the method “destroys” the internal library cache, that is, it is not guaranteed that previously received key pair and certificate handles are valid.


After receiving a list of devices, you need to obtain information about each specific device. For this purpose, a method is provided:

DeviceInfo getDeviceInfo(unsigned long deviceId);


struct  DeviceInfo
{
    // пользовательское наименование устройства
    std::string label;
    // серийный номер устройства (совпадает с напечатанным на устройстве)
    std::string serial;
    // модель устройства
    std::string model;  
    // тип устройства
    unsigned int type;  
    // была ли произведена авторизация на устройство  
    bool isLoggedIn;  
    // находится ли PIN-код к данному устройству в постоянном кэше библиотеки
    bool isPinCached;  
    ...
};

Possible types of Rutoken devices are defined as follows:

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


An example of basic work with devices:

std::auto_ptr cp(new CryptoCore(pkcs11path));
std::vector devices = cp->enumerateDevices();
std::cout <<"Found " << devices.size() << " devices" << std::endl;
for (std::vector::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;
}

It should be noted that the USB-token Rutoken EDS and the smart card Rutoken EDS are of type RUTOKEN_ECP. For a more accurate identification, the device model should be clarified. In the case of a smart card, the string “Rutoken ECP SC” will be returned.

The type RUTOKEN_PINPAD_2 corresponds to the serial device Rutoken PINPad. The type RUTOKEN_PINPAD_IN is deprecated and is used solely for backward compatibility.

Authorization on the device


To authorize on the device, you need to enter a PIN code.

The method is intended for this:
void login(unsigned long deviceId, const std::string& pin);


To unlock the device, respectively:
void logout(unsigned long deviceId);


To obtain information about whether a login to the device has been made, use the method:
DeviceInfo getDeviceInfo(unsigned long deviceId);


with the option isLoggedIn;

Types of objects, possible operations with objects


The library supports working with key pairs GOST R 34.10-2001 and digital certificates of the public key GOST R 34.10-2001 in the X.509 format.

For certain operations with these objects authorization on the device is required, for others it is not. The following operations with objects are possible using the library:
  1. get a list of certificates stored on the device
  2. write certificate to device
  3. read certificate from device
  4. remove certificate from device
  5. get a list of key pairs stored on the device
  6. create a key pair on the device
  7. remove key pair from device


Operations 1 and 3 do not require authorization on the device, operations 2, 4, 5, 6, 7 require.

Work with key pairs


To get the list of key pairs available on the token, use the method:
std::vector enumerateKeys( unsigned long deviceId, const std::string& marker);


The returned key pair handles are unique and persistent for this key pair.

An example of retrieving key pairs stored on a device:
cp->login(id, "12345678");
std::vector 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;
    }
}


You can create a key pair on the device using the function:
std::string generateKeyPair( unsigned long deviceId, const std::string& params,  const std::string& marker, const std::map& options);
 


Params sets the parameters in accordance with RFC 4357, which will be used for the generated key.

The following options are possible:
  • "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


An example of generating a key pair on a device:
cp->login(id, "12345678");
std::map 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);


To remove a key pair, use the method:
void  deleteKeyPair(unsigned long deviceId, const std::string& keyId);


Work with certificates


The certificate is issued by the CA upon request of PKCS # 10 and can be recorded on the device.

To record (import) a certificate on a device, the method is used:
std::string importCertificate(unsigned long deviceId, const std::string& certificate, unsigned long category);

The category parameter sets the attribute with which the certificate will be saved to the device:
  • user-defined (PKCS11_CERT_CATEGORY_USER), this is a certificate associated with the user's private key.
    They are used, for example, for signing CMS / PKCS # 7, user authentication in the TLS protocol. If the certificate is imported as a user certificate, then the import will search for the corresponding private key in the device. If such a key is found, then the certificate will be “bound” to this key. If the key is not found, an error will be returned.
  • root (PKCS11_CERT_CATEGORY_CA), this is the certificate of the certificate issuer, used to verify the signature on the certificates.
    Such a signature verification (building a chain of trust) allows you to determine whether the user trusts the signature of another user. For example, in the verify function there is a certificate verification mode on which the message was signed. In this case, the token root certificate store created by importing root certificates for the token is used.
  • the other (PKCS11_CERT_CATEGORY_OTHER) is a certificate that is not associated with the private key and is not root.


The list of certificates stored on the device is obtained using the function:
std::vector enumerateCertificates( unsigned long deviceId, unsigned long category);


The category parameter allows you to limit the certificate search to one of the groups listed above.

An example of searching for user certificates on a token:
std::vector 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));
    }
}


In the above example, the printCertInfo function renders the certificate using the method for this:
CertFields parseCertificate( unsigned long deviceId, const std::string& certId);

First, information about the issuer of the certificate (issuer) is visualized, then about the owner (subject). In addition, the certificate expiration date and its serial number are displayed. The method also allows you to parse such certificate extensions as keyUsage, extendedKeyUsage, certificatePolicies.

Certificate Rendering Example:

typedef std::vector > DnList;
typedef std::map > 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& 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& 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;  
}

The certificate is read from the device (export) in the PEM format using the method:
std::string getCertificate(unsigned long deviceId, const std::string& certId);
 


You get something like this line:

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

To remove a certificate from a device, there is a method:
void deleteCertificate(unsigned long deviceId, const std::string& certId);


We now turn to the completed user scripts for working in the system.

Registration in the system


The certificate used to authenticate the user can be issued either directly in the system (if there is a CA) or by an external CA.

If a digital certificate is issued directly in the system, then registration takes place according to the presented scheme:



Example of key generation and generation of PKCS # 10 request:
std::string pkcs11path = "./";
cp = new CryptoCore(pkcs11path);
std::vector 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 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 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 > subject;
typedef std::map 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 > extensions;
std::cout << "PKCS10 request: "<< std::endl<< cp->createPkcs10(id, keyId, subject, extensions, true);
cp->logout(id);


Example of importing a user certificate:
std::string pkcs11path = "./";
std::auto_ptr cp(new CryptoCore(pkcs11path));
std::vector 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(certFile)),
std::istreambuf_iterator());
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& 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;


If the registration takes place according to a certificate that the user already has, then the following scheme is applied:



Example of generating an authentication signature:
std::string pkcs11path = "./";
cp = new CryptoCore(pkcs11path);
std::vector 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 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);


The string of random data received from the server inside the method:
std::string authenticate(unsigned long deviceId, const std::string& certId, const std::string& salt);

supplemented by random data 32 characters in length and signed in CMS attached format.

The output



contains the following message: After checking the signature on the server using the certificate located in it, you should extract the data, disconnect serverSalt and verify it.

After that, you need to register the user in the system with a certificate.

Electronic signature


The library supports two types of signatures:
  • in CMS format with data hashing according to GOST R 34.11-94 and with signature calculation according to GOST R 34.10-2001;
  • Raw signature GOST R 34.10-2001 with data hashing according to GOST R 34.11-94.


To sign in CMS format, use the method:
std::string sign(unsigned long deviceId, const std::string& certId, const std::string& data, const std::map& options);
 


Signature options in CMS format:
  • addUserCertificate - add or not add user certificate to message
  • addSignTime - whether to add the signature time (system) as a signed attribute
  • detached - whether to add signed data to the message (“connected” or “disconnected” signature)


For a raw signature, use the method:
std::string rawSign(unsigned long deviceId, const std::string& keyId, const std::string& data, const std::map& options);

The data parameter can be either data directly in the hex representation or a pre-computed hash of GOST R 34.11-94 from the data. In the second case, you need to set the calculateHash option to false.

The useHardwareHash option is used in both functions and makes it necessary to calculate the hash in accordance with GOST R 34.11-94. If this option is set to false, then a fast software implementation of the calculation of the hash function in accordance with GOST R 34.11-94 is applied.

To verify the "raw" signature on the server, the public key is used. You can get the open part of the key pair using the method:

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

Example of signing data in CMS format:

std::auto_ptr 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 certs = cp->enumerateCertificates(id, PKCS11_CERT_CATEGORY_USER); 
if(certs.size() > 0)
{
    cp->login(id, "12345678");
    std::map 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;
}

To verify the signature, use the method:

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


The method accepts a “disconnected” or “attached” signature in CMS format. In case of a “disconnected” signature, data must be transferred in the data parameter. Verification of the signature is carried out in two stages with the verifyCertificate option set to true: first, the signature under the data is verified using the public key from the certificate (which is located in the CMS or passed through the userCerts parameter), then the signature under the certificate is verified using the public key from the root certificate. If the verifyCertificate option is set to false, only the data signature is verified, the signature under the certificate is not verified.

To verify the signature under the certificate, root certificates stored on the device (imported to the device as root) are used. In addition, the list of root certificates can be expanded by passing an array of additional root certificates (in PEM format) in the ca parameter.

To check whether the certificate on which the verification of the signature is revoked, the crl parameter is provided, in which the CRL array (revocation list) is transmitted, each in PEM format.

When the useHardwareHash option is set to true, the signature verification will use hardware calculation of the GOST R 34.11-94 hash function.

Encryption


In order to ensure the confidentiality of data exchange, the library provides for encryption / decryption of data using GOST 28147-89.

Data is encrypted in CMS format. To encrypt data in CMS format, a certificate of the public key of the “destination” is required. At the same time, only the owner of the private key corresponding to the certificate can decrypt such a message.

To store the “destination” certificate on the device, it should be written to the device by calling the importCertificate method, and PKCS11_CERT_CATEGORY_OTHER should be passed as the category parameter.

Data encryption is performed using the method:
std::string cmsEncrypt(unsigned long deviceId, const std::string& certId, const std::string& recipientCert, const std::string& data, const std::map& options);


For use in the method, you need to get the body of the “destination” certificate, either by reading it from the token using the getCertificate method, or by receiving it in the PEM format directly from the information system. To use hardware encryption according to GOST 28147-89, you must set the useHardwareEncryption option to true. Otherwise, a quick software implementation of GOST 28147-89 will be used.

To decrypt data, use the method:
std::string cmsDecrypt(unsigned long deviceId, const std::string& keyId, const std::string& cmsData, const std::map& options);


The keyId parameter sets the identifier of the key pair corresponding to the certificate with which the respondent encrypted the message.

Where to get the library


The library will be distributed as part of the Rutoken SDK. Now it can be obtained by writing a letter to info@rutoken.ru.

Windows Options:
  • Static library
  • Header file
  • Examples of using


This library requires the latest version of the PKCS # 11 library, which can be downloaded at www.rutoken.ru/support/download/pkcs

Also popular now: