Перейти к содержанию

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.

Build Status luarocks opm

Описание

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 для более глубокого изучения.

Контекст в настоящее время влияет на следующие модули:

Этот модуль доступен на 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 закрытый ключ