Генерируем собственный сертификат
Генерируем собственный сертификат

В данной статья я хочу рассказать, как работать с X509 сертификатом используя OpenSSL 3.0.0 в С++, начиная от генерации своего сертификата и заканчивая его валидацией.

Так как информации в интернете по этой теме почти нет, то все, что я вам расскажу, я узнал исходя из своего печального опыта работы с этой библиотекой. Я очень надеюсь, что эта статья окажется вам полезной и сможет сохранить ваше время.

В данной статье, я не буду рассказывать вам, что такое X509 сертификат, надеюсь, что это вы уже знаете, а если нет, то ссылка на статью вот тут.

Создаем сертификат

Начнем с простого, создания сертификата. Для начала нам необходимо подключить заголовочный файл <openssl/x509v3.h>. Тут все просто, для создания - вызываем X509_new(), а для очистки памяти - X509_free().

#include <openssl/x509v3.h>
#include <iostream>

int main() {
    std::unique_ptr<X509, decltype(&::X509_free)> certificate(X509_new(), ::X509_free);
    if (certificate == nullptr) {
        std::cerr << "Failed to create certificate" << std::endl;
        return -1;
    }
}

Копируем сертификат

Если у нас уже имеется заполненная структура X509 сертификата, а нам нужно полностью ее скопировать в нашу переменную, то мы можем воспользоваться функцией X509_dup(), в которую нужно передать указатель на сертификат, который мы хотим скопировать.

#include <openssl/x509v3.h>
#include <iostream>

int main() {
    std::unique_ptr<X509, decltype(&::X509_free)> certificate(X509_new(), ::X509_free);
    if (certificate == nullptr) {
        std::cerr << "Failed to create certificate" << std::endl;
        return -1;
    }
  
    std::unique_ptr<X509, decltype(&::X509_free)> duplicate(X509_dup(certificate.get()), ::X509_free);
    if (duplicate == nullptr) {
        std::cerr << "Failed to duplicate certificate" << std::endl;
        return -1;
    }
}

Добавим Serial Number

Давайте добавим нашему сертификату пропертей, начнем с номера, он же serial number. С помощью X509_get_serialNumber получаем указатель на проперть сертификата, а далее, с помощью ASN1_INTEGER_set() выставляем необходимое значение. ASN1_INTEGER_set() вернет 1 на удачный вызов, 0 на завалившийся. Это правило работает для всех функций при работе с сертификатом.

bool setSerialNumber(X509* cert, int32_t serial) {
    bool result = false;
    ASN1_INTEGER* serialNumber = X509_get_serialNumber(cert);
    if (serialNumber != nullptr) {
        const int res = ASN1_INTEGER_set(serialNumber, serial);
        result = res == 1;
    }
    return result;
}

Выставляем версию

Выставим версию нашему сертификату, используя функцию X509_set_version(). На текущий момент есть три версии сертификата, которым соответствуют следующие значения:

  1. 0x0

  2. 0x1

  3. 0x2

bool setVersion(X509* cert, long version) {
    return X509_set_version(cert, version) == 1;
}

Выставляем Subject Name

Используя функцию X509_get_subject_name мы можем получить указатель на subject name нашего сертификата, а вызвав X509_NAME_add_entry_by_txt(), можем его обновить.

bool updateSubjectName(X509* cert, const char* key, const char* value) {
    bool result = false;
    X509_NAME* subjectName = X509_get_subject_name(cert);
    if (subjectName != nullptr) {
        const int res = X509_NAME_add_entry_by_txt(subjectName, key, MBSTRING_ASC, (unsigned char*)value, -1, -1, 0);
        result = res == 1;
    }
    return result;
}

Выставляем сроки валидности сертификата

Теперь добавим сроки валидности нашего сертификата, с какой по какую дату он будет работать. Для этого воспользуемся функциями X509_getm_notAfter() и X509_getm_notBefore(). Дальше в примерах я пропускаю обработку ошибок, но вы должны держать ее в голове и использовать в настоящем проекте.

bool setNotAfter(X509* cert, uint32_t y, uint32_t m, uint32_t d, int32_t offset_days) {
    struct tm base;
    memset(&base, 0, sizeof(base));
    base.tm_year = y - 1900;
    base.tm_mon = m - 1;
    base.tm_mday = d;
    time_t tm = mktime(&base);

    bool result = false;
    ASN1_STRING* notAfter = X509_getm_notAfter(cert);
    if (notAfter != nullptr) {
        X509_time_adj(notAfter, 86400L * offset_days, &tm);
        result = true;
    }
    return result;
}

