Статьи

Подписание цифровых сертификатов с помощью библиотеки OpenSSL

Работая над расширением pgopenssltypes, я понял, что не обсуждал, как подписывать цифровые сертификаты с помощью библиотеки OpenSSL. (По крайней мере, я не вспоминаю об этом — я мог обсуждать это в первые дни блога. Я почти уверен, что уже обсуждал подписание цифровых сертификатов с помощью библиотеки BouncyCastle (java).)

Мое расширение pgopenssltypes будет иметь возможность подписывать цифровые сертификаты в целях тестирования, но настоящая работа будет выполнена в возможном расширении pgca. Быть CA требует намного больше, чем умение подписывать простые сертификаты.

Основной код для подписи сертификата прост.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
BIGNUM *serialNumber;
X509_NAME *issuerName;
X509_NAME *subjectName;
time_t notBefore;
time_t notAfter;
EVP_PKEY *issuerKey; // private key
EVP_PKEY *subjectKey; // public key
 
// allocate memory for a cert.
X509 cert = X509_new();
 
// this is standard
X509_set_version(cert, 3);
 
// set the the mandatory fields.
X509_set_serialNumber(cert, BN_to_ASN1_INTEGER(serialNumber, NULL));
X509_set_issuer_name(cert, issuerName);
X509_set_subject_name(cert, subjectName);
X509_set_notBefore(cert, ASN1_TIME_set(NULL, notBefore));
X509_set_notAfter(cert, ASN1_TIME_set(NULL, notAfter));
X509_set_pubkey(cert, subjectKey);
 
// sign the certificate. In this case I'm using SHA256 for the hash.
if ((r = X509_sign(cert, issuerKey, EVP_sha256())) <= 0) {
    fprintf(STDERR, "%s", ERR_reason_error_string(ERR_get_error());
    X509_free(cert);
    return NULL;
}
 
// release the memory
PKCS8_PRIV_KEY_INFO_free(p8);
EVP_PKEY_free(pkey);
 
// write certificate to file
// FILE *fp = fopen(...)
// PEM_write_X509(fp, cert);
 
return cert;

Есть вариант, использующий X509_REQ вместо объекта X509, но это тривиальное изменение. (Насколько я помню, X509_REQ — это специальный самозаверяющий сертификат X509, в основном используемый полными центрами сертификации (CA).)

Эмитент обычно связан с собственным цифровым сертификатом до бесконечности. Это формирует «цепочку сертификатов», в которой эмитент каждого сертификата является субъектом следующего. Цепочка заканчивается либо «доверенным» сертификатом (где пользователь сам решает, что является «доверенным»), либо самозаверяющим сертификатом (где опять же пользователь сам решает, насколько этот субъект / издатель является «доверенный»).

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

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

Типы данных

Код содержит несколько незнакомых типов данных.

BIGNUM — это реализация OpenSSL целого числа произвольной длины. Некоторые CA используют 128 или 160-битные серийные номера, не совпадая с длиной хеш-значения MD5 или SHA1. Однако это всего лишь соглашение, и нет причины, по которой злоумышленник не может использовать серийный номер размером 10 Кбайт, пытаясь вызвать переполнение буфера, когда плохо написанный код пытается его прочитать.

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

Некоторые CA обеспокоены тем, что простая последовательность пропускает конфиденциальную информацию, а именно, сколько сертификатов было выпущено. Обычной контрмерой является использование серийного номера на основе даты, например, шестнадцатеричное значение может быть 0x20141230NNNNNN, где NNNNNN — время, псевдослучайное число или некоторая комбинация.

Наконец, осторожные центры сертификации могут кодировать информацию в самом серийном номере. Например, он может спокойно гарантировать, что серийный номер всегда равен 3 mod 17, или что аналогичные значения отражают информацию, присутствующую в другом месте в сертификате. Злоумышленник, имеющий доступ к закрытому ключу CA, вероятно, не знает об этих проверках, и обнаружение неверных серийных номеров в открытом виде может быть первым признаком нарушения.

EVP_PKEY — это общий закрытый или открытый ключ. Он может содержать ключи RSA, ECC, DSA или DH. Я думаю, что нас интересуют только ключи RSA при создании цифровых сертификатов, возможно, ECC с устройствами с низким энергопотреблением.

Мы можем преобразовать ключ RSA в EVP_PKEY с помощью:

01
02
03
04
05
06
07
08
09
10
11
RSA *rsa;
EVP_PKEY *pkey;
 
pkey = EVP_PKEY_new();
if (EVP_PKEY_set1_RSA(pkey, rsa) <= 0) {
    fprintf(STDERR, "%s", ERR_reason_error_string(ERR_get_error());
    EVP_PKEY_free(pkey);
    return NULL;
}
 
return pkey;

Также легко преобразовать ключ .p8 (PKCS8) в EVP_PKEY:

01
02
03
04
05
06
07
08
09
10
PKCS8_PRIV_KEY_INFO *p8;
EVP_PKEY *pkey;
 
pkey = EVP_PKCS82PKEY(p8);
if (pkey == null) {
    fprintf(STDERR, "%s", ERR_reason_error_string(ERR_get_error());
    return NULL;
}
 
return pkey;

Имя X509_NAME совпадает с выделенным именем LDAP. Буквально — они приходят из тех же стандартов X.500. (Отсюда X509.) Многие архитектуры используют это преимущество — это хороший способ привязать цифровой сертификат пользователя к корпоративным службам каталогов.

X509_NAME — это стек значений X509_NAME_ENTRY . Каждая запись является парой ключ-значение. Существует несколько десятков стандартных клавиш, некоторые из них можно повторить. (Например, «DC» для компонента домена. Сервером на invariantproperties.com будет «DC = invariantproperties, DC = com».) Дополнительные сведения см. В типах атрибутов RFC 4519 .

Вы можете напечатать X509_NAME с:

1
2
3
4
X509_NAME *name;
char buf[BUF_LEN];
 
X509_NAME_oneline(name, buf, BUF_LEN);

или же:

1
2
3
4
5
BIO *bio;  // can point to memory buffer, file, etc.
X509_NAME *name;
int obase; // indentation on multi-line output.
 
X509_NAME_print(bio, name, obase);

Создание объектов X509_NAME немного сложнее.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
char *name;              // e.g., "C" for Country
unsigned char *value;    // e.g., "US"
int type = MBSTRING_ASC; // or MBSTRING_UTF8
X509_NAME *name;
 
// value is null-terminated string.
int len = -1;
 
// these values add entry to end of X509_NAME.
int loc = -1;
int set = 0;
 
name = X509_NAME_new();
if (X509_NAME_add_entry_by_txt(name, key, type, value, len, loc, set) <= 0) {
    fprintf(STDERR, "%s", ERR_reason_error_string(ERR_get_error());
    return NULL;
}

расширения

Истинная сила сертификатов X509v3 заключается в том, что они могут быть расширены произвольной информацией. Существует около десятка широко используемых расширений, но любой может получить новый OID ASN1 и добавить свою собственную информацию в цифровой сертификат. (Новый OID должен быть явно запрошен и зарегистрирован — это вызовет проблемы, если один и тот же OID будет использоваться для разных целей.) Должен быть зарегистрирован только корневой OID, могут быть созданы вспомогательные OID.

Подробное обсуждение стандартных расширений выходит за рамки данной статьи. Достаточно того, чтобы они могли предоставить информацию об использовании ключа (например, сертификат должен использоваться для шифрования электронной почты, но не на сервере), дополнительных псевдонимов для сертификата или даже об ограничениях сертификатов, подписанных этим сертификатом. (например, это должно быть для сервера в домене «example.com»).

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