It is difficult to find information on the OpenSSL API library on the Russian-language Internet. Much attention is paid to using console commands to manipulate self-signed certificates for web servers or OpenVPN servers.
This approach is good when you need to make a couple of certificates per hour. And if you need to create a couple of hundred in a minute? Or write a script and parse the output from the console? And if an error occurred in the process?
Using the API, generating certificates, validating and signing is much easier. It becomes possible to monitor and handle errors at all stages of work, as well as specify additional certificate parameters (since not all parameters can be set from the console) and fine-tune it.
Separately, it is worth noting the network component. If the certificate is and simply lies on the disk, it is useless.
Unfortunately, there is very little Russian documentation on the organization of an SSL server, on how to organize an SSL client to receive data. Official documentation is not so complete and good, so that you can immediately get involved in working with the library. Not all functions are described in detail, you have to experiment with the parameters, in what sequence and what exactly needs to be cleaned, and what the library will remove on its own.
This article is a compilation of my experience with the OpenSSL library when implementing a client-server application. The functions described in it will work both on the desktop and on Android devices. Attached to the article is a repository with C / C ++ code so that you can see the work of the described functions.
When studying a new library or technology, I try to solve problems with the help of a new functional. In this case, try to make MITM
to intercept traffic to the HTTPS server.
Formulate the requirements for the program:
Wait for port connections (SSL server)
When an incoming connection appears:
Since we will have an SSL server, we will need a certificate from a certificate authority and a certificate for our server.
Let this data be generated by our program, and the CA certificate will be uploaded to a file in the working folder of the program.
Development will be conducted on Ubuntu, other tools: GCC compiler 5.4.0, OpenSSL 1.0.2, curl 7.52.1, CMake 3.8.1 (the only one not from the packages).
To send requests to our application, we will use curl from the console. Since we need to specify a CA certificate, the command will look like this:
curl --cacert ca.crt -v https://127.0.0.1:5566 -H "Host: taigasystem.com"
Specifying the Host header is required in order for curl to correctly compose an HTTP request. Without this, the server will respond with an error.
To work with the OpenSSL library, you need to initialize it. Use the following code:
#include <openssl/bio.h> #include <openssl/ssl.h> #include <openssl/err.h> ... void InitOpenSSL() { OpenSSL_add_all_algorithms(); ERR_load_BIO_strings(); ERR_load_crypto_strings(); SSL_load_error_strings(); SSL_library_init(); }
Before completing the application, you should clean the library, for this you can use the following code:
void ClearOpenSSL() { EVP_cleanup(); CRYPTO_cleanup_all_ex_data(); ERR_remove_thread_state(NULL); ERR_free_strings(); }
Most OpenSSL library operations require context. This structure, which stores the algorithms used, their parameters and other data. It is created using the function:
SSL_CTX *SSL_CTX_new(const SSL_METHOD *method);
The list of methods that can be passed to this function is quite extensive, but the documentation tells us that we need to use SSLv23_server_method()
for the server and SSLv23_client_method()
for the client.
At the same time, the library will automatically select the most secure protocol supported by the client and server.
Here is an example of creating a context for a client:
SSL_CTX *ctx = NULL; ctx = SSL_CTX_new(SSLv23_client_method()); if (ctx == NULL) { // }
To remove the context correctly, use the SSL_CTX_free
function.
I do not really like to use SSL_CTX_free
every time the context needs to be deleted. You can use smart pointers with the delete function or wrap the structure in a RAII class:
std::shared_ptr<SSL_CTX> m_ctx(ctx, SSL_CTX_free);
Most of the OpenSSL library functions return 1 as a sign of successful execution. Here is the usual code to check for an error:
if (SSL_CTX_load_verify_locations(ctx, fileName, NULL) != 1) { // }
However, sometimes this is not enough, and sometimes a more detailed description of the problem is needed. To do this, OpenSSL uses a separate message queue for each stream. To retrieve the error code from the queue, use the ERR_get_error()
function.
The error code itself is not very user-friendly, so you can use the ERR_error_string
function to get a string representation of the error code. If the function returns 0 , it means that there is no error.
Here is an example of getting a string describing an error by an error code:
#include <openssl/err.h> ... // - OpenSSL std::cerr << ERR_error_string(ERR_get_error(), NULL) << std::endl; ...
The second parameter of the ERR_error_string
function is a pointer to a buffer that must be at least 120 characters long. If you do not specify it, the static buffer will be used, which is overwritten each time this function is called.
It is worth noting that for each separate thread a separate queue of error messages is created.
Now, in order to organize an OpenSSL server, we need to create a certificate of certification authority and a server certificate. For each of them we need to create keys for the signature.
OpenSSL uses the EVP_PKEY
structure to store the private / public key EVP_PKEY
. This structure is created as follows:
EVP_PKEY *pkey = NULL; pkey = EVP_PKEY_new(); if (pkey == NULL) { // }
Read more about EVP here .
Inverse for EVP_PKEY_new
function frees the memory and deletes the EVP_PKEY
structure.
Now it is necessary to prepare the BIGNUM
structure for generating RSA (for more information about this structure, see here ):
BIGNUM *big = NULL; big = BN_new(); if (big == NULL) { // } else if (BN_set_word(big, RSA_F4) != 1) { // BN_free(big); }
The BN_set_word
function sets the size for the BIGNUM
structure. Valid values are RSA_3
and RSA_F4
, the latter is preferred.
The turn has come for key generation. To do this, create an RSA
structure:
RSA *rsa = NULL; rsa = RSA_new(); if (rsa == NULL) { // }
Now the key generation itself:
if (RSA_generate_key_ex(rsa, 4096, big, NULL) != 1) { // }
4096 is the size of the key we want to receive.
We finish the generation of keys by writing new keys to the EVP_PKEY
structure:
if (EVP_PKEY_assign_RSA(pkey, rsa) !=1) { // }
PEM is a fairly simple format for storing keys and certificates. It is a text file in which records of the following type are stored sequentially:
-----BEGIN RSA PRIVATE KEY----- MIIJJwIBAAKCAgEAvNwgYmIyfvY6IsVZwRCkAHTOhwE3Rp/uNcUoTcPl5atOwPVW JLY3odYmILsa8se7B/aNNzO7AlvXwlzxinQ3AF7l37LqGzf8v16TFVN4kit8vrq0 V9bBXHpiWH+YQT4gBVmSkwqEMZ/wQlUOIxz4Q2M7cXRu4fRe3rt3kGHCPJ66Ybax yEp6nfdK8IKsyxqAXjBkqfC5rkdw2n7UAd/OnPRCDowyvythDb8jR1LkbJjlIatK .... yajhmBDpS11hzuWHhDmpjbrV79OMRzKQAWBKRubObtGIsFB2CzbabusV+oq/Y78y OxriZYqoRv3WB5GH/pPO9w1ptveddLU33NVBSRfFS1jyqyj/1CqXlE4gcQ== -----END RSA PRIVATE KEY----- -----BEGIN CERTIFICATE----- MIIFkTCCA3mgAwIBAgIJAMPIqA2oVd/SMA0GCSqGSIb3DQEBCwUAMF8xCzAJBgNV BAYTAlJVMQ8wDQYDVQQIDAZNb3Njb3cxDzANBgNVBAcMBk1vc2NvdzEUMBIGA1UE ... bt9NHGnCxYcParG+YqU5UTUrCUGUfnZhJAX+qkgsVSC5c81Tk0VXTQx3EiEvdzV+ wUX9LMRLIxjy1D5AO6a29LkzNAvw+iFm36VO+ssdkJW4Q6MAYA== -----END CERTIFICATE-----
It is worth noting that the number of characters ----- at the beginning of the title and at the end, as well as in the closing line, should be the same.
In more detail this format is described here:
- RFC1421 Part I: Message Encryption and Authentication Procedures
- RFC1422 Part II: Certificate-Based Key Management
- RFC1423 Part III: Algorithms, Modes, and Identifiers
- RFC1424 Part IV: Key Certification and Related Services
Suppose we have a public / private key pair in the EVP_PKEY structure, then to write them to a file, use the PEM_write_PrivateKey
and PEM_write_PUBKEY
.
Here is an example of using these functions:
FILE *f = fopen("server.pem", "wb"); if (!PEM_write_PrivateKey(f, key, NULL, NULL, 0, 0, NULL)) { // fclose(f); } else if (!PEM_write_PUBKEY(f, key)) { // fclose(f); } fclose(f);
It is worth giving some explanations regarding the function
int PEM_write_PrivateKey(FILE *fp, EVP_PKEY *x, const EVP_CIPHER *enc, unsigned char *kstr, int klen, pem_password_cb *cb, void *u);
where const EVP_CIPHER *enc
is a pointer to an encryption algorithm for encrypting a private key before it is saved.
For example, EVP_aes_256_cbc()
means "AES with a 256-bit key in CBC".
There are a lot of encryption algorithms, and you can always
pick something to your liking. Relevant definitions can be found in openssl/evp.h
unsigned char *kstr
expects to get a pointer to a string with a password to encrypt the key, and int klen
is the length of this string.
If kstr
and klen
, the cb
and u
parameters are ignored, where: - cb is a pointer to a function of the form:
int cb(char *buf, int size, int rwflag, void *u);
- buf - pointer to the buffer for recording the password
- size - maximum password size (i.e. buffer size)
- rwflag is 0 for reading and 1 for writing
The result of the function is the password length or 0 in case of an error.
The void *u
parameter for both functions is used to transfer additional data. For example, as a pointer to a window for a GUI application.
Keys are PEM_read_PrivateKey
using the PEM_read_PrivateKey
and PEM_read_PUBKEY
. Both functions have the same parameters and return value:
EVP_PKEY *PEM_read_PUBKEY(FILE *fp, EVP_PKEY **x, pem_password_cb *cb, void *u); EVP_PKEY *PEM_read_PrivateKey(FILE *fp, EVP_PKEY **x, pem_password_cb *cb, void *u);
Where:
- FILE *fp
- open file descriptor
- EVP_PKEY **x
- structure that must be overwritten
- pem_password_cb *cb
- the function to get the key decryption password
- void *u
- string with key password, ending with \0
Here is an example of a function to get a password to decrypt a key:
int pass_cb(char *buf, int size, int rwflag, void *u) { int len; char *tmp; if (rwflag == 1) std::cout << " " << (char*)u << ": "; else std::cout << " : "; std::string pass; std::cin >> pass; if (pass.empty() || pass.length() <=0) return 0; len = pass.length(); if (len > size) len = size; memcpy(buf, pass.c_str(), len); return len; }
Here is an example of how you can load an unencrypted private key from a file:
FILE *f = NULL; f = fopen(fileName.c_str(), "rb"); if (f == NULL) { // } EVP_PKEY *key = NULL; key = PEM_read_PrivateKey(f, NULL, NULL, NULL); if (key == NULL) { // } fclose(f);
Sometimes it is convenient to store a key or certificate as a constant in the program. For such cases, you can use the structure type BIO . This structure and its associated functions repeat the I / O functionality for FILE
.
This is how you can load the key from memory:
const char *key = "-----BEGIN RSA PRIVATE KEY-----\n" "MIIJKAIBAAKCAgEA40vjOGzVpuJv+wIfNBQSr9U/EeRyvSy/L6Idwh799LOPIwjF\n" ..... "zkxvkGMPBY3BcSPjipuydWTt8xE8MOe0SmEcytHZ/DifwF9qyToDlTFOUN8=\n" "-----END RSA PRIVATE KEY-----"; BIO *buf = NULL; buf = BIO_new_mem_buf(key, -1); if (buf == NULL) { // } EVP_PKEY *pkey = NULL; pkey = PEM_read_bio_PrivateKey(buf, NULL, NULL, NULL); if (pkey == NULL) { // }
Now that we can create keys, let's see how to create certificates. Certificates can be self-signed or have a signature from a certification authority. To obtain a certificate signed by the certification authority, you must create a certificate request ( CSR ) and send it to the certification authority. In response, he will send a signed certificate.
In the case when we want to create a self-signed certificate or a certificate for our own certifying center, you do not need to create a CSR , you can immediately go to the Certificates section.
Certificate Signing Request (CSR) is a message or request that the certificate issuer sends to the certification authority (CA) and which contains information about the public key, country of issue, as well as the digital signature of the creator.
To create a CSR, we need the key EVP_PKEY
created earlier. It all starts with memory allocation for the CSR structure:
X509_REQ *req = NULL; req = X509_REQ_new(); if (req == NULL) { // }
The inverse function to X509_REQ_new
is X509_REQ_free
.
Now you need to set the version of the certificate. In this case, the version is 2:
if (X509_REQ_set_version(req, 2) != 1) { // X509_REQ_free(req); }
According to the X.509 standard , this version should be one less than the certificate version. Those. For certificate version 3, the number 2 should be used.
Now we will set the data of the request creator. We will use the following fields:
- - two-letter country code, for example, RU
- ST - region, in our case Moscow
- L - city, again Moscow
- O - organization, for example Taigasystem
- CN is a domain name, taigasystem.com will be for us
This is how these fields are specified in the query:
X509_NAME *name = X509_REQ_get_subject_name(req); if (X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (const unsigned char *)"RU", -1, -1, 0) != 1) { // X509_REQ_free(req); } if (X509_NAME_add_entry_by_txt(name, "ST", MBSTRING_ASC, (const unsigned char *)"Moscow", -1, -1, 0) != 1) { // } if (X509_NAME_add_entry_by_txt(name, "L", MBSTRING_ASC, (const unsigned char *)"Moscow", -1, -1, 0) != 1) { // } if (X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (const unsigned char *)"Taigasystem", -1, -1, 0) != 1) { // } if (X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (const unsigned char *)"taigasystem.com", -1, -1, 0) != 1) { // }
Note that we first get the X509_NAME
structure from the CSR request structure and set the values for it.
Now you need to set the public key for this request:
if (X509_REQ_set_pubkey(req, key) != 1) { // }
The final touch is the request signature:
if (X509_REQ_sign(req, key, EVP_sha256()) <= 0) { // }
Unlike other OpenSSL functions, X509_REQ_sign
returns
signature size in bytes, not 1, on successful completion, and 0 in the case of an error.
Now the certificate request is ready.
Saving a CSR to a file is quite simple. You need to open the file, and then call the function PEM_write_X509_REQ
:
FILE *f = NULL; f = fopen("server.csr", "wb"); if (PEM_write_X509_REQ(f, csr) != 1) { // fclose(f); } fclose(f);
As a result, we get the following text in the server.csr file:
-----BEGIN CERTIFICATE REQUEST----- MIICnzCCAYcCAQEwWjELMAkGA1UEBhMCUlUxDzANBgNVBAgMBlJ1c3NpYTEPMA0G ... fbzFJ6EM00mbyr472lEXZpvdZgBCfxpkNDyp9nsiIQf0EyC05MgufOAKDT/fGQfa 4gWK -----END CERTIFICATE REQUEST-----
To download CSR , use the following functions:
X509_REQ *PEM_read_X509_REQ(FILE *fp, X509_REQ **x, pem_password_cb *cb, void *u); X509_REQ *PEM_read_bio_X509_REQ(BIO *bp, X509_REQ **x, pem_password_cb *cb, void *u);
The first one loads the CSR from the file, the second one allows to load the CSR from memory.
Parameters of the data function are similar to loading keys from a PEM file , for
with the exception of pem_password_cb
and u
, which are ignored.
X.509 is one of the variations of the ASN.1 language, standardized in rfc2459 .
You can read more about X.509 format here and here .
You can generate a certificate without using CSR . This is useful for creating a CA.
To generate a certificate without CSR, we need a public / private key pair in the EVP_PKEY
structure. We start by allocating memory for the certificate structure:
X509 *x509 = X509_new(); if (!x509) //
The reverse for X509_new
is X509_free
.
The certificate is created in the same way as the CSR request, with only one difference - in addition to the version and data of the publisher, you must specify the serial number of the certificate.
You also need to use other functions to access certificate data:
- X509_set_version
instead of X509_REQ_set_version
- X509_get_subject_name
instead of X509_REQ_get_subject_name
- X509_set_pubkey
instead of X509_REQ_set_pubkey
- X509_sign
instead of X509_REQ_sign
Thus, it becomes quite simple to distinguish by name the objects for which certain functions are intended.
Now you can set the serial number of the certificate:
ASN1_INTEGER *aserial = NULL; aserial = M_ASN1_INTEGER_new(); ASN1_INTEGER_set(aserial, 1); if (X509_set_serialNumber(x509, aserial) != 1) { // }
For each new certificate, you must create a new serial number.
Now it's time to set the certificate lifetime. For this, two parameters are set - the beginning and the end of the certificate life:
if (!(X509_gmtime_adj(X509_get_notBefore(cert), 0))) { // X509_free(cert); } // 31536000 * 3 = 3 year valid period if (!(X509_gmtime_adj(X509_get_notAfter(cert), 31536000 * 3))) { // X509_free(cert); }
The beginning of the life of the certificate will be the moment of its release - the value 0 for the function X509_get_notBefore
. The end of the certificate life is set by the X509_get_notAfter
function.
The final touch is the signature of the certificate using the private key:
EVP_PKEY *key; // Not null if (X509_sign(cert, key, EVP_sha256()) <=0) { long e = ERR_get_error(); if (e != 0) { // X509_free(cert); } }
There is an interesting feature here: the X509_sign
function returns the signature size in bytes, if everything went well, and 0 in case of an error. Sometimes the function returns zero, even if there is no error. Therefore, it is necessary to introduce an additional error check here.
To generate a CSR certificate, we need the CA private key to sign the certificate, the CA certificate to set the publisher data and the CSR request itself.
Creating the certificate itself and installing the version and number are the same as for the certificate without CSR. The difference appears when you need to extract the publisher data from the CSR request and install it in the certificate:
X509_REQ *csr; //not null X509_NAME *name = NULL; name = X509_REQ_get_subject_name(csr); if (name == NULL) { // X509_free(cert); } if (X509_set_subject_name(cert, name) != 1) { // X509_free(cert); }
After that, you need to fix the data of the issuer of the certificate. This requires a CA certificate:
X509 *CAcert; //not null name = X509_get_subject_name(CAcert); if (name == NULL) { // X509_free(cert); } if (X509_set_issuer_name(cert, name) != 1) { // X509_free(cert); }
It can be seen that we set the data from the CSR using X509_set_subject_name
, and the CA data using X509_set_issuer_name
.
The next step is to get the public key from the CSR and install it into the new certificate.
In addition to installing the key, you can immediately check whether the CSR was signed with this key:
// Get pub key from CSR EVP_PKEY *csr_key = NULL; csr_key = X509_REQ_get_pubkey(csr); if (csr_key == NULL) { // } // Verify CSR if (X509_REQ_verify(csr, csr_key) !=1) { // X509_free(cert); } // Set pub key to new cert if (X509_set_pubkey(cert, csr_key) != 1) { // X509_free(cert); }
Now you can set the serial number of the certificate:
ASN1_INTEGER *aserial = NULL; aserial = M_ASN1_INTEGER_new(); ASN1_INTEGER_set(aserial, 1); if (X509_set_serialNumber(cert, aserial) != 1) { // }
The final touch is to sign the certificate using the CA's private key:
EVP_PKEY *CAkey; // Not null if (X509_sign(cert, CAkey, EVP_sha256()) <=0) { long e = ERR_get_error(); if (e != 0) { // X509_free(cert); } }
After signing our new certificate is ready.
Saving is pretty simple:
X509 *cert; ... FILE *f = NULL; f = fopen("server.crt", "wb"); if (!PEM_write_X509(f, cert)) { // fclose(f); } fclose(f);
Loading takes place using two functions:
X509 *PEM_read_X509(FILE *fp, X509 **x, pem_password_cb *cb, void *u); X509 *PEM_read_bio_X509(BIO *bp, X509 **x, pem_password_cb *cb, void *u);
The parameters are described above .
Connecting to a host using SSL sockets is not very different from a normal TCP connection.
First you need to create a TCP connection to the server:
// IP struct hostent *ip = nullptr; ip = gethostbyname(host.c_str()); if (ip == nullptr) { // } // int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == -1) { // } struct sockaddr_in dest_addr; memset(&dest_addr, 0, sizeof(struct sockaddr_in)); dest_addr.sin_family = AF_INET; dest_addr.sin_port = htons(port); dest_addr.sin_addr.s_addr = *(long *)(ip->h_addr); //: if (connect(sock, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr)) == -1) { // }
Now you need to create a client-side SSL- context:
const SSL_METHOD *method = SSLv23_client_method(); SSL_CTX *ctx = NULL; ctx = SSL_CTX_new(method); if (ctx == NULL) //
Now you need to install the resulting socket in the SSL- structure. It is derived from the context:
SSL *ssl = SSL_new(ctx); if (ssl == NULL) { // } if (SSL_set_fd(ssl, sock) != 1) { // }
The final touch is the connection itself:
if (SSL_connect(ssl) != 1) { // }
To read data from an SSL socket, use the function:
int SSL_read(SSL *ssl, void *buf, int num);
It reads data from a socket bound to an SSL structure to the buffer. If you need to know if there is data in the socket buffer that needs to be read, you can use the function
int SSL_pending(const SSL *ssl);
Those. for reading you can use the following construction:
const int size = SSL_pending(ssl); char *buf = new char[size]; memset(buf, 0, size); if (SSL_read(ssl, buf, size) <= 0) { // }
As a result, already decoded data will be in the buffer.
For recording, use the function:
int SSL_write(SSL *ssl, const void *buf, int num);
At the entrance, it receives a pointer to the SSL structure that will record, the data itself, and their size.
Here is an example of such a record:
char buf[] = "12345678" if (SSL_write(ssl, buf, strlen(buf)) <= 0) { // }
The server part is similar to the client part - it also needs to get SSL- context, and in it - the same functions of reading the record. The difference is that the server must prepare the context in order to give the client a certificate, as well as arrange a handshake with the client.
Let's start by preparing the context:
const SSL_METHOD *method = SSLv23_server_method(); SSL_CTX *ctx = NULL; ctx = SSL_CTX_new(method); if (ctx == NULL) { // }
Documentation tells us that the choice of SSLv23_server_method
will allow the library to independently determine the most secure version of the protocol supported by the client.
If you need to enable or disable a specific version or change other settings, you can use the SSL_set_options
function. Documentation for it can be found here .
We considered loading of the X.509 certificate and loading of keys a bit earlier, therefore we believe that we already have a couple of these structures.
Install the certificate and key for the server context:
X509 *serverCert; //not null EVP_PKEY *serverKey; //not null if (SSL_CTX_use_certificate(ctx, serverCert) != 1) { // } if (SSL_CTX_use_PrivateKey(ctx, serverKey) != 1) { // }
Our server is ready to accept incoming connections. Let the usual accept
. We will be interested in the socket received from this function.
We start with the fact that for each such socket, we need a new SSL structure:
SSL *ssl = NULL; ssl = SSL_new(ctx); if (ssl == NULL) { // }
Now we install our socket into this structure:
int sock; //accepted tcp socket if (SSL_set_fd(ssl, sock) != 1) { //Hadle error }
The handshake mechanism itself:
if (SSL_accept(ssl) != 1) { // }
: , . , :
int ret = SSL_accept(ssl); if (ret == 0) { // } if (ret < 0) { unsigned long error = ERR_get_error(); if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE || error == 0) { // , // } else { // } } if (ret == 1) { // }
- .
:
mkdir build cd build cmake .. make
(build) :
./openssl_api
ca.crt —
.
,
cppurl --cacert ca.crt -v https://127.0.0.1:5566 -H "Host: taigasystem.com"
CA , -v . -H "Host: taigasystem.com" , GET - Host. , 404- .
curl ( ):
$ curl --cacert ca.crt -v https://127.0.0.1:5566 -H "Host: taigasystem.com" * Rebuilt URL to: https://127.0.0.1:5566/ * Trying 127.0.0.1... * Connected to 127.0.0.1 (127.0.0.1) port 5566 (#0) * found 1 certificates in ca.crt * found 700 certificates in /etc/ssl/certs * ALPN, offering http/1.1 * SSL connection using TLS1.2 / RSA_AES_128_GCM_SHA256 * server certificate verification OK * server certificate status verification SKIPPED * common name: 127.0.0.1 (matched) * server certificate expiration date OK * server certificate activation date OK * certificate public key: RSA * certificate version: #3 * subject: C=RU,CN=127.0.0.1,L=Moscow,O=Taigasystem,ST=Moscow * start date: Mon, 28 Aug 2017 07:36:42 GMT * expire date: Thu, 27 Aug 2020 07:36:42 GMT * issuer: C=RU,CN=127.0.0.1,L=Moscow,O=Taigasystem,ST=Moscow * compression: NULL * ALPN, server did not agree to a protocol > GET / HTTP/1.1 > Host: taigasystem.com > User-Agent: curl/7.47.0 > Accept: */* > < HTTP/1.1 200 OK < Server: nginx/1.4.6 (Ubuntu) < Date: Mon, 28 Aug 2017 07:39:18 GMT < Content-Type: text/html; charset=utf-8 < Transfer-Encoding: chunked < Connection: keep-alive < Vary: Accept-Language, Cookie < X-Frame-Options: SAMEORIGIN < Content-Language: ru < Strict-Transport-Security: max-age=604800 < ....
: -, ; -, .
curl () :
$ ./openssl_api OpenSSL 'curl --cacert ca.crt -v https://127.0.0.1:5566 -H "Host: taigasystem.com" ' BIGNUM BIGNUM RSA EVP . : 512 BIGNUM BIGNUM RSA EVP CSR . : 512 . : 512 ca.crt SSL 5566 taigasystem.com 443 taigasystem.com[188.225.73.237]:443 SSL SSL taigasystem.com:443 / ####################################### 79 ####################################### GET / HTTP/1.1 Host: taigasystem.com User-Agent: curl/7.47.0 Accept: */* ####################################### 4096 ####################################### HTTP/1.1 200 OK Server: nginx/1.4.6 (Ubuntu) Date: Mon, 28 Aug 2017 07:39:18 GMT Content-Type: text/html; charset=utf-8 Transfer-Encoding: chunked Connection: keep-alive Vary: Accept-Language, Cookie X-Frame-Options: SAMEORIGIN Content-Language: ru Strict-Transport-Security: max-age=604800 ...
Those. , GET - (curl) .
Many thanks for the research tomasloh
Source: https://habr.com/ru/post/338764/
All Articles