Я понимаю, что это очень поздний (и длинный) ответ. Но, учитывая, насколько хорошо этот вопрос оценивается в результатах поисковых систем, я подумал, что, возможно, стоит написать достойный ответ.
Многое из того, что вы прочтете ниже, заимствовано из этой демонстрации и документации OpenSSL. Код ниже относится как к С, так и к С ++.
Прежде чем мы сможем создать сертификат, нам нужно создать закрытый ключ. 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
сертификата на текущее время. (Функция 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());
Обратите внимание, что мы используем алгоритм хеширования SHA-1 для подписи ключа. Это отличается от демонстрации mkcert.c
, о которой я упоминал в начале этого ответа, в которой используется 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.
Для тех, кому интересно посмотреть, как весь приведенный выше код выглядит в реальном приложении, я собрал Gist (написанный на C ++), который вы можете просмотреть здесь .