Добрый день!
У нас возникла необходимость работы с ГОСТовыми сертификатами на Linux, а именно написание shared library (*.so) для генерации сертификата.
Для решения задачи сначала был выбран следующий подход, назову его №1:
Написание консольного приложения с++ с использованием статических библиотек libcrypto.a и libssl.a.
Приложение написать получилось и она генерирует сертификат.
Далее приступил к переделыванию консольного приложения в shared library.
Здесь возникла проблема - shared library требует, чтобы исходные объектные файлы компилировались c флагом -fPIC (Position Independent Code). Видимо объектные файлы из библиотек libcrypto.a и libssl.a компилировались без данного флага.
Как результат было решено, что этот подход тупиковый.
Далее было решено перейти к подходу №2:
Написание консольного приложения с++ с использованием динамических библиотек libcrypto.so, libssl.so, kncagost.so.
Проект вроде как компилируется, но выдает ошибку Unable to get "kncagost" engine.
Т.е. получается, что ENGINE_load_builtin_engines() не загружает ГОСТовый движок.
На всякий случай пробовал писать вместо этой функции ENGINE_load_gost(), но ее, при использовании динамических библиотек, компилятор вообще не распознает.
Нужна помощь:
1) Либо получить от вас статические библиотеки скомпилированные с флагом -fPIC
или
2) Объяснить, что у меня не так делается при использовании динамических библиотек
Привожу код, который я пытаюсь скомпилировать (со статическими библиотеками она работает отлично):
#include <iostream>
#include <openssl/engine.h>
#include <openssl/x509.h>
#include <openssl/pkcs12.h>
#include <openssl/pem.h>
/* Generates GOST key */
EVP_PKEY * generate_gost_key(ENGINE *engine) {
// Приватный ключ
EVP_PKEY *pkey = NULL;
// Контекст приватного ключа
EVP_PKEY_CTX *pkey_ctx = NULL;
// Создание контекста
pkey_ctx = EVP_PKEY_CTX_new_id(NID_id_Gost34310_2004, engine);
if (!pkey_ctx) {
std::cerr << "Unable to create 'NID_id_Gost34310_2004' context." << std::endl;
goto cleanup;
}
EVP_PKEY_paramgen_init(pkey_ctx);
EVP_PKEY_CTX_ctrl(pkey_ctx,
NID_id_Gost34310_2004,
EVP_PKEY_OP_PARAMGEN,
// Some kind of magic...
// Здесь должен быть макрос EVP_PKEY_CTRL_GOST_PARAMSET (EVP_PKEY_ALG_CTRL+1)
EVP_PKEY_ALG_CTRL + 1,
NID_id_Gost34310_2004_PKIGOVKZ_A_ParamSet,
NULL);
if (EVP_PKEY_keygen_init(pkey_ctx) <= 0) {
std::cerr << "Unable to init keygen." << std::endl;
goto cleanup;
}
/* Generate key */
if (EVP_PKEY_keygen(pkey_ctx, &pkey) <= 0) {
std::cerr << "Unable to generate keys." << std::endl;
goto cleanup;
}
cleanup:
if (pkey_ctx) {
EVP_PKEY_CTX_free(pkey_ctx);
}
return pkey;
}
/* Generates a self-signed x509 certificate. */
X509 * generate_x509(EVP_PKEY * pkey, const EVP_MD *md)
{
/* Allocate memory for the X509 structure. */
X509 * x509 = X509_new();
/* Set the serial number. */
ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
/* This certificate is valid from now until exactly one year from now. */
X509_gmtime_adj(X509_get_notBefore(x509), 0);
X509_gmtime_adj(X509_get_notAfter(x509), 31536000L);
/* Set the public key for our certificate. */
X509_set_pubkey(x509, pkey);
/* We want to copy the subject name to the issuer name. */
X509_NAME * name = X509_get_subject_name(x509);
/* Set the country code and common name. */
X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *)"CA", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *)"MyCompany", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)"localhost", -1, -1, 0);
/* Now set the issuer name. */
X509_set_issuer_name(x509, name);
/* Actually sign the certificate with our key. */
if (!X509_sign(x509, pkey, md))
{
std::cerr << "Error signing certificate." << std::endl;
X509_free(x509);
return NULL;
}
return x509;
}
bool save_pem(EVP_PKEY * pkey, X509 * x509)
{
bool result = false;
FILE * pkey_file = fopen("key.pem", "wb");
FILE * x509_file = fopen("cert.cer", "wb");
if (!pkey_file) {
std::cerr << "Unable to open \"key.pem\" for writing." << std::endl;
goto cleanup;
}
if (!x509_file) {
std::cerr << "Unable to open \"cert.cer\" for writing." << std::endl;
goto cleanup;
}
if (!PEM_write_PrivateKey(pkey_file, pkey, NULL, NULL, 0, NULL, NULL)) {
std::cerr << "Unable to write private key to disk." << std::endl;
goto cleanup;
};
if (!PEM_write_X509(x509_file, x509)) {
std::cerr << "Unable to write x509 to disk." << std::endl;
goto cleanup;
}
result = true;
cleanup:
if (pkey_file) {
fclose(pkey_file);
}
if (x509_file) {
fclose(x509_file);
}
return result;
}
bool save_pkcs12(EVP_PKEY * pkey, X509 * x509) {
bool result = false;
PKCS12 * p12 = NULL;
FILE * p12_file = NULL;
char pass[128] = "123456";
p12 = PKCS12_create(pass, NULL, pkey, x509, NULL, 0, 0, PKCS12_DEFAULT_ITER, 1, NID_key_usage);
if (!p12) {
std::cerr << "Error creating pkcs12." << std::endl;
goto cleanup;
}
p12_file = fopen("cert.p12", "wb");
if (i2d_PKCS12_fp(p12_file, p12) != 1) {
std::cerr << "Error saving cert.p12." << std::endl;
// OpenSSL Error. Use `ERR_peek_last_error_line` to find out more.
goto cleanup;
}
result = true;
cleanup:
if (p12) {
PKCS12_free(p12);
}
if (p12_file) {
fclose(p12_file);
}
return result;
}
int main()
{
ENGINE *engine = NULL;
EVP_PKEY *pkey = NULL;
X509 * x509 = NULL;
const EVP_MD *md;
//ENGINE_load_openssl(); //эту строку и добавлял и удалял. Со статическими библиотеками
//приложение работает и без этой строки
//ENGINE_load_gost(); // эту строку использую в случае со статическими библиотеками.
ENGINE_load_builtin_engines(); //эта строка для случая динамических библиотек
OpenSSL_add_all_algorithms(); // Без этого не создастся pkcs12
//ENGINE_register_all_pkey_asn1_meths(); //эту строку так же и удалял и добавлял. Приложение
//со статическими библиотеками работает и без этой строки
engine = ENGINE_by_id("kncagost"); // Ищем нуцовский гост
if (!engine) {
std::cerr << "Unable to get 'kncagost' engine." << std::endl;
goto cleanup;
}
pkey = generate_gost_key(engine);
if (!pkey) {
std::cout << "Unable to generate key";
goto cleanup;
}
md = EVP_get_digestbyname(SN_id_Gost34311_95);
if (!md) {
std::cerr << "Unable to get 'md_gost95' md." << std::endl;
goto cleanup;
}
x509 = generate_x509(pkey, md);
if (!x509) {
std::cout << "Unable to generate x509";
goto cleanup;
}
save_pem(pkey, x509);
save_pkcs12(pkey, x509);
cleanup:
if (engine) {
ENGINE_free(engine);
}
if (x509) {
X509_free(x509);
}
if (pkey) {
EVP_PKEY_free(pkey);
}
return 0;
}
Компилирую командой:
g++ -c -fPIC CertDLL CertDLL.cpp -I/opt/certgen/Linux/dynamic/x64/include
Линкую командой:
g++ -o CertDLL СertDLL.o -l:libcrypto.so.1.0.0 -l:libssl.so.1.0.0 -l:libknca-pkcs11-x64.so -l:libkncagost.so -L/opt/certgen/Linux/dynamic/x64 -L/opt/certgen/Linux/dynamic/x64/engines -Wl,-rpath=/opt/certgen/Linux/dynamic/x64:/opt/certgen/Linux/dynamic/x64/engines
добавлял эти пути также в LD_LIBRARY_PATH
при проверке командой ldd CertDLL выдает следующее:
linux-vdso.so.1 => (0x00007ffea2752000)
libcrypto.so.1.0.0 => /opt/certgen/Linux/dynamic/x64/libcrypto.so.1.0.0 (0x00007fc62d95c000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fc62d5da000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc62d210000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fc62d00c000)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fc62cdf2000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fc62cae9000)
/lib64/ld-linux-x86-64.so.2 (0x00007fc62dd47000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fc62c8d3000)
Как будто остальные библиотеки вообще не линкуются, так обычно происходит когда они нигде по коду не используются.
Короче говоря я запутался очень конкретно.