Теперь добавим стартовую точку. Код получается точно таким же, как и для начальной, за исключением имени функции для получения проперти - X509_getm_notBefore()

bool setNotBefore(X509* cert, uint32_t y, uint32_t m, uint32_t d, int32_t offset_days) {
    struct tm base;
    memset(&base, 0, sizeof(base));
    base.tm_year = y - 1900;
    base.tm_mon = m - 1;
    base.tm_mday = d;
    time_t tm = mktime(&base);

    bool result = false;
    ASN1_STRING* notBefore = X509_getm_notBefore(cert);
    if (notBefore != nullptr) {
        X509_time_adj(notBefore, 86400L * offset_days, &tm);
        result = true;
    }
    return result;
}

Выставляем Issuer

Допустим, нам понадобилось выставить Issuer для нашего сертификата, тот сертификат, которым подписан наш. Для этого нужно использовать X509_set_issuer_name().

bool setIssuer(X509* cert, X509* issuer) {
    bool result = false;
    X509_NAME* subjectName = X509_get_subject_name(issuer);
    if (subjectName != nullptr) {
        result = X509_set_issuer_name(cert, subjectName) == 1;
    }
    return result;
}

Добавить что-то в поле Issuer нашему сертификату тоже очень просто, можно сделать следующим образом, используя уже знакомую нам X509_NAME_add_entry_by_txt():

bool addIssuerInfo(X509* cert, const char* key, const char* value) {
    bool result = false;
    X509_NAME* issuerName = X509_get_issuer_name(cert);
    if (issuerName != nullptr) {
        result = X509_NAME_add_entry_by_txt(issuerName, key, MBSTRING_ASC, (unsigned char*)value, -1, -1, 0) == 1;
    }
    return result;
}

Стандартные расширения

Добавим пару стандартных расширений (standart extensions) для нашего сертификата. Для стандартных расширений в OpenSSL существуют собственные ID (nid). Например, для Basic Constraints - это NID_basic_constraints, а для Key Usage - NID_key_usage. Эти айди необходимы, если мы хотим задать те или иные расширения для нашего сертификата.

bool addStandardExtension(X509* cert, X509* issuer, int nid, const char* value) {
    X509V3_CTX ctx; // create context
    X509V3_set_ctx_nodb(&ctx); // init context
    X509V3_set_ctx(&ctx, issuer, cert, nullptr, nullptr, 0); // set context

    std::unique_ptr<X509_EXTENSION, decltype(&::X509_EXTENSION_free)> ex(X509V3_EXT_conf_nid(nullptr, &ctx, nid, value), ::X509_EXTENSION_free);
    if (ex != nullptr) {
        return X509_add_ext(cert, ex.get(), -1) == 1;
    }
    return false;
}

Кастомные расширения

Это все работает до тех пор, пока нам не понадобится добавить кастомное расширение, поддержку которых добавили в третьей версии сертификата. Тут все немного сложнее, но тоже выполнимо. Для начала создадим объект в базе данных OpenSSL, в который позднее запишем наше расширение. Теперь можно заняться подготовкой данных, создаем строку, которая будет хранить ключ и значение нашего расширения. Добавляем расширение в сертификат.

bool addCustomExtension(X509* cert, const char* key, const char* value, bool critical) {
    const int nid = OBJ_create(key, value, nullptr);

    std::unique_ptr<ASN1_OCTET_STRING, decltype(&::ASN1_OCTET_STRING_free)> data(ASN1_OCTET_STRING_new(), ::ASN1_OCTET_STRING_free);
    int ret = ASN1_OCTET_STRING_set(data.get(), reinterpret_cast<unsigned const char*>(value), strlen(value));
    if (ret != 1) {
        return false;
    }

    std::unique_ptr<X509_EXTENSION, decltype(&::X509_EXTENSION_free)> ex(X509_EXTENSION_create_by_NID(nullptr, nid, critical, data.get()), ::X509_EXTENSION_free);
    return X509_add_ext(cert, ex.get(), -1) == 1;
}

Это довольно сложный и долгий путь, но, рабочий, как я выяснил на практике. Но если у вас есть более быстрый или эффективный вариант - прошу в комментарии.

