Предыстория ~~~~~~~~~~~ В сообщении http://marc.theaimsgroup.com/?l=openssl-dev&m=109947214028600&w=2 мы описывали наши предложения по генерализации архитектуры работы с асимметричными алгоритмами. В данном сообщении мы представляем очередную версию нашего патча, реализующую наши предложения. Обусловленность задачи ~~~~~~~~~~~~~~~~~~~~~~ Задача интересна прежде всего с точки зрения возможности легкого добавления в openssl национальной криптографии. Требования по сертификации криптографического программного обеспечения некоторых стран (в частности России) делают законным использование решений, в которых сертифицирован только модуль, непосредственно реализующий национальные криптоалгоритмы. Поэтому наличие сертифицированного (коммерческого) криптографического модуля, совместимого с OpenSSL, позволит пользователям применять сертифицированные криптоалгоритмы в решениях на базе любых OpenSource приложений, использующих OpenSSL. Кроме того, наличие стандартизированного и документированного интерфейса для добавления новых алгоритмов упростит разработку и тестирование реализаций ранее неподдерживаемых алгоритмов и увеличит надежность этих модификаций, так как для добавления нового алгоритма не будет требоваться внесение многочисленных модификцаций в ядро OpenSSL. Новые возможности, добавляемые патчем ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Цель данного патча - сделать возможным добавление в виде подгружаемых модулей engine не только новых реализаций существующих ассиметричных криптографических алгоритмов (DSA, RSA, ECDSA) но и добавление новых асимметричных алгоритмов. Существующее API OpenSSL позволяет только выбирать ту или иную реализацию стандартных алгоритмов, предоставляемую разработчиками engine. В частности, наша компания смогла, опираясь на данный патч, реализовать для OpenSSL российские криптографические алгоритмы электронной подписи GOST-1994, GOST-2001. Мы протестировали для нашей реализации всю функциональность PKI и работу с подписанными и зашифрованными сообщениями (SMIME). Описание патча ~~~~~~~~~~~~~~ Предлагаемый патч делает работу с асимметричными алгоритмами на уровне ядра OpenSSL полностью аналогичной работе с симметричными алгоритмами и алгоритмами дайджеста. Патч решает проблему использования одного алгоритма дайджеста с несколькими алгоритмами подписи без клонирования и заведения отдельных OID для, по сути своей, нескольких экземпляров одного алгоритма. Идея аналогична присутствующей в закомментированном виде в evp.h структуре EVP_PKEY_MD. Патч вводит структуру EVP_ASYMMETRIC, аналогичную EVP_CIPHER и EVP_MD и API управления данной структурой, аналогичное имеющемуся для EVP_CIPHER/EVP_MD. Эта структура содержит - NID реализуемого алгоритма, - Тип реализуемого алгоритма (RSA/DSA/EC/DH), - NID используемого с ним алгоритма дайджеста, если алгоритм, как DSA или российские алгоритмы, требует специфического дайджеста (DSA требует SHA1, российские алгоритмы подписи - российских алгоритмов дайджеста), - NID используемой комбинации "алгоритм подписи - алгоритм дайджеста", в случае, если это не удается определить из поля pkey_type соответствующего дайджеста. - указатели на специфичные для алгоритмов функции, осуществляющие: - разбор параметров генерации ключа (используются в команде openssl req) (синтаксис опции -newkey генерализован и сделан расширяемым), - конвертацию из представления в X509-сертификате во внутреннее представление OpenSSL и обратно открытого ключа, - конвертацию из представления в X509-сертификате во внутреннее представление OpenSSL и обратно параметров алгоритма, - конвертацию из представления в PKCS8-формате во внутреннее представление OpenSSL и обратно секретного ключа, - сохранение ASN1-параметров алгоритма в SIGNER_INFO PKCS7-структуры, - сохранение и разбор информации о получателе зашифрованного сообщения. - выработку и проверку подписи. Патч не реализует предложенную в нашем оригинальном пропозале генерализацию работы с эллиптическими кривыми, так как нам оказалось проще инкапсулировать преобразование параметров алгоритма ГОСТ 2001 в структуру EC_GROUP в вышеупомянутые функции соответствующего алгоритма. Добавлена поддержка асимметричных алгоритмов на уровень ENGINE API, аналогичная существовавшим там функциям ENGINE_get_ciphers/ENGINE_get_digests. Эти функции задействованы, в частности, в команде openssl engine. Все алгоритмы с открытым ключом, поддерживаемые OpenSSL (RSA, DSA, DH, ECDSA, ECDH) переведены на работу через таблицу ассиметричных алгоритмов. При этом комплект тестов, входящий в дистрибутив OpenSSL полностью выполняется. API EVP_ASYMMETRIC и его использование ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Все описанные в этом разделе функции, если не оговорено особо, возвращают положительное значение в случае успеха и неположительное - в случае ошибки. /* Recieves EVP_PKEY to store parameters, string from openssl req */ /* options and BIO channel to report errors to */ int (*parse_keygeneration_params)(EVP_PKEY *newkey,const char *params, BIO* bio_err); Принимает указатель на структуру EVP_PKEY, в которую при успехе сохраняются параметры, строковое представление параметров и BIO, используемое для выдачи сообщений об ошибках. Функция предназначена для разбора параметра командной строки команды openssl req. Эта функция должна быть определена для того, чтобы была возможность создания X509-сертификатов с алгоритмом, соответствующим данному EVP_ASYMMETRIC. Возвращает длину ключа в случае успеха. Эта фукнция используется для замены условной компиляции файла req.c. /* Parses ASN1_STRUCTRE of public key (typically from X509 cert) */ int (*d2i_pub_key)(EVP_PKEY *key, const unsigned char *buf, long length); Эта функция используется для приведения прочитанного из сертификата открытого ключа ко внутреннему представлению. Должна быть определена для всех алгоритмов, для которых есть возможность создания сертификатов. /* Parses private key ASN1 STRUCTURE. Handles some cases of broken */ /* PKCS8 returning appropriate constant in the int *broken field */ int (*d2i_PKCS8_key)(EVP_PKEY *key, const unsigned char *buf, long length, int *broken); Эта функция используется для приведения прочитанного из сертификата секретного ключа ко внутреннему представлению. Должна быть определена для всех алгоритмов, для которых есть возможность создания сертификатов. Обрабатывает legacy-форматы секретных ключей. /* Parses ASN1 STRUCTURE of algorithm parameters */ /* If algorithm doesn't need parameters, this field could be NULL. */ /* Otherwise it should allocate algorithm-type specific parameter */ /* structure and assign it to pkey->pkey. It should also set */ /* save_parameters field of pkey to true. */ int (*d2i_algor_params) (EVP_PKEY *pkey,const ASN1_TYPE *param); Функция предназначена для разбора хранимых в сертификате или секретном ключе параметров алгоритма. Если алгоритм не сохраняет свои параметры в сертификате, эту функцию можно не определять. В противном случае она должна сохранять алгоритм-специфичные параметры в соответствующем поле структуры EVP_PKEY и выставлять в true поле save_parameters. /* Packs public key into buffer according to algorithm specific */ /* rules */ int (*i2d_pub_key)(EVP_PKEY *key, unsigned char **buf); Функция выполняет упаковку открытого ключа в ASN1-структуру. Должна быть определена для возможности создания сертификатов. /* Packs private key into buffer according to algorithm specific */ /* rules */ int (*i2d_priv_key)(EVP_PKEY *key, unsigned char **buf); Функция выполняет упаковку секретного ключа в PKCS8-структуру. Должна быть определена для возможности создания сертификатов. /* Fills given freshly allocated ASN1_TYPE structure with algorithm */ /* parameters. Returns length of the structure. If param is NULL, */ /* just returns length. Returns -1 on error. */ int (*i2d_algor_params) (EVP_PKEY *key, ASN1_TYPE *param); Функция сохраняет параметры алгоритма в переданной ASN1-структуре. При передаче NULL возвращает необходимую длину. int (*i2d_signature_algor) (X509_ALGOR* param); Функция сохраняет NID алгоритма подписи и параметры подписи в переданную структуру X509_ALGOR. /* S/MIME key encryption and pack */ /* Returns non-positive value on error, positive on success */ int (*pkcs7_key_transport_encrypt) (EVP_PKEY *pubk, const unsigned char *key,int key_len, ASN1_OCTET_STRING *); Эта функция предназначена для упаковки ключа, с помощью которого выполняется симметричное шифрование сообщения, при отправке шифрованных S/MIME сообщений. Сейчас она рассчитана в соответствии с ограничениями текущей реализации OpenSSL на упаковку RSA-подобной структуры KeyTransRecipientInfo (RFC 2630, 6.2.1; драфты от "КриптоПро"). Должна быть определена в случае, когда алгоритм планируется использовать для создания зашифрованных S/MIME сообщений. Российские криптоалгоритмы используют KeyTransRecipientInfo для передачи эфемерных ключей из-за особенностей реализации Windows, рассматривающей любой алгоритм как RSA-подобный. /* S/MIME key unpack and decryption */ /* Returns negative absolute value of required size on too small buffer * zero on error, decrypted key size on success*/ int (*pkcs7_key_transport_decrypt) (EVP_PKEY* priv, unsigned char *key, int max_key_len, const ASN1_OCTET_STRING * data); Эта функция предназначена для распаковки симметричного ключа в зашифрованных S/MIME сообщениях. В случае недостаточного размера переданного буфера, если такая возможность есть, возвращает отрицательное значение, равное по абсолютной величине необходимой длине буфера. В случае успеха возвращает длину симметричного ключа. /* Signature generation. */ /* Returns non-negative value on success. */ int (*sign) (const EVP_PKEY *priv, int dgst_type, const unsigned char *dgst_buf, unsigned int dgst_len, unsigned char *sigret, unsigned int *siglen); Вырабатывает подпись. /* Signature verification. */ /* Returns non-negative value on success. */ int (*verify) (const EVP_PKEY *pub, int dgst_type, const unsigned char *dgst_buf, unsigned int dgst_len, const unsigned char *sigbuf, unsigned int siglen); Проверяет подпись. В случае, когда для EVP_ASYMMETRIC определяются функции sign и verify, рекомендуется определять их через соответствующие функции union'а method, чтобы обеспечить возможность легкой смены реализации через смену method (например, для аппаратных решений). Добавлена функция EVP_PKEY* EVP_PKEY_by_asymmetric(const EVP_ASYMMETRIC *as); Она аналогична EVP_PKEY_new, за тем исключением, что выполняет более полную инициализацию структуры EVP_PKEY, так как располагает большей информацией. В структуру ENGINE добавлен callback, отвечающий за работу с EVP_ASYMMETRIC. В API ENGINE добавлены функции ENGINE_ASYMS_PTR ENGINE_get_asyms(const ENGINE *e), аналогичная ENGINE_get_digests/ENGINE_get_ciphers, и возвращающая соответствующий callback, ENGINE_set_asyms(ENGINE *e, ENGINE_ASYMS_PTR f), устанавливающая callback, const EVP_ASYMMETRIC *ENGINE_get_asym(ENGINE *e, int nid), аналогичная ENGINE_get_digest/ENGINE_get_cipher, obtains an asymmetric implementation from an ENGINE functional reference, ENGINE *ENGINE_get_asym_engine(int nid), аналогичная ENGINE_get_cipher_engine/ENGINE_get_digest_engine, возвращающая ENGINE, в котором реализован алгоритм, относящийся к искомому nid. Описание конкретных изменений ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ В коде libcrypto и утилиты openssl (команд, связанных с PKI и SMIME) явные ветвления по типу алгоритма с открытым ключом заменены на поиск алгоритма в таблице по его имени или OID-у и вызов соответствующих функций по указателям, содержащимся в структуре EVP_ASYMMETRIC. Благодаря этому удалось отказаться также от условной компиляции многих файлов уровня EVP и выше в зависимости от макросов OPENSSL_NO_RSA, OPENSSL_NO_DSA и OPENSSL_NO_EC. Условная компиляция остается только при добавлении алгоритмов в таблицу (файл crypto/evp/c_alla.c, добавляемый нашим патчем). Во всех остальных ситуациях алгоритм, не поддерживаемый текущим вариантом компиляции просто не находится в таблице, что приводит к соответствующему сообщению об ошибке. Явное прописывание дайджеста, жестко требуемого по спецификации того или иного алгоритма, также заменено на поиск по значению из соответствующего поля структуры EVP_ASYMMETRIC. Добавлена функция EVP_PKEY_by_asymmetric, создающая новый экземпляр структуры EVP_PKEY и инициализирующая его в зависимости от типа алгоритма. В функциях EVP_SignFinal и EVP_VerifyFinal перед вызовом соответствующей функции дайджеста проверяется, есть ли функция sign/verify у структуры EVP_ASYMMETRIC, соответствующей переданному ключу. В функциях ASN1_sign и ASN1_item_sign алгоритм связки "дайджест-подпись" берется из дайджеста. Если результат - NID_undef, то он берется из EVP_ASYMMETRIC, соответствующей переданному ключу. Предлагаемый патч по умолчанию сохраняет секретные ключи в формате PKCS8, так как этот формат универсален и позволяет работать с любыми алгоритмами, в то время как непатченный OpenSSL по умолчанию использует алгоритм-специфичные форматы секретных ключей. При экспорте секретных ключей из PKCS12-структуры они также экспортируются в PKCS8-формате. Для преобразования в исторически сложившиеся форматы предполагается, как и в исходной версии нашего предложения, использовать команду openssl pkcs8. Кроме того, с целью упрощения логики создания PKCS8-файлов, нами изменена логика записи DSA-ключей и RSA в формат PKCS8. В текущем снапшоте создание PKCS8 структуры для DSA, удовлетворяющей стандарту, является частным случаем создания broken структуры. Т.е. функция EVP_PKEY2PKCS8 вызывает функцию EVP_PKEY2PKCS8_broken. В результате применения нашего патча функция EVP_PKEY2PKCS8 создает корректную с точки зрения стандарта структуру, а функция EVP_PKEY2PKCS8_broken сначала создает стандартную структуру вызовом EVP_PKEY2PKCS8, а потом, если необходимо breaks it. Команде openssl smime добавлена интерпретация ключей -algname, где имя алгоритма в зависимости от осуществляемой операции может быть либо алгоритмом дайджеста, либо алгоритмом шифрования. При реализации работы с S/MIME мы опирались на специфицированные в draft'ах "Соглашения о совместимости СКЗИ российских производителей". Хотя используемая семантика соответствует KeyAgreement (RFC2630, 6.2.2), эти документы специфицируют расширение формата KeyTransport (RFC2630, 6.2.1). Это связано, в частности, с необходимостью встраивания в Microsoft Windows, где применяется RSA-подобная схема. Эта же функциональность реализована в OpenSSL (до нашего патча там поддерживались только RSA-ключи получателя). К сожалению, в процессе разработки патча мы столкнулись с проблемами, возникающими при создании объектов функцией OBJ_create изнутри динамически подгружаемых библиотек. Проблема заключается в том, что необходимо, чтобы динамически созданные внутри engine объекты существовали а) в момент разбора командной строки openssl, так как они определяют допустимый набор параметров команд dgst, enc и req (после генерализации опции -newkey) б) в момент выполнения EVP_cleanup, так как при помещении алгоритмов в соответствующие таблицы функциями EVP_add_cipher, EVP_add_digest, EVP_add_asymmetric в поле name структуры OBJ_NAME прписывается ссылка на поле sn либо ln структуры ASN1_OBJECT. Соответственно, есил динамически размещенный объект, на который ссылается OBJ_NAME уже освобожден в тот момент, когда мы пытаемся удалить OBJ_NAME из таблицы с помощью функции lh_delete, происходит segmentation fault. Для того, чтобы преодолеть эту проблему, мы перенесли очистку таблицы объектов из отдельных процедур, реализующих команды openssl (req_main, x509_main, ca_main etc) в код процедуры main файла openssl.c ПОСЛЕ вызова apps_shutdown. Reference implementation of engine ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Планы на будущее ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ В предлагаемом патче не реализована поддержка национальных криптографических алгоритмов собственно в протоколе SSL. Обеспечение такой поддержки в текущей архитектуре этой части OpenSSL является довольно сложной задачей, поэтому нам показалось целесообразным выделить эти работы в отдельный этап. Кроме того, предлагаемый патч обеспечивает возможность использования национальных криптографических алгоритмов после подгрузки реализующего их модуля engine. В существующих программах, использующих OpenSSL, такая подгрузка не предусмотрена. Эта проблема может быть решена, если OpenSSL будет собрана с необходимым модулем engine. Однако данное решение неудобно для тех, кто использует бинарные дистрибутивы программного обеспечения (например, дистрибутивы ОС Linux) и не хочет терять совместимости с системой обновления соответствующих дистрибутивов. Поэтому в будущем мы планируем усовершенствовать возможность управление подгрузкой модулей engine через файлы конфигурации. Мы планируем реализовать поддержку российских алгоритмов в PKCS12 (в настоящее время используются классические алгоритмы) и поддержку экспорта зашифрованных секретных ключей с использованием предоставляемых engine алгоритмов.