Rutoken, OpenSSL, and a local CA for signing messages
Some time ago, there was a need in the framework of the project to limit the simultaneous number of computers that have access to a web application running within the customer’s local area network.
The decision to use hardware USB tokens to identify the computer came by itself. The choice rested on the Rutoken EDS: it works without drivers, to work in a Web application you only need a browser plug-in released by the developer. Since the token should identify the computer, not the user, working with it should be completely “transparent”: if it exists, then the system simply silently works without asking the user unnecessary questions.
It was decided: when logging into the system, sign the user credentials with an unqualified signature of a certificate stored on Rutoken using the Rootoken plugin, and check on the server. After a successful login using the plug-in, check the physical presence of the same token, and in the absence of it, log out. Within the framework of the mentioned project, this was enough.
There is a need to create your own Certification Authority (CA) for exchanging signed messages, or rather, for sending signed messages from a client to a server. Client certificates should be located on USB tokens in private key containers, and signature verification should be performed on the server using OpenSSL tools.
So, the task:installation and configuration on the Linux server of the CA. Deploy client certificates that identify computers on USB tokens .
To solve it, you will need:
The decision to use hardware USB tokens to identify the computer came by itself. The choice rested on the Rutoken EDS: it works without drivers, to work in a Web application you only need a browser plug-in released by the developer. Since the token should identify the computer, not the user, working with it should be completely “transparent”: if it exists, then the system simply silently works without asking the user unnecessary questions.
It was decided: when logging into the system, sign the user credentials with an unqualified signature of a certificate stored on Rutoken using the Rootoken plugin, and check on the server. After a successful login using the plug-in, check the physical presence of the same token, and in the absence of it, log out. Within the framework of the mentioned project, this was enough.
There is a need to create your own Certification Authority (CA) for exchanging signed messages, or rather, for sending signed messages from a client to a server. Client certificates should be located on USB tokens in private key containers, and signature verification should be performed on the server using OpenSSL tools.
So, the task:installation and configuration on the Linux server of the CA. Deploy client certificates that identify computers on USB tokens .
To solve it, you will need:
- OpenSSL 1.1.0 and higher, with support for GOST algorithms
- Rutoken EDS
- integration module with OpenSSL from the developer's site , PKCS # 11 library , as well as a utility for administering the token (rtAdmin)
- opensc, pcsc-lite-ccid packages installed
- We create a directory where the CA will be located and copy the OpenSSL config included with the system (there is /etc/ssl/openssl.cnf on the latest versions of Ubuntu )
- We configure "our" openssl.cnf:
a) Add the directives for connecting the token engine to the beginning of the file:openssl_conf = openssl_def [ openssl_def ] engines = engine_section [ engine_section ] rtengine = gost_section [ gost_section ] dynamic_path = /path/to/rutoken/openssl/connector/librtengine.so MODULE_PATH = /path/to/rutoken/pkcs11/librtpkcs11ecp.so RAND_TOKEN = pkcs11:manufacturer=Aktiv%20Co.;model=Rutoken%20ECP default_algorithms = CIPHERS, DIGEST, PKEY, RAND
b) uncomment the line# req_extensions = v3_req # The extensions to add to a certificate request
c) in the [v3_req] section, specify the following parameters:subjectSignTool = ASN1:FORMAT:UTF8,UTF8String:Наш Рутокен ЭЦП extendedKeyUsage=emailProtection keyUsage=digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment
d) in the [v3_ca] section, remove the critical option from the basicConstraints parameter:basicConstraints = CA:true
For what? Honest answer: I don’t know. However, all the examples of root certificates that I downloaded while trying to figure out the topic were without a critical sign. I am asking the question “for what?” more experienced colleagues.
e) optionally set the default values that will be offered when issuing self-signed certificates and generating requests for issuing client certificates. These parameters are located in the [req_distinguished_name] section .
The parameter with the _default postfix is the default value. Example:countryName = Country Name (2 letter code) countryName_default = AU countryName_min = 2 countryName_max = 2
When the system asks you to enter the countryName parameter , it will indicate in square brackets that it will leave the value AU by default.
This completes the configuration of the OpenSSL config. It remains to indicate to OpenSSL that it is necessary to use it. To do this, set the environment variable OPENSSL_CONF:export OPENSSL_CONF=/path/to/your/openssl.cnf
- We create a directory structure where information about our CA will be stored.
To do this, go to the created directory with just edited openssl.cnf, and perform the following steps:
a) create subdirectories in it:
demoCA
demoCA / private
demoCA / newcerts
Note: the demoCA name is written in the [CA_default] section of the openssl.cnf file. You can change it (in step 2) and then work with it instead of demoCA.
b) in the demoCA directory, create an empty index.txt file and a serial file, which we open with a text editor and write line 01 there. This is the counter of issued certificates. After the issuance of each next certificate, the value in this file increases by one. - We optionally format our token using the rtAdmin utility. Now everything is ready for deployment of the CA.
The algorithm of actions in general is simple:
a) we issue the root certificate of the certification center using the GOST algorithm:- generate a private key to issue a self-signed CA certificate
- generate a self-signed X509 certificate using the generated key
b) on each of the USB tokens- generate a key pair (the so-called private key container)
- generate a certificate signing request using the generated token key
- issue a certificate for this request
- save the certificate on the token in the private key container
The following is the implementation of this algorithm for one token:
Generating a private key for a CA certificate (we use the GOST algorithm):openssl genpkey -algorithm gost2012_256 -pkeyopt paramset:A -outform PEM -out demoCA/private/cakey.pem
We issue a self-signed CA certificate:openssl req -new -x509 -key demoCA/private/cakey.pem -out demoCA/certs/cacert.pem -extensions v3_ca -days +3650 -outform PEM
Please note: we indicated on the command line that it is necessary to use the v3_ca extensions from the openssl_cnf config. It is there that it is our CA. Validity of 10 years. A common thing for CA. But more is possible.
In the process of issuing the certificate, the system will ask you to enter the values of the parameters that are in the [req_distinguished_name] section of our openssl.cnf file .
Now we proceed with the token operations. If the token is new or formatted with default values, then the user's PIN on it is 12345678. I proceed from the assumption that this is exactly so. Otherwise, you must specify the correct user PIN and generally try to ensure that in the examples below the names of objects already existing on the token do not overlap with the entered ones.
First of all, we will generate a key pair. OpenSSL is not able to perform this operation on Rutoken, so we will use the pkcs11-tool utility from the OpenSC package:pkcs11-tool --module /path/to/your/librtpkcs11ecp.so --login --pin 12345678 --keypairgen --key-type GOSTR3410:A --id 303030303031 --label 'client01'
Important note: we specified id 303030303031. Every two digits of this id are nothing more than the ASCII code of the characters “0” and “1”, respectively. For operations with OpenSSL, it will look like "id = 000001" We
generate a certificate request:openssl req -utf8 -new -keyform engine -key 'pkcs11:id=000001' -engine rtengine -out demoCA/newcerts/client01.csr
If everything was done correctly, then the system- request a PIN
- will request certificate name parameters (from the [req_distinguished_name] section )
- will issue a certificate signing request file
Using this request, we sign a client certificate ( in the example, the certificate is valid for 1825 days. It is important that this period does not exceed the validity period of your root certificate ):openssl ca -utf8 -days +1825 -keyfile demoCA/private/cakey.pem -cert demoCA/certs/cacert.pem -in demoCA/newcerts/client01.csr -outdir demoCA/newcerts -out demoCA/certs/client01.pem
The system will display the certificate, ask about the decision to sign it (answer “y”), and about the decision to save the new certificate (again answer “y”).
We save the received certificate for the token:pkcs11-tool --module /path/to/your/librtpkcs11ecp.so --login --pin 12345678 --id=303030303031 -w demoCA/certs/client01.pem -y cert
All.
Testing the created “miracle”. To do this, we sign and verify the signature of the phrase "Hello, world!":echo Hello,world! | openssl cms -nodetach -sign -signer demoCA/certs/client01.pem -keyform engine -inkey "pkcs11:id=000001" -engine rtengine -binary -noattr -outform PEM | openssl cms -verify -CAfile demoCA/certs/cacert.pem -inform PEM
If everything is done correctly, the system will request a PIN, sign the message, then verify the signature and, if successful, will display the original message and the verification result (“success”)
Note . Returning to the title task and signing using the plugin, it should be noted that by default the plugin gives the result of signing not in PEM format, but in DER format, encoded in base64. Therefore, to verify the signature, you must first decode from base64, and when checking specify the input DER format.
Good luck!