📜 ⬆️ ⬇️

Embedding electronic signatures in systems with a WEB interface using a browser plugin and openssl



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:
')


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:


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:



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:


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





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.



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 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





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 Plugin
WEB-service for generating keys, generating requests, managing certificates, generating certificate request templates
Rutoken Plugin Documentation
Documentation on the use of the openssl utility with Russian cryptographic algorithms
Sample PHP script using openssl utility

Source: https://habr.com/ru/post/246861/


All Articles