Ну и какой же сертификат без публичного ключа? Давайте его добавим. Тут все очень просто, вызываем X509_set_pubkey() и готово. Если вам интересен процесс генерации и работы с публичными и приватным ключами в OpenSSL, то пишите комментарии.

Выставляем публичный ключ

Тут все просто, генерируем пару ключей - приватный и публичный, используя EVP_RSA_gen(). Подробнее о генерации ключей и работе с ними читайте в следующей статье.

Выставляем ключ с помощью X509_set_pubkey()

#include <openssl/evp.h>
///
std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)> generateKeyPair(int32_t bits) {
    std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)> key(EVP_RSA_gen(bits), ::EVP_PKEY_free);
    return std::move(key);
}
///
bool setPublicKey(X509* cert, EVP_PKEY* key) {
    return X509_set_pubkey(cert, key) == 1;
}

Подписываем сертификат

И наконец, подписываем наш сертификат. Есть несколько популярных алгоритмов для подписи, но основной - это SHA256, он и приведен в примере. По нему можно понять процесс, а по его имени найти нужный в исходном коде OpenSSL.

signCert(certificate.get(), keyPair.get(), EVP_sha256());
///
bool signCert(X509* cert, EVP_PKEY* key, const EVP_MD* algo) {
    return X509_sign(cert, key, algo) != 0;
}

Сохраняем сертификат в файл

Подробнее о работе с BIO будет написано в следующей статье, пока что не будем на этом останавливаться. Создаем био BIO_new(BIO_s_file(), с помощью функции BIO_write_filename создаем файл и используя PEM_write_bio_X509() сохраняем сертификат в файл в формате PEM.

#include <openssl/pem.h>
///
bool saveCertToPemFile(X509* cert, const std::string& file) {
    bool result = false;
    std::unique_ptr<BIO, decltype(&::BIO_free)> bio(BIO_new(BIO_s_file()), ::BIO_free);
    if (bio != nullptr) {
        if (BIO_write_filename(bio.get(), const_cast<char*>(file.c_str())) > 0) {
            result = PEM_write_bio_X509(bio.get(), cert) == 1;
        }
    }
    return result;
}
Полный код примера
#include <openssl/x509v3.h>
#include <openssl/evp.h>
#include <openssl/pem.h>

#include <memory>
#include <iostream>

bool setSerialNumber(X509* cert, int32_t serial) {
    bool result = false;
    ASN1_INTEGER* serialNumber = X509_get_serialNumber(cert);
    if (serialNumber != nullptr) {
        const int res = ASN1_INTEGER_set(serialNumber, serial);
        result = res == 1;
    }
    return result;
}

bool setVersion(X509* cert, long version) {
    return X509_set_version(cert, version) == 1;
}

bool updateSubjectName(X509* cert, const char* key, const char* value) {
    bool result = false;
    X509_NAME* subjectName = X509_get_subject_name(cert);
    if (subjectName != nullptr) {
        const int res = X509_NAME_add_entry_by_txt(subjectName, key, MBSTRING_ASC, (unsigned char*)value, -1, -1, 0);
        result = res == 1;
    }
    return result;
}

bool setNotAfter(X509* cert, uint32_t y, uint32_t m, uint32_t d, int32_t offset_days) {
    struct tm base;
    memset(&base, 0, sizeof(base));
    base.tm_year = y - 1900;
    base.tm_mon = m - 1;
    base.tm_mday = d;
    time_t tm = mktime(&base);

    bool result = false;
    ASN1_STRING* notAfter = X509_getm_notAfter(cert);
    if (notAfter != nullptr) {
        X509_time_adj(notAfter, 86400L * offset_days, &tm);
        result = true;
    }
    return result;
}

bool setNotBefore(X509* cert, uint32_t y, uint32_t m, uint32_t d, int32_t offset_days) {
    struct tm base;
    memset(&base, 0, sizeof(base));
    base.tm_year = y - 1900;
    base.tm_mon = m - 1;
    base.tm_mday = d;
    time_t tm = mktime(&base);

    bool result = false;
    ASN1_STRING* notBefore = X509_getm_notBefore(cert);
    if (notBefore != nullptr) {
        X509_time_adj(notBefore, 86400L * offset_days, &tm);
        result = true;
    }
    return result;
}

bool setPublicKey(X509* cert, EVP_PKEY* key) {
    return X509_set_pubkey(cert, key) == 1;
}

bool signCert(X509* cert, EVP_PKEY* key, const EVP_MD* algo) {
    return X509_sign(cert, key, algo) != 0;
}

bool saveCertToPemFile(X509* cert, const std::string& file) {
    bool result = false;
    std::unique_ptr<BIO, decltype(&::BIO_free)> bio(BIO_new(BIO_s_file()), ::BIO_free);
    if (bio != nullptr) {
        if (BIO_write_filename(bio.get(), const_cast<char*>(file.c_str())) > 0) {
            result = PEM_write_bio_X509(bio.get(), cert) == 1;
        }
    }
    return result;
}

std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)> generateKeyPair(int32_t bits) {
    std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)> key(EVP_RSA_gen(bits), ::EVP_PKEY_free);
    return std::move(key);
}

