Программное создание сертификата X509 с помощью OpenSSL
У меня есть приложение C/C++, и мне нужно создать сертификат PEM X509, содержащий как открытый, так и закрытый ключ. Сертификат может быть самоподписанным, или нет, не важно.
Я хочу сделать это внутри приложения, а не из командной строки.
какие функции OpenSSL сделают это для меня? Любой пример кода-это бонус!
4 ответов
сначала вам нужно ознакомиться с терминологией и механизмами.
Сертификат X. 509 сертификат по определению, не содержит закрытого ключа. Вместо этого это версия открытого ключа с подписью CA (вместе с любыми атрибутами, которые CA помещает в подпись). Формат PEM действительно поддерживает только отдельное хранение ключа и сертификата-хотя затем вы можете объединить их.
в любом случае вам нужно будет вызвать 20+ различные функции API OpenSSL для создания ключа и самозаверяющего сертификата. Пример находится в самом источнике OpenSSL, в демонстрации / x509 / mkcert.c
более подробный ответ см. В разделе объяснение Натана Османа ниже.
я понимаю, что это очень поздний (и длинный) ответ. Но, учитывая, насколько хорошо этот вопрос ранжируется в результатах поиска, я подумал, что, возможно, стоит написать достойный ответ.
многое из того, что вы прочитаете ниже, взято из демо и документы OpenSSL. Приведенный ниже код применим как к C, так и к c++.
прежде чем мы сможем создать сертификат, нам нужно создать закрытый ключ. В OpenSSL предоставляет EVP_PKEY
структура для хранения алгоритмически независимого закрытого ключа в памяти. Эта структура объявлена в openssl/evp.h
, а как openssl/x509.h
(который нам понадобится позже), поэтому вам не нужно явно включать заголовок.
для того, чтобы выделить EVP_PKEY
структуре, мы используем EVP_PKEY_new
:
EVP_PKEY * pkey;
pkey = EVP_PKEY_new();
существует также соответствующая функция для освобождения структуры -EVP_PKEY_free
- который принимает один аргумент:EVP_PKEY
структура инициализирована выше.
теперь нам нужно сгенерировать ключ. Для нашего примера мы сгенерируем ключ RSA. Это делается с помощью RSA_generate_key
функция, которая объявлена в openssl/rsa.h
. Эта функция возвращает указатель на RSA
структура.
простой вызов функции может выглядеть так:
RSA * rsa;
rsa = RSA_generate_key(
2048, /* number of bits for the key - 2048 is a sensible value */
RSA_F4, /* exponent - RSA_F4 is defined as 0x10001L */
NULL, /* callback - can be NULL if we aren't displaying progress */
NULL /* callback argument - not needed in this case */
);
если возвращаемое значение RSA_generate_key
и NULL
, потом что-то пошло не так. Если нет, то у нас теперь есть ключ RSA, и мы может назначить его нашему EVP_PKEY
структура из более ранних:
EVP_PKEY_assign_RSA(pkey, rsa);
на RSA
структура будет автоматически освобождается, когда EVP_PKEY
структура освобождается.
теперь для самого сертификата.
OpenSSL использует X509
структура для представления сертификата x509 в памяти. Определение для этой структуры находится в openssl/x509.h
. Первая функция, которая нам понадобится, это X509_new
. Его использование относительно просто:
X509 * x509;
x509 = X509_new();
как и в случае с EVP_PKEY
есть соответствующая функция для освобождения структуры - X509_free
.
теперь нам нужно установить несколько свойств сертификата, используя некоторые X509_*
функции:
ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
это устанавливает серийный номер нашего сертификата в '1'. Некоторые HTTP-серверы с открытым исходным кодом отказываются принимать сертификат с серийным номером "0", который используется по умолчанию. Следующий шаг-указать промежуток времени во время которого сертификат действительно действителен. Мы делаем это со следующими двумя вызовами функций:
X509_gmtime_adj(X509_get_notBefore(x509), 0);
X509_gmtime_adj(X509_get_notAfter(x509), 31536000L);
первая строка устанавливает сертификат notBefore
свойство к текущему времени. (The X509_gmtime_adj
функция добавляет указанное количество секунд текущего времени - в этом случае нет.) Вторая строка устанавливает сертификат notAfter
свойство до 365 дней отныне (60 секунд * 60 минут * 24 часа * 365 дней).
теперь нам нужно установить открытый ключ для нашего сертификат с использованием ключа, который мы создали ранее:
X509_set_pubkey(x509, pkey);
поскольку это самозаверяющий сертификат, мы устанавливаем имя эмитента на имя субъекта. Первым шагом в этом процессе является получение название темы:
X509_NAME * name;
name = X509_get_subject_name(x509);
если вы когда-либо создавали самозаверяющий сертификат в командной строке раньше, вы, вероятно, помните, что вас попросили код страны. Вот где мы предоставляем его вместе с организацией ('O') и общим названием ('CN'):
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 Inc.", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
(unsigned char *)"localhost", -1, -1, 0);
(я использую значение " CA " здесь, потому что я канадец, и это наш код страны. Также обратите внимание, что параметр #4 должен быть явно приведен к unsigned char *
.)
теперь мы можем установить наименование эмитента:
X509_set_issuer_name(x509, name);
и, наконец, мы готовы выполнить процесс подписания. Мы зовем X509_sign
С ключом, который мы создали ранее. Код для этого до боли прост:
X509_sign(x509, pkey, EVP_sha1());
обратите внимание, что мы используем ша-1 алгоритм хэширования для подписи ключа. Это отличается от mkcert.c
demo я упомянул в начале этого ответа, который использует MD5.
теперь у нас есть самоподписанный сертификат! Но мы еще не закончили - нам нужно записать эти файлы на диск. К счастью OpenSSL имеет нас покрыты там тоже с PEM_*
функции, которые объявлены в openssl/pem.h
. Первый, который нам понадобится, это PEM_write_PrivateKey
для сохранения нашего закрытого ключа.
FILE * f;
f = fopen("key.pem", "wb");
PEM_write_PrivateKey(
f, /* write the key to the file we've opened */
pkey, /* our key from earlier */
EVP_des_ede3_cbc(), /* default cipher for encrypting the key on disk */
"replace_me", /* passphrase required for decrypting the key on disk */
10, /* length of the passphrase string */
NULL, /* callback for requesting a password */
NULL /* data to pass to the callback */
);
если вы не хотите шифровать закрытый ключ, а затем просто пройти NULL
для третьего и четвертого параметра выше. В любом случае, вы обязательно захотите убедиться, что файл не читается по всему миру. (Для пользователей Unix, это означает chmod 600 key.pem
.)
уфф! Теперь у нас осталась одна функция - нужно записать сертификат на диск. Для этого нам нужна функция PEM_write_X509
:
FILE * f;
f = fopen("cert.pem", "wb");
PEM_write_X509(
f, /* write the certificate to the file we've opened */
x509 /* our certificate */
);
и мы сделали это! Надеюсь, информации в этом ответе достаточно, чтобы дайте вам приблизительное представление о том, как все работает, хотя мы едва поцарапали поверхность OpenSSL.
для тех, кто заинтересован в том, как выглядит весь приведенный выше код в реальном приложении, я собрал суть (написанную на C++), которую вы можете просмотреть здесь.
любой шанс сделать это через system
звонок из вашего приложения? Несколько веских причин для этого:
лицензирование: вызов
openssl
исполняемый файл, возможно, отделяет его от вашего приложения и может предоставить определенные преимущества. отказ от ответственности: проконсультируйтесь с адвокатом по этому поводу.документация: OpenSSL поставляется с феноменальную документация командной строки, которая значительно упрощает потенциально сложные инструменты.
тестируемость: вы можете осуществлять OpenSSL из командной строки, пока не поймете, как именно создать свои сертификаты. Есть много вариантов; ожидайте потратить около дня на это, пока не получите все детали правильно. После этого тривиально включать команду в ваше приложение.
Если вы решили использовать API, проверьте openssl-dev
список разработчиков на www.openssl.org.
хорошее удачи!
очень простой учебник для создания цифровых сертификатов http://publib.boulder.ibm.com/infocenter/rsthelp/v8r0m0/index.jsp?topic=/com.ibm.rational.test.lt.doc/topics/tcreatecertopenssl.html
о выполнении этих команд из вашего кода я не уверен.