openssl: FFI-привязка OpenSSL для nginx-module-lua
Установка
Если вы еще не настроили подписку на RPM-репозиторий, зарегистрируйтесь. Затем вы можете перейти к следующим шагам.
CentOS/RHEL 7 или Amazon Linux 2
yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install lua-resty-openssl
CentOS/RHEL 8+, Fedora Linux, Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-openssl
Чтобы использовать эту Lua-библиотеку с NGINX, убедитесь, что установлен nginx-module-lua.
В этом документе описывается lua-resty-openssl v1.9.0 выпущенный 14 августа 2026 года.
FFI-привязка OpenSSL для LuaJIT, поддерживающая OpenSSL 3, 4 и серию 1.1.1.
Поддержка OpenSSL 1.1.0, 1.0.2 и BoringSSL была прекращена, но все еще доступна в ветке 0.x.
Описание
lua-resty-openssl — это библиотека привязки OpenSSL на основе FFI, которая в настоящее время
поддерживает OpenSSL 3.x, 4.x и серию 1.1.1.
Синопсис
Эта библиотека в значительной степени вдохновлена luaossl и использует
соглашение об именовании, более близкое к оригинальному OpenSSL API.
Например, функция X509_set_pubkey в C API OpenSSL представлена
как resty.openssl.x509:set_pubkey.
CamelCase заменяется на underscore_case, например, X509_set_serialNumber становится
resty.openssl.x509:set_serial_number. Еще одним отличием от luaossl является то, что ошибки никогда не выбрасываются
с помощью error(), а вместо этого возвращаются последним параметром.
Каждая Lua-таблица, возвращаемая new(), содержит cdata-объект ctx. Пользователям не следует вручную устанавливать
ffi.gc или вызывать соответствующий деструктор структуры ctx (например, функции *_free).
resty.openssl
Этот мета-модуль обеспечивает проверку совместимости версий с связанной библиотекой OpenSSL.
openssl.load_library
синтаксис: name, err = openssl.load_library()
Пытается загрузить общие библиотеки OpenSSL. Эта функция пробует несколько известных
шаблонов имен библиотек и возвращает имя библиотеки crypto при успешной
загрузке или ошибку, если таковая возникла.
При работе внутри CLI resty или OpenResty с включенным SSL вызов этой функции
не обязателен.
openssl.load_modules
синтаксис: openssl.load_modules()
Загружает все доступные под-модули в текущий модуль:
bn = require("resty.openssl.bn"),
cipher = require("resty.openssl.cipher"),
digest = require("resty.openssl.digest"),
hmac = require("resty.openssl.hmac"),
kdf = require("resty.openssl.kdf"),
pkey = require("resty.openssl.pkey"),
objects = require("resty.openssl.objects"),
rand = require("resty.openssl.rand"),
version = require("resty.openssl.version"),
x509 = require("resty.openssl.x509"),
altname = require("resty.openssl.x509.altname"),
chain = require("resty.openssl.x509.chain"),
csr = require("resty.openssl.x509.csr"),
crl = require("resty.openssl.x509.crl"),
extension = require("resty.openssl.x509.extension"),
extensions = require("resty.openssl.x509.extensions"),
name = require("resty.openssl.x509.name"),
store = require("resty.openssl.x509.store"),
ssl = require("resty.openssl.ssl"),
ssl_ctx = require("resty.openssl.ssl_ctx"),
Начиная с OpenSSL 3.0, также доступны provider,
mac и ctx.
openssl.luaossl_compat
синтаксис: openssl.luaossl_compat()
Предоставляет API в стиле luaossl, который использует именование camelCase; пользователи могут рассчитывать на замену "под ключ".
Например, pkey:get_parameters сопоставляется с pkey:getParameters.
Обратите внимание, что не весь API luaossl реализован, пожалуйста, проверьте readme для получения достоверной информации.
openssl.get_fips_mode
синтаксис: enabled = openssl.get_fips_mode()
Возвращает логическое значение, указывающее, включен ли режим FIPS.
openssl.set_fips_mode
синтаксис: ok, err = openssl.set_fips_mode(enabled)
Включает или выключает режим FIPS.
lua-resty-openssl поддерживает следующие режимы:
Серия OpenSSL 1.0.2 с модулем fips 2.0
Скомпилируйте модуль в соответствии с политикой безопасности,
Провайдер FIPS OpenSSL 3
Обратитесь к https://wiki.openssl.org/index.php/OpenSSL_3.0 Раздел 7 Скомпилируйте провайдер в соответствии с руководством, установите fipsmodule.cnf, который соответствует хэшу провайдера FIPS fips.so.
Начиная с OpenSSL 3.0, эта функция также включает и выключает свойства по умолчанию для функций EVP. При включении все приложения, использующие API EVP_*, будут перенаправлены на реализации, соответствующие FIPS, и не будут иметь доступа к алгоритмам, не соответствующим FIPS.
Вызов этой функции эквивалентен загрузке провайдера fips и
вызову openssl.set_default_properties("fips=yes").
Если провайдер FIPS загружен, но свойства по умолчанию не установлены, используйте следующее для явного получения реализации FIPS.
local provider = require "resty.openssl.provider"
assert(provider.load("fips"))
local cipher = require "resty.openssl.cipher"
local c = assert(cipher.new("aes256"))
print(c:get_provider_name()) -- выводит "default"
local c = assert(cipher.new("aes256", "fips=yes"))
print(c:get_provider_name()) -- выводит "fips"
openssl.get_fips_version_text
синтаксис: text, err = openssl.get_fips_version_text()
Возвращает текстовую версию модуля FIPS, доступно на OpenSSL 3.0 или новее.
openssl.set_default_properties
синтаксис: ok, err = openssl.set_default_properties(props)
Устанавливает свойства по умолчанию для всех будущих выборов алгоритмов EVP, как неявных, так и явных. См. "ALGORITHM FETCHING" в crypto(7) для получения информации о неявной и явной выборке.
openssl.list_cipher_algorithms
синтаксис: ret = openssl.list_cipher_algorithms(hide_provider?)
Возвращает доступные алгоритмы шифрования в виде массива. Установите hide_provider в true, чтобы
скрыть имя провайдера из результата.
openssl.list_digest_algorithms
синтаксис: ret = openssl.list_digest_algorithms(hide_provider?)
Возвращает доступные алгоритмы дайджеста в виде массива. Установите hide_provider в true, чтобы
скрыть имя провайдера из результата.
openssl.list_mac_algorithms
синтаксис: ret = openssl.list_mac_algorithms(hide_provider?)
Возвращает доступные алгоритмы MAC в виде массива. Установите hide_provider в true, чтобы
скрыть имя провайдера из результата.
openssl.list_kdf_algorithms
синтаксис: ret = openssl.list_kdf_algorithms(hide_provider?)
Возвращает доступные алгоритмы KDF в виде массива. Установите hide_provider в true, чтобы
скрыть имя провайдера из результата.
openssl.list_ssl_ciphers
синтаксис: cipher_string, err = openssl.list_ssl_ciphers(cipher_list?, ciphersuites?, protocol?)
Возвращает SSL-шифры по умолчанию в виде строки. cipher_list (до TLSv1.3) и
ciphersuites (TLSv1.3) могут использоваться для расширения настроек шифров, соответствующих
protocol. OpenSSL 4.x отклоняет "SSLv3", поскольку поддержка SSLv3 была
удалена.
openssl.list_ssl_ciphers()
openssl.list_ssl_ciphers("ECDHE-ECDSA-AES128-SHA")
openssl.list_ssl_ciphers("ECDHE-ECDSA-AES128-SHA", nil, "TLSv1.2")
openssl.list_ssl_ciphers("ECDHE-ECDSA-AES128-SHA", "TLS_CHACHA20_POLY1305_SHA256", "TLSv1.3")
resty.openssl.ctx
Модуль для обеспечения переключения контекста OSSL_LIB_CTX.
OSSL_LIB_CTX — это внутренний тип контекста библиотеки OpenSSL. Приложения могут выделять свой собственный контекст, но также могут использовать NULL для использования контекста по умолчанию с функциями, принимающими аргумент OSSL_LIB_CTX.
См. OSSL_LIB_CTX.3 для более глубокого изучения.
Контекст в настоящее время влияет на следующие модули:
- cipher
- digest
- kdf
- mac
- pkcs12.encode
- pkey
- provider
- rand
- x509, x509.csr, x509.crl и некоторые функции x509.store
Этот модуль доступен на OpenSSL 3.0 или новее.
ctx.new
синтаксис: ok, err = ctx.new(request_context_only?, conf_file?)
Создает новый контекст и использует его как контекст по умолчанию для этого модуля. Когда
request_context_only установлен в true, контекст используется только в контексте текущего
запроса. conf_file может дополнительно указывать файл конфигурации OpenSSL
для создания контекста.
Созданный контекст автоматически освобождается в соответствии с заданным жизненным циклом.
-- инициализировать экземпляр шифра AES из данной реализации провайдера только
-- для текущего запроса, не влияя на другие части кода
-- или будущие запросы, использующие тот же алгоритм.
assert(require("resty.openssl.ctx").new(true))
local p = assert(require("resty.openssl.provider").load("myprovider"))
local c = require("resty.openssl.cipher").new("aes256")
print(c:encrypt(string.rep("0", 32), string.rep("0", 16), "🦢"))
-- не нужно освобождать provider и ctx, они освобождаются GC автоматически
ctx.free
синтаксис: ctx.free(request_context_only?)
Освобождает контекст, который был ранее создан с помощью ctx.new.
resty.openssl.err
Модуль для предоставления сообщений об ошибках.
err.format_error
синтаксис: msg = err.format_error(ctx_msg?, return_code?, all_errors?)
синтаксис: msg = err.format_all_errors(ctx_msg?, return_code?)
Возвращает последнее сообщение об ошибке из последнего кода ошибки. Ошибки форматируются как:
[ctx_msg]: https://github.com/fffonion/lua-resty-openssl/blob/1.9.0/code: [return_code]: error:[error code]:[library name]:[func name]:[reason string]:[file name]:[line number]:
Для версий OpenSSL до 3.0 ошибки форматируются как:
[ctx_msg]: https://github.com/fffonion/lua-resty-openssl/blob/1.9.0/code: [return_code]: [file name]:[line number]:error:[error code]:[library name]:[func name]:[reason string]:
Если all_errors установлен в true, все ошибки, а не только последняя, будут возвращены в одной строке. Все ошибки, выбрасываемые
этой библиотекой внутренне, выбрасывают только последнюю ошибку.
Например:
local f = io.open("t/fixtures/ec_key_encrypted.pem"):read("*a")
local privkey, err = require("resty.openssl.pkey").new(f, {
format = "PEM",
type = "pr",
passphrase = "wrongpasswrod",
})
ngx.say(err)
-- pkey.new:load_key: error:4800065:PEM routines:PEM_do_header:bad decrypt:crypto/pem/pem_lib.c:467:
err.get_last_error_code
синтаксис: code = err.get_last_error_code()
Возвращает последний код ошибки.
err.get_lib_error_string
синтаксис: lib_error_message = err.get_lib_error_string(code?)
Возвращает имя библиотеки последнего кода ошибки в виде строки. Если code установлен, возвращает имя библиотеки,
соответствующее предоставленному коду ошибки.
err.get_reason_error_string
синтаксис: reason_error_message = err.get_reason_error_string(code?)
Возвращает причину последнего кода ошибки в виде строки. Если code установлен, возвращает причину,
соответствующую предоставленному коду ошибки.
resty.openssl.version
Модуль для предоставления информации о версии.
resty.openssl.provider
Модуль для взаимодействия с провайдерами. Этот модуль работает только на OpenSSL 3.0 или новее.
provider.load
синтаксис: pro, err = provider.load(name, try?)
Загружает провайдера с именем name. Если try установлен в true, OpenSSL не отключит
резервных провайдеров, если провайдер не может быть загружен и инициализирован. Если провайдер
загружается успешно, однако резервные провайдеры отключаются.
По умолчанию эта функция загружает провайдера в контекст по умолчанию, что означает, что она повлияет на другие приложения в том же процессе, использующие контекст по умолчанию. Если такое поведение нежелательно, рассмотрите возможность использования ctx для загрузки провайдера только в ограниченной области.
provider.istype
синтаксис: ok = pkey.provider(table)
Возвращает true, если таблица является экземпляром provider. В противном случае возвращает false.
provider.is_available
синтаксис: ok, err = provider.is_available(name)
Проверяет, доступен ли именованный провайдер для использования.
provider.set_default_search_path
синтаксис: ok, err = provider.set_default_search_path(name)
Указывает путь поиска по умолчанию, который будет использоваться для поиска провайдеров.
provider:unload
синтаксис: ok, err = pro:unload(name)
Выгружает провайдера, который был ранее загружен с помощью provider.load.
provider:self_test
синтаксис: ok, err = pro:self_test(name)
Запускает самотестирование провайдера по требованию. Если самотестирование не удается, провайдер не сможет предоставлять какие-либо дальнейшие услуги и алгоритмы.
provider:get_params
синтаксис: ok, err = pro:get_params(key1, key2?...)
Возвращает одно или несколько значений параметров провайдера.
local pro = require "resty.openssl.provider"
local p = pro.load("default")
local name = assert(p:get_params("name"))
print(name)
-- выводит "OpenSSL Default Provider"
local result = assert(p:get_params("name", "version", "buildinfo", "status"))
print(require("cjson").encode(result))
-- выводит метаданные провайдера; version и buildinfo зависят от выпуска OpenSSL
resty.openssl.pkey
Модуль для взаимодействия с закрытыми и открытыми ключами (EVP_PKEY).
Каждый тип ключа может поддерживать только часть операций:
| Тип ключа | Загрузка существующего ключа | Генерация ключа | Шифрование/Расшифровка | Подпись/Проверка | Обмен ключами | Инкапсуляция/Декапсуляция |
|---|---|---|---|---|---|---|
| RSA | Y | Y | Y | Y | Y (RSASVE, OpenSSL 3.5+) | |
| DH | Y | Y | Y | |||
| EC | Y | Y | Y (ECDSA) | Y (ECDH) | Y (DHKEM, OpenSSL 3.5+) | |
| Ed25519 | Y | Y | Y (PureEdDSA) | |||
| X25519 | Y | Y | Y (ECDH) | Y (DHKEM, OpenSSL 3.5+) | ||
| Ed448 | Y | Y | Y (PureEdDSA) | |||
| X448 | Y | Y | Y (ECDH) | Y (DHKEM, OpenSSL 3.5+) | ||
| ML-DSA (OpenSSL 3.5+) | Y | Y | Y | |||
| SLH-DSA (OpenSSL 3.5+) | Y | Y | Y | |||
| ML-KEM (OpenSSL 3.5+) | Y | Y | Y | |||
| ML-KEM TLS hybrid (OpenSSL 3.5+) | Зависит от провайдера | Y | Y |
Прямой поддержки шифрования и дешифрования для EC и ECX не существует, но такие процессы, как ECIES, возможны с помощью pkey:derive, kdf и cipher
pkey.new
Загрузка существующего ключа
синтаксис: pk, err = pkey.new(string, opts?)
Поддерживает загрузку закрытого или открытого ключа в формате PEM, DER или JWK, переданного первым аргументом string.
Второй параметр opts принимает необязательную таблицу для ограничения поведения загрузки ключа.
opts.format: установите явно"PEM","DER","JWK"для загрузки конкретного формата или установите"*"для автоматического определенияopts.type: установите явно"pr"для закрытого ключа,"pu"для открытого ключа; установите"*"для автоматического определения
При загрузке ключа RSA в кодировке PEM это может быть либо SubjectPublicKeyInfo/PrivateKeyInfo в кодировке PKCS#8, либо RSAPublicKey/RSAPrivateKey в кодировке PKCS#1.
При загрузке зашифрованного ключа в кодировке PEM парольная фраза для его расшифровки может быть установлена
в opts.passphrase или opts.passphrase_cb:
pkey.new(pem_or_der_text, {
format = "*", -- выбор из "PEM", "DER", "JWK" или "*" для автоматического определения
type = "*", -- выбор из "pr" для закрытого ключа, "pu" для открытого ключа и "*" для автоматического определения
passphrase = "secret password", -- парольная фраза для шифрования PEM
passphrase_cb = function()
return "secret password"
end, -- функция обратного вызова для парольной фразы шифрования PEM
}
При загрузке JWK есть несколько предостережений:
- Убедитесь, что передан закодированный текст JSON, он должен быть декодирован из base64.
- Ограничение opts.type для ключей JWK требует OpenSSL 3.0 или новее и
lua-resty-openssl 1.6.0 или новее. С OpenSSL 1.1.1 или старше
в выпусках lua-resty-openssl параметры в предоставленном JSON будут определять,
загружается ли закрытый или открытый ключ; указание type приведет к ошибке;
также часть открытого ключа для ключей OKP (параметр x) не учитывается и
выводится из части закрытого ключа (параметр d), если она указана.
- Поддерживаются только типы ключей RSA, P-256, P-384 и P-512 EC,
Ed25519, X25519, Ed448 и X448 OKP.
- Подписи и проверка должны использовать опцию ecdsa_use_raw для работы со стандартами JWS
для ключей EC. См. pkey:sign и pkey.verify для подробностей.
- При работе вне OpenResty необходимо установить библиотеку JSON (cjson или dkjson)
и basexx.
Генерация ключа
синтаксис: pk, err = pkey.new(config?)
Генерирует новый открытый или закрытый ключ.
Для генерации ключа RSA таблица config может содержать поля bits и exp для управления генерацией ключа.
Когда config опущен, эта функция генерирует 2048-битный ключ RSA с exponent 65537,
что эквивалентно:
local key, err = pkey.new({
type = 'RSA',
bits = 2048,
exp = 65537
})
Для генерации ключа EC или DH, пожалуйста, обратитесь к pkey.paramgen для возможных значений
таблицы config. Например:
local key, err = pkey.new({
type = 'EC',
curve = 'prime256v1',
})
На OpenSSL 3.0 или новее можно запросить любой тип ключа, реализованный загруженным провайдером, по имени. Например, OpenSSL 3.5 или новее предоставляет пост-квантовые типы ключей, включая:
local ml_dsa = assert(pkey.new({ type = "ML-DSA-44" }))
local ml_kem = assert(pkey.new({ type = "ML-KEM-768" }))
local slh_dsa = assert(pkey.new({ type = "SLH-DSA-SHA2-128s" }))
config.properties может использоваться для выбора реализации провайдера для этих
собственных типов ключей провайдера.
Также можно передать параметры EC или DH в кодировке PEM в config.param для генерации ключа:
local dhparam = pkey.paramgen({
type = 'DH',
group = 'dh_1024_160'
})
-- ИЛИ
-- local dhparam = io.read("dhparams.pem"):read("*a")
local key, err = pkey.new({
type = 'DH',
param = dhparam,
})
Также можно передавать необработанные управляющие строки pkeyopt в таблице config, как это используется в программе CLI genpkey.
См. openssl-genpkey(1) для списка опций.
Например:
pkey.new({
type = 'RSA',
bits = 2048,
exp = 65537,
})
-- эквивалентно
pkey.new({
type = 'RSA',
exp = 65537,
"rsa_keygen_bits:4096",
})
Композиция ключа
синтаксис: pk, err = pkey.new(config?)
Составляет открытый или закрытый ключ, используя существующие параметры. Чтобы увидеть список параметров для каждого ключа, обратитесь к pkey:set_parameters.
В таблице config должны присутствовать только type и params, все остальные ключи будут проигнорированы.
local private_bn = require "resty.openssl.bn".new("7F48282CCA4C1A65D589C06DBE9C42AE50FBFFDF3A18CBB48498E1DE47F11BE1A3486CD8FA950D68F111970F922279D8", 16)
local p_384, err = assert(require("resty.openssl.pkey").new({
type = "EC",
params = {
private = private_bn,
group = "secp384r1",
}
}))
pkey.istype
синтаксис: ok = pkey.istype(table)
Возвращает true, если таблица является экземпляром pkey. В противном случае возвращает false.
pkey.paramgen
синтаксис: pem_txt, err = pk.paramgen(config)
Генерирует параметры для ключа EC или DH и выводит в виде текста в кодировке PEM.
Для ключа EC:
| Параметр | Описание |
|---|---|
| type | "EC" |
| curve | Кривые EC. Если опущено, по умолчанию используется "prime192v1". Чтобы увидеть список поддерживаемых кривых EC, используйте openssl ecparam -list_curves. |
Для ключа DH:
| Параметр | Описание |
|---|---|
| type | "DH" |
| bits | Генерирует новый параметр DH с простым числом длиной bits. Если опущено, по умолчанию используется 2048. Начиная с OpenSSL 3.0, разрешены только биты, равные 2048. |
| group | Использует предопределенные группы вместо генерации новой. bit будет проигнорирован, если установлен group. |
Возможные значения для group:
- RFC7919 "ffdhe2048", "ffdhe3072",
"ffdhe4096", "ffdhe6144", "ffdhe8192"
- RFC5114 "dh_1024_160", "dh_2048_224", "dh_2048_256"
- RFC3526 "modp_1536", "modp_2048",
"modp_3072", "modp_4096", "modp_6144", "modp_8192"
local pem, err = pkey.paramgen({
type = 'EC',
curve = 'prime192v1',
})
local pem, err = pkey.paramgen({
type = 'DH',
group = 'ffdhe4096',
})
Также можно передавать необработанные управляющие строки pkeyopt в таблице config, как это используется в программе CLI genpkey.
См. openssl-genpkey(1) для списка опций.
pkey:get_provider_name
синтаксис: name = pkey:get_provider_name()
Возвращает имя провайдера pkey.
Эта функция доступна на OpenSSL 3.0 или новее.
pkey:gettable_params, pkey:settable_params, pkey:get_param, pkey:set_params
Запрос устанавливаемых или получаемых параметров и установка или получение параметров. См. Generic EVP parameter getter/setter.
pkey:get_parameters
синтаксис: parameters, err = pk:get_parameters()
Возвращает таблицу, содержащую parameters экземпляра pkey.
Для ключей ECX и, отдельно, собственных ключей провайдера OpenSSL 3, таких как ML-KEM,
ML-DSA, SLH-DSA и гибридных ключей ML-KEM TLS, таблица предоставляет бинарные поля public
и private, когда провайдер разрешает экспорт этих компонентов.
Некоторые провайдеры намеренно не экспортируют закрытый компонент.
pkey:set_parameters
синтаксис: ok, err = pk:set_parameters(params)
Устанавливает параметры pkey из таблицы params.
Если параметр не установлен в таблице params,
он остается нетронутым в экземпляре pkey.
Для ключей ECX и, отдельно, собственных ключей провайдера OpenSSL 3, public и
private содержат необработанные бинарные компоненты ключа. Установка любого компонента
заменяет базовый неизменяемый ключ провайдера, сохраняя объект pkey.
local pk, err = require("resty.openssl.pkey").new()
local parameters, err = pk:get_parameters()
local e = parameters.e
ngx.say(e:to_number())
-- выводит 65537
local ok, err = pk:set_parameters({
e = require("resty.openssl.bn").from_hex("100001")
})
local ok, err = pk:set_parameters(parameters)
Параметры для ключа RSA:
| Параметр | Описание | Тип |
|---|---|---|
| n | модуль, общий для открытого и закрытого ключа | bn |
| e | открытая экспонента | bn |
| d | закрытая экспонента | bn |
| p | первый множитель n | bn |
| q | второй множитель n | bn |
| dmp1 | d mod (p - 1), exponent1 |
bn |
| dmq1 | d mod (q - 1), exponent2 |
bn |
| iqmp | (InverseQ)(q) = 1 mod p, коэффициент |
bn |
Параметры для ключа EC:
| Параметр | Описание | Тип |
|---|---|---|
| private | закрытый ключ | bn |
| public | открытый ключ | bn |
| x | координата x открытого ключа | bn |
| y | координата y открытого ключа | bn |
| group | группа именованной кривой | [NID] в виде числа, при передаче в set_parameters() также можно использовать текстовое представление. Это отличается от luaossl, где возвращается экземпляр EC_GROUP. |
Невозможно установить x, y вместе с public, так как x и y — это, по сути, другое представление
public. Также в настоящее время можно установить x и y только одновременно.
Параметры для ключа DH:
| Параметр | Описание | Тип |
|---|---|---|
| private | закрытый ключ |