
A few years ago, our company launched the product
Rutoken Plugin , which is designed to embed electronic signatures in systems with a web interface. Based on the experience gained in integrating the product into real projects, I would like to note that developers often use the openssl supporting Russian cryptoalgorithms to implement the server part.
This article will describe the typical scheme for such integration, based on the following use cases for the plugin:
')
- Registration on the portal (with the issuance of a certificate or by existing certificate)
- Strong portal authentication
- Electronic signature of data and / or files in CMS format
- Encryption of data and / or files in CMS format
These scripts involve client-server interaction, writing client-side scripts in JavaScript and their corresponding openssl server calls.
Details under the cut.
General operations
Device operations
Search for connected devices
Any client script begins with a search for Rutoken USB devices connected to the computer. In the context of this article, the focus is on the device Rutoken EDS.
var devices = Array(); try { devices = plugin.enumerateDevices(); } catch (error) { console.log(error); }
This returns a list of identifiers of the connected devices. The identifier is the number associated with the slot number to which the device is connected. When re-listing this number may be different for the same device.
Rutoken Plug-in detects all USB-connected devices Rutoken EDS, Rutoken PINPad, Rutoken WEB. Therefore, the next step is to determine the type of device.
Getting device information
To determine the type of device, use the
getDeviceInfo function with the TOKEN_INFO_DEVICE_TYPE parameter. The value of this constant is contained in the plug-in object.
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; }
Also, using the
getDeviceInfo function,
you can get:
- device model
- device label
- device serial number
- information about whether the user is logged in to the device
PIN change
Example of changing the PIN-code on the device:
var options = {}; try { plugin.changePin(deviceId, "12345678", "12345671", options); } catch (error) { console.log(error); }
Here, the first parameter is the old PIN, and the second is the new PIN.
Work with certificates
1. On the token 3 categories of certificates can be stored:
- custom (constant in plugin CERT_CATEGORY_USER)
These are certificates associated with the user's private key. Used, for example, for signing CMS / PKCS # 7, user authentication in the TLS protocol.
If the certificate is imported as a user, then the import will search the device for the corresponding private key. If such a key is found, the certificate will be “tied” to this key. If the key is not found, an error will be returned.
- root (constant in plugin CERT_CATEGORY_CA)
These are certificates issued by certificates issued to verify the signature on certificates. This verification of the signature (building a chain of trust) allows you to determine whether the user trusts the signature of another user. For example, in the verify plugin function, there is a certificate verification mode on which the message was signed. This uses the root certificate store on the token created by importing the root certificates to the token.
- others (constant in plugin CERT_CATEGORY_OTHER)
These are certificates that are not associated with a private key and are not root.
2. To read certificates stored on the device, no authorization is required on the device.
Example of reading user certificates from the device:
var certs = Array(); try { certs = plugin.enumerateCertificates(deviceId, plugin.CERT_CATEGORY_USER); } catch (error) { console.log(error); }
3. The certificate can be exported in PEM format:
var certpem; try { certpem = plugin.getCertificate(deviceId, certId); } catch (error) { console.log(error); }
It turns out like this:
-----BEGIN CERTIFICATE----- MIIBmjCCAUegAwIBAgIBATAKBgYqhQMCAgMFADBUMQswCQYDVQQGEwJSVTEPMA0G A1UEBxMGTW9zY293MSIwIAYDVQQKFBlPT08gIkdhcmFudC1QYXJrLVRlbGVjb20i MRAwDgYDVQQDEwdUZXN0IENBMB4XDTE0MTIyMjE2NTEyNVoXDTE1MTIyMjE2NTEy NVowEDEOMAwGA1UEAxMFZmZmZmYwYzAcBgYqhQMCAhMwEgYHKoUDAgIjAQYHKoUD AgIeAQNDAARADKA/O1Zw50PzMpcNkWnW39mAJcTehAhkQ2Vg7bHkIwIdf7zPe2Px HyAr6lH+stqdACK6sFYmkZ58cBjzL0WBwaNEMEIwJQYDVR0lBB4wHAYIKwYBBQUH AwIGCCsGAQUFBwMEBgYpAQEBAQIwCwYDVR0PBAQDAgKkMAwGA1UdEwEB/wQCMAAw CgYGKoUDAgIDBQADQQD5TY55KbwADGKJRK+bwCGZw24sdIyayIX5dn9hrKkNrZsW detWY3KJFylSulykS/dfJ871IT+8dXPU5A7WqG4+ -----END CERTIFICATE-----
4. The certificate can be parsed by calling the function
parseCertificate and obtaining from it DN Subject, DN Issuer, extensions, public key value, signature, serial number, expiration date, etc.
5. The certificate can be written to the device.
Example of writing a certificate to a device as a user:
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.
By calling the deleteCertificate function,
you can delete the certificate from the token.
Work with key pairs of GOST R 34.10-2001
1. To obtain the decryptor key pairs stored on the device, you must enter a PIN code. It should be understood that the value of the private key itself cannot be obtained, since the key is non-recoverable.
var keys = Array(); try { plugin.login(deviceId, "12345678"); keys = plugin.enumerateKeys(deviceId, null); } catch (error) { console.log(error); }
2. To generate a key pair, you must enter a PIN code. When generating a key, parameters can be selected from the set:
- 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 GOST R 34.10-2001:
var options = {}; var keyId; try { keyId = plugin.generateKeyPair(deviceId, "A", null, options); } catch (error) { console.log(error); }
3. Using the
deleteKeyPair function
, the key pair can be deleted from the token.
Configuring openssl
Openssl supports Russian cryptoalgorithms starting from version 1.0. In order to use them, in openssl you need to load the engine gost. Most openssl distributions have this library. In order for the engine to load, you can put it in the openssl configuration file:
[openssl_def] engines = engine_section [engine_section] gost = gost_section [gost_section] engine_id = gost default_algorithms = ALL
If the openssl configuration file is not located in a standard location, then the path to it can be set via the environment variable OPENSSL_CONF.
Another option for loading engine gost is to send it in the command line parameters of the openssl utility.
If the engine gost is not located in the standard location, then the environment variable OPENSSL_ENGINES can be used to set the path to the directory in which openssl will look for it.
For information on whether the openssl utility was successful or not, with the ability to clarify the error, you need to parse stdout and stderror. At the end of the article there is a link to a PHP script that uses this utility.
We now turn to the implementation of complete custom scripts.
Registration on the portal
Certificate is issued upon registration in the system
- We receive the list of devices connected to the computer Rutoken EDS
- We generate a key pair of GOST R 34.10-2001 on the selected Rutoken EDS
- Create a PKCS # 10 certificate request for a generated key pair
- Send a request to the server
- On the server, create a certificate, bind to the account (the certificate itself or its descriptor). It should be noted that the certificate handles received when calling the enumerateCertificates function are unique and unchanged.
- We send the certificate to the client
- On the client, we visualize the received certificate.
- We import the received certificate into Rutoken EDS
The sequence of calls in the client script will be as follows:

Then the request is sent to the server, where a certificate is issued on its basis.
To do this, the server must have installed and correctly configured openssl version from 1.0 and deployed the CA functionality.
1. Generation of Ulyuch TC:
openssl genpkey -engine gost -algorithm GOST2001 -pkeyopt paramset:A -out ca.key
After that, the private key will be created in the ca.key file.
2. Creating a self-signed CA certificate:
openssl req -engine gost -x509 -new -key ca.key -out ca.crt
After entering the necessary information about the publisher in the ca.crt file, the CA certificate will be created.
The request received from the client is saved in the file user.csr and we issue a certificate based on it (without modifying the data from the request):
openssl ca -engine gost -keyfile ca.key -cert ca.crt -in user.csr -out user.crt -outform PEM -batch
After that, a user certificate in PEM format is created in the user.crt file. It should be sent to the client.
Further sequence of calls on the client:

The certificate is already on the token, issued by an external CA
The key pair must be created in a format compatible with the rtPKCS11ECP library for Rutoken EDS.
- We receive the list of devices connected to the computer Rutoken EDS
- We receive the list of all available user certificates on the selected Rutoken EDS
- We visualize every certificate
- The user selects the desired certificate.
- The server forms the initial sequence of random data (salt string) and sends it to the client.
- Call authenticate on the client. When sending salt to the function of the plugin authenticate, this sequence is supplemented with additional random data of 32 characters in size, and the final sequence is signed on the certificate selected by the user in the CMS attached format
- The signature is sent to the server
- On the server, CMS attached signature is verified using a root certificate.
- The resulting random sequence is extracted from the CMS attached message, the salt is “disconnected” and a comparison is made.
- If the comparison is successful, then register the user with the certificate contained in the CMS attached message
The sequence of calls on the client:

The signature is obtained in base64 format. When checking it on the server using openssl, the signature should be framed with headers to make it PEM. This signature will look something like this:
-----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-----
Signature verification on server:
openssl cms -engine gost -verify -in sign.cms -inform PEM -CAfile ca.crt -out data.file -certsout user.crt
Here, sign.cms is the file that contains the signature, ca.crt is the file with root certificates, up to one of which a chain should line up, data.file is the file where the signed data will be saved, user.crt is the file where the user certificate will be saved. It is from data.file that you need to extract the data to disconnect the last 32 characters and compare the salt.
If on the server you need to get information from the certificate, then you can parse it like this:
Show certificate contents in text view:
openssl x509 -in cert.pem -noout -text
Show certificate serial number:
openssl x509 -in cert.pem -noout -serial
Show subject DN:
openssl x509 -in cert.pem -noout -subject
Show publisher DN:
openssl x509 -in cert.pem -noout -issuer
Show Subject Postal Address:
openssl x509 -in cert.pem -noout -email
Show certificate start time:
openssl x509 -in cert.pem -noout -startdate
Show certificate expiration time:
openssl x509 -in cert.pem -noout -enddate
Strong portal authentication
The general authentication scheme used in the Rutoken Plugin is as follows:
- the server forms the initial sequence of random data (salt string) and sends it to the client
- when transferring salt to the function of the plugin authenticate, this sequence is supplemented with random data of 32 characters in size, and the final sequence is signed on the certificate selected by the user in the CMS format attached
- the signature is sent to the server
- On the server, the signature is checked
- the final random sequence is extracted from the CMS attached message, the salt is “disconnected” and a comparison is made
- upon successful verification, the user is authenticated based on the certificate extracted from the CMS message
The implementation of this scheme is not fundamentally different from "Registration, the certificate is already available, issued by an external CA".
Electronic signature of data and / or files in CMS format
- a text message (string) is formed, the message can be generated both on the server and on the client
- if you want to sign a document of arbitrary format (for example, PDF), then you need to encode it into base64 format
- the string containing the data for the signature is passed to the sign function
- if the string is base64 encoded data, then the isBase64 function parameter must be set to true, and the base64 decoding will occur before the signature
- if you want to use hardware calculation of the GOST R 34.11-94 hash function (certified implementation, speed 60-70 Kb / s), then in the options you need to set the useHardwareHash option to true. If this option is set to false, then a quick software implementation of the hash function GOST R 34.11-94 will be used
- if you want to create a “detached” (detached) CMS signature, then you need to set the detached option to true, otherwise, a “attached” signature will be formed
- in order to include / not include the user certificate in the signed CMS message there is an option addUserCertificate
- Setting the addSignTime option to true will result in the system time being added to the signed CMS message as a signed attribute
Verification of the signature on the server is described above.
Encryption / decryption of data and / or files in CMS format
Client data encryption for the server
In order to ensure confidentiality of data exchange between the client and the server, the plugin provides for data encryption / decryption. Data is encrypted in CMS format. In order to encrypt data in the CMS format, a certificate of the “recipient” public key is required. In this case, only the owner of the private key can decrypt such a message. When encrypting data for the server, it is recommended to store the server certificate on the Rutoken EDS. This certificate can be written to the device when the user registers on the portal. To do this, use the
importCertificate function, and you should pass CERT_CATEGORY_OTHER as the category parameter. To be used in the
cmsEncrypt function,
you need to get the body of the certificate by its descriptor using the
getCertificate function. In this case, the descriptor is unique and unchanged and can be stored in the user account on the server when importing the server certificate. In order to use hardware encryption according to GOST 28147-89, it is required to set the useHardwareEncryption option to true. Otherwise, fast software implementation of GOST 28147-89 will be used.
The sequence of calls is shown in the picture:

Data encryption on the client:
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); }
To decrypt data on the server, before decrypting, the message must be framed with PEM headers "----- BEGIN PKCS7 -----" and "----- END PKCS7 -----":
openssl smime -engine gost -decrypt -in message.cms -inform PEM -recip recipient.crt -inkey recipient.key
recipient.crt is the certificate of the person for whom the message is encrypted, recipient.key is the key of the person for whom the message is encrypted.
Decryption of data received from the server on the client
To decrypt data received from the server, the
cmsDecrypt function is
used . Since the server encrypts the client using its certificate, the client's private key descriptor corresponding to the public key in the certificate must be passed as the keyId. This descriptor is unique and unchanged and therefore can be stored in a user account on the server. In addition, a user key descriptor can be obtained explicitly by calling the
getKeyByCertificate function.
Data encryption on the server for the client:
openssl smime -encrypt -engine gost -gost89 -binary -outform PEM -in data.file -out message.enc user.crt
Decryption of data on the client:
var data; var options = {}; try { data = plugin.cmsDecrypt(deviceId, keyId, cms, options); } catch (error) { console.log(error); }
useful links
These links can be useful for information system developers with EDS support based on the Rutoken Plugin and openssl:
Demo System Rutoken PluginWEB-service for generating keys, generating requests, managing certificates, generating certificate request templatesRutoken Plugin DocumentationDocumentation on the use of the openssl utility with Russian cryptographic algorithmsSample PHP script using openssl utility