bool addCustomExtension(X509* cert, const char* key, const char* value, bool critical) {
    const int nid = OBJ_create(key, value, nullptr);

    std::unique_ptr<ASN1_OCTET_STRING, decltype(&::ASN1_OCTET_STRING_free)> data(ASN1_OCTET_STRING_new(), ::ASN1_OCTET_STRING_free);
    int ret = ASN1_OCTET_STRING_set(data.get(), reinterpret_cast<unsigned const char*>(value), strlen(value));
    if (ret != 1) {
        return false;
    }

    std::unique_ptr<X509_EXTENSION, decltype(&::X509_EXTENSION_free)> ex(X509_EXTENSION_create_by_NID(nullptr, nid, critical, data.get()), ::X509_EXTENSION_free);
    return X509_add_ext(cert, ex.get(), -1) == 1;
}

bool addStandardExtension(X509* cert, X509* issuer, int nid, const char* value) {
    X509V3_CTX ctx; // create context
    X509V3_set_ctx_nodb(&ctx); // init context
    X509V3_set_ctx(&ctx, issuer, cert, nullptr, nullptr, 0); // set context

    std::unique_ptr<X509_EXTENSION, decltype(&::X509_EXTENSION_free)> ex(X509V3_EXT_conf_nid(nullptr, &ctx, nid, value), ::X509_EXTENSION_free);
    if (ex != nullptr) {
        return X509_add_ext(cert, ex.get(), -1) == 1;
    }
    return false;
}

bool setIssuer(X509* cert, X509* issuer) {
    bool result = false;
    X509_NAME* subjectName = X509_get_subject_name(issuer);
    if (subjectName != nullptr) {
        result = X509_set_issuer_name(cert, subjectName) == 1;
    }
    return result;
}

bool addIssuerInfo(X509* cert, const char* key, const char* value) {
    bool result = false;
    X509_NAME* issuerName = X509_get_issuer_name(cert);
    if (issuerName != nullptr) {
        result = X509_NAME_add_entry_by_txt(issuerName, key, MBSTRING_ASC, (unsigned char*)value, -1, -1, 0) == 1;
    }
    return result;
}

int main() {
    std::unique_ptr<X509, decltype(&::X509_free)> certificate(X509_new(), ::X509_free);
    if (certificate == nullptr) {
        std::cerr << "Failed to create certificate" << std::endl;
        return -1;
    }

    const int32_t serialNum = 1;
    bool res = setSerialNumber(certificate.get(), serialNum);
    if (!res) {
        std::cerr << "Failed to setSerialNumber" << std::endl;
        return -1;
    }

    const long ver = 0x0; // version 1
    res = setVersion(certificate.get(), ver);
    if (!res) {
        std::cerr << "Failed to setVersion" << std::endl;
        return -1;
    }

    static constexpr const char* key = "CN";
    static constexpr const char* value = "Common Name";
    res = updateSubjectName(certificate.get(), key, value);
    if (!res) {
        std::cerr << "Failed to updateSubjectName" << std::endl;
        return -1;
    }

    const uint32_t y = 2022;
    const uint32_t m = 12;
    const uint32_t d = 25;
    const int32_t offset_days = 0;
    res = setNotAfter(certificate.get(), y, m, d, offset_days);
    if (!res) {
        std::cerr << "Failed to setNotAfter" << std::endl;
        return -1;
    }

    res = setNotBefore(certificate.get(), y, m, d, offset_days);
    if (!res) {
        std::cerr << "Failed to setNotBefore" << std::endl;
        return -1;
    }

    const int32_t bits = 2048;
    std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)> keyPair = generateKeyPair(bits);
    res = setPublicKey(certificate.get(), keyPair.get());
    if (!res) {
        std::cerr << "Failed to setPublicKey" << std::endl;
        return -1;
    }

    const int nid = NID_basic_constraints;
    static const char* extensionValue = "critical,CA:TRUE";

    res = addStandardExtension(certificate.get(), nullptr, nid, extensionValue);
    if (!res) {
        std::cerr << "Failed to addStandardExtension" << std::endl;
        return -1;
    }

    res = addCustomExtension(certificate.get(), "1.2.3", "myvalue", false);
    if (!res) {
        std::cerr << "Failed to addCustomExtension" << std::endl;
        return -1;
    }

    res = signCert(certificate.get(), keyPair.get(), EVP_sha256());
    if (!res) {
        std::cerr << "Failed to signCert" << std::endl;
        return -1;
    }

    std::unique_ptr<X509, decltype(&::X509_free)> duplicate(X509_dup(certificate.get()), ::X509_free);
    if (duplicate == nullptr) {
        std::cerr << "Failed to duplicate certificate" << std::endl;
        return -1;
    }

    res = setIssuer(certificate.get(), duplicate.get());
    if (!res) {
        std::cerr << "Failed to setIssuer" << std::endl;
        return -1;
    }

    res = addIssuerInfo(certificate.get(), key, value);
    if (!res) {
        std::cerr << "Failed to addIssuerInfo" << std::endl;
        return -1;
    }

    res = signCert(certificate.get(), keyPair.get(), EVP_sha256());
    if (!res) {
        std::cerr << "Failed to signCert" << std::endl;
        return -1;
    }

    static const std::string filename = "certificate.pem";
    res = saveCertToPemFile(certificate.get(), filename);
    if (!res) {
        std::cerr << "Failed to saveCertToPemFile" << std::endl;
        return -1;
    }
}

Послесловие

Если вам понравилась данная статья и вы хотите увидеть продолжение, описывающее работу с ключами и стораджем сертификатов, а также научиться их валидировать - ставьте лайки и оставляйте комментарии.

Надеюсь, данная статья будет вам полезна и сохранит вам кучу времени, избавив от часов чтения скудной документации и редких мануалов.

UPD: Добавил примеры кода на гитхаб, буду обновлять по мере написания статей

Комментарии (24)


  1. Evengard
    22.04.2023 01:59

    А я-то думал в BouncyCastle заморочно. OpenSSL вообще очень low level API имеет, в очередной раз убеждаюсь.


    1. Witcher136 Автор
      22.04.2023 01:59

      Все так) и документация оставляет желать лучшего :) Поэтому я и решил написать статью, чтобы помочь остальным в этом нелегком пути познания опенссл


    1. Mingun
      22.04.2023 01:59

      В BouncyCastle тоже low level API есть, и к слову, если просто смотреть в спецификации, то даже без документации все достаточно понятно — там просто прямолинейное отображение ASN.1 структур. Вот их high level API действительно не понять, как использовать и зачем он вообще нужен. Мой issue полностью игнорируют.


      1. Evengard
        22.04.2023 01:59

        Вы его в Java мире используете... В C# мире документации нет вообще =) Официальный ответ "смотрите исходники или документацию на Java версию".

        API в принципе не такое плохое, но местами конечно особенное.


  1. lrrr11
    22.04.2023 01:59

    Это довольно сложный и долгий путь, но, рабочий, как я выяснил на практике. Но если у вас есть более быстрый или эффективный вариант - прошу в комментарии.

    `X509_EXTENSION_create_by_NID`, по сути все из этого сниппета в одной функции.

    А вообще код сложный, потому что вся эта предметная область (сертификаты X509 и т.п.) сложная. Спецификации на десятки и сотни страниц, ничего не поделаешь.

    А у openssl на самом деле вполне себе единообразный API. Если к нему привыкнуть, то потом проблем не будет, а вместо документации лучше читать исходники. Причем не самого openssl, а boringssl - там все очень сильно упростили внутри.


    1. Witcher136 Автор
      22.04.2023 01:59

      Надо попробовать сделать так:)


    1. Witcher136 Автор
      22.04.2023 01:59

      Спасибо за совет, обновил пример. Работает как часы :)


  1. Dmitri-D
    22.04.2023 01:59
    +3

    как же так? Это не С++, а простой старый С. Где владение ресурсами? Где гарантия освобождения ресурсов при исключениях? Где умные указатели?


    1. Witcher136 Автор
      22.04.2023 01:59

      Все есть, но как я и написал, чтобы не загромождать пример, умные указатели я оставил на конец. Где и написал, как их можно использовать: std::unique_ptr<X509_EXTENSION, decltype(&::X509_EXTENSION_free)> ex(X509_EXTENSION_new(), ::X509_EXTENSION_free); Моей главной задачей было показать последовательность вызовов, которая поможет решить человеку проблему. Красоту он сможет навести позже, когда у него наконец-то все заработает :)


      1. Dmitri-D
        22.04.2023 01:59
        +1

        ну это был сарказм, я не против и С, сам использую с 80х. Но С++ требует другого подхода, спасибо что проапдйтили.
        Я использую openssl довольно давно, хотя переход на 3.х только в планах на это лето. Что касается документации - увы, она хромает. Но, если вы находите публикации или примеры как использовать сам openssl бинарник, то несложно раскрутить что он внутри делает, благо вся коммуникация с остальной частью идет по тому же API. И вообще примеры в openssl вполне вполне полезные.


    1. Witcher136 Автор
      22.04.2023 01:59

      Обновил примеры :)


      1. Dmitri-D
        22.04.2023 01:59

        а как насчет длинных серийников? Они там байт 20, а вовсе не int32_t. Нужно бы через BN.


        1. Witcher136 Автор
          22.04.2023 01:59

          Спасибо за замечание, в моем проекте просто таких длинных не встречается. Обновлю пример, чтобы можно было для всех использовать:)


      1. Dmitri-D
        22.04.2023 01:59

        как насчет использования C++17 std::filesystem для сохранения в файл?


        1. Witcher136 Автор
          22.04.2023 01:59

          На работе просто C++11, поэтому я еще не работал с 17. А чем плохи вызовы самой библиотеки? BIO_write_filename() и PEM_write_bio_X509()?


      1. Dmitri-D
        22.04.2023 01:59

        перед подписью, openssl, во всяком случае в версии 1.1, еще ставит NID_subject_key_identifier в "hash" и NID_authority_key_identifier в "keyid:always" и для CA ключей делает NID_key_usage в "keyCertSign,cRLSign", а для остальных в "digitalSignature,keyEncipherment"


  1. kovserg
    22.04.2023 01:59

    А кто такой &x ?


    1. Evengard
      22.04.2023 01:59
      +1

      Я предполагаю данные сниппеты выдернуты из продакшн кода, а сертификаты в таком коде часто именуют переменной X (от X509, особенно в OpenSSL мире), и автор просто забыл поменять на certificate.


      1. Witcher136 Автор
        22.04.2023 01:59

        все именно так, поправил :)


  1. Evengard
    22.04.2023 01:59
    +2

    Ещё могу посоветовать https://lapo.it/asn1js/ для просмотра контента сгенеренного сертификата (или даже ключа, или чего-то ещё в asn1/pem кодировке) если у вас с ним что-то не так. Сильно помогает иногда. Меня спасло от сумасшедствия когда я не мог понять почему сгенеренный мной сертификат Фаерфокс упрямо не желает импортировать, хотя такой же сгенеренный командной строкой openssl работал безо всяких проблем...


  1. Mingun
    22.04.2023 01:59

    Только зачем версию сертификату самому ставить? Она вычисляется по правилам спецификации, чтобы парсер знал, что дальше парсить. Уж хотя бы это поле библиотека должна самостоятельно генерировать. BouncyCastle так и делает в своем low-level API.


    1. Witcher136 Автор
      22.04.2023 01:59

      Может быть я чего-то не знаю, но когда я добавлял расширения, которые есть только в v3 у меня все равно выставлялась версия 1


      1. BackLaN
        22.04.2023 01:59

        Версия должна быть == 2. Там вообще все просто делается и примеров куча в самой OpenSSL. В версиях 1.0.x был пример ~/demos/x509/mkcert.c , потом примеры убрали, но там ничего особо не поменялось в вызовах. Ну и код в ~/apps/x509.c как был так и остался, там больше всего, но тоже все просто. Весь код для генерации сертификата с ключами и подписью можно было уложить в 100 строк или меньше, у вас куча кусков кода повставлено и результат по итогу практически нулевой.