lua-resty-openssl
基于 FFI 的 LuaJIT OpenSSL 绑定
$ opm get xiaocang/lua-resty-openssl
lua-resty-openssl
基于 FFI 的 LuaJIT OpenSSL 绑定,支持 OpenSSL 1.1 和 1.0.2 系列
描述
lua-resty-openssl
是一个基于 FFI 的 OpenSSL 绑定库,目前支持 OpenSSL 1.1.1
、1.1.0
和 1.0.2
系列。
状态
生产环境。
概要
该库很大程度上受到了 luaossl 的启发,但使用更接近原始 OpenSSL API 的命名转换。例如,OpenSSL C API 中名为 X509_set_pubkey
的函数预计存在于 resty.openssl.x509:set_pubkey
中。CamelCase 被替换为 underscore_case,例如 X509_set_serialNumber
变成 resty.openssl.x509:set_serial_number
。与 luaossl
的另一个区别是,错误永远不会使用 error()
抛出,而是作为最后一个参数返回。
由 new()
返回的每个 Lua 表都包含一个 cdata 对象 ctx
。用户不应该手动设置 ffi.gc
或调用 ctx
结构的相应析构函数(如 *_free
函数)。
resty.openssl
此元模块提供针对链接的 OpenSSL 库的版本健全性检查,并将所有导出的模块返回到一个表中。
return {
_VERSION = '0.1.0',
bn = require("resty.openssl.bn"),
cipher = require("resty.openssl.cipher"),
digest = require("resty.openssl.digest"),
hmac = require("resty.openssl.hmac"),
pkey = require("resty.openssl.pkey"),
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"),
name = require("resty.openssl.x509.name"),
store = require("resty.openssl.x509.store"),
}
openssl.luaossl_compat
语法: openssl.luaossl_compat()
提供 luaossl
风格的 API,它使用 camelCase 命名;用户可以期待直接替换。
例如,pkey:get_parameters
映射到 pkey:getParameters
。
请注意,并非所有 luaossl
API 都已实现,请检查自述文件以获取真相来源。
resty.openssl.version
提供版本信息的模块。
version_num
OpenSSL 版本号。
version_text
OpenSSL 版本文本。
version.version
语法: text = version.version(types)
返回各种 OpenSSL 信息。types
的可用值为
VERSION
CFLAGS
BUILT_ON
PLATFORM
DIR
ENGINES_DIR
VERSION_STRING
FULL_VERSION_STRING
MODULES_DIR
CPU_INFO
对于 OpenSSL 1.1.x 之前的版本,仅支持 VERSION
、CFLAGS
、BUILT_ON
、PLATFORM
和 DIR
。
local version = require("resty.openssl.version")
ngx.say(string.format("%x", version.version_num))
-- outputs "101000bf"
ngx.say(version.version_text)
-- outputs "OpenSSL 1.1.0k 28 May 2019"
ngx.say(version.version(version.PLATFORM))
-- outputs "darwin64-x86_64-cc"
OPENSSL_11
一个布尔值,指示链接的 OpenSSL 是否为 1.1 系列。
OPENSSL_10
一个布尔值,指示链接的 OpenSSL 是否为 1.0 系列。
resty.openssl.pkey
与私钥和公钥 (EVP_PKEY) 交互的模块。
pkey.new
语法: pk, err = pkey.new(config)
语法: pk, err = pkey.new(string, opts?)
语法: pk, err = pkey.new()
创建一个新的 pkey 实例。第一个参数可以是
一个
config
表,用于创建一个新的 PKEY 对。默认值为
pkey.new({
type = 'RSA',
bits = 2048,
exp = 65537
})
创建 EC 私钥
pkey.new({
type = 'EC',
curve = 'primve196v1',
})
一个
string
,包含 PEM 或 DER 格式的私钥或公钥;可以选择接受一个表opts
来明确加载format
和密钥type
。在加载 PEM 格式的密钥时,可以提供passphrase
或passphrase_cb
来解密密钥。
pkey.new(pem_or_der_text, {
format = "*", -- choice of "PEM", "DER" or "*" for auto detect
type = "*", -- choice of "p"r for privatekey, "pu" for public key and "*" for auto detect
passphrase = "secret password", -- the PEM encryption passphrase
passphrase_cb = function()
return "secret password"
end, -- the PEM encryption passphrase callback function
}
nil
,用于创建一个 2048 位的 RSA 密钥。一个
EVP_PKEY*
指针,用于返回一个包装的pkey
实例。通常用户不会使用这种方法。用户不应该自行释放指针,因为指针不会被复制。
pkey.istype
语法: ok = pkey.istype(table)
如果表是 pkey
的实例,则返回 true
。否则返回 false
。
pkey:get_parameters
语法: parameters, err = pk:get_parameters()
返回一个包含 pkey 实例的 parameters
的表。目前仅支持 RSA 密钥的 n
、e
和 d
参数。返回表中的每个值都是一个 resty.openssl.bn 实例。
local pk, err = require("resty.openssl.pkey").new()
local parameters, err = pk:get_parameters()
local e = parameters.e
ngx.say(ngx.encode_base64(e:to_binary()))
-- outputs 'AQAB' (65537) by default
pkey:sign
语法: signature, err = pk:sign(digest)
使用 pkey
实例中定义的私钥对一个 digest 进行签名。digest
参数必须是一个 resty.openssl.digest 实例。返回签名的原始二进制数据和错误(如果有)。
local pk, err = require("resty.openssl.pkey").new()
local digest, err = require("resty.openssl.digest").new("SHA256")
digest:update("dog")
local signature, err = pk:sign(digest)
ngx.say(ngx.encode_base64(signature))
pkey:verify
语法: ok, err = pk:verify(signature, digest)
验证签名(可以通过 pkey:sign 生成)。第二个参数必须是一个 resty.openssl.digest 实例,它使用与 sign
中使用的相同的摘要算法。如果验证成功,ok
返回 true
,否则返回 false
。请注意,当验证失败时,不会设置 err
。
pkey:encrypt
语法: cipher_txt, err = pk:encrypt(txt, padding?)
使用 pkey
实例加密明文 txt
,该实例必须加载了公钥。
当密钥是 RSA 密钥时,该函数接受一个可选的第二个参数 padding
,它可以是
pkey.PADDINGS = {
RSA_PKCS1_PADDING = 1,
RSA_SSLV23_PADDING = 2,
RSA_NO_PADDING = 3,
RSA_PKCS1_OAEP_PADDING = 4,
RSA_X931_PADDING = 5,
RSA_PKCS1_PSS_PADDING = 6,
}
如果省略,padding
默认值为 pkey.PADDINGS.RSA_PKCS1_PADDING
。
pkey:decrypt
语法: txt, err = pk:decrypt(cipher_txt, padding?)
使用 pkey 实例解密密文 cipher_txt
,该实例必须加载了私钥。
可选的第二个参数 padding
在 pkey:encrypt 中具有相同的含义。
local pkey = require("resty.openssl.pkey")
local privkey, err = pkey.new()
local pub_pem = privkey:to_PEM("public")
local pubkey, err = pkey.new(pub_pem)
local s, err = pubkey:encrypt("🦢", pkey.PADDINGS.RSA_PKCS1_PADDING)
ngx.say(#s)
-- Outputs 256
local decrypted, err = privkey:decrypt(s)
ngx.say(decrypted)
-- Outputs "🦢"
pkey:to_PEM
语法: pem, err = pk:to_PEM(private_or_public?)
以 PEM 格式的文本输出 pkey 实例的私钥或公钥。第一个参数必须是 public
、PublicKey
、private
、PrivateKey
或 nil 之一。默认情况下,它返回公钥。
resty.openssl.bn
公开 BIGNUM 结构的模块。
bn.new
语法: b, err = bn.new(number?)
创建一个 bn
实例。第一个参数可以是 Lua 数字或 nil
,以创建一个空的实例。
bn.dup
语法: b, err = bn.dup(bn_ptr_cdata)
复制一个 BIGNUM*
以创建一个新的 bn
实例。
bn.istype
语法: ok = bn.istype(table)
如果表是 bn
的实例,则返回 true
。否则返回 false
。
bn.from_binary
从二进制字符串创建 bn
实例。
local b, err = require("resty.openssl.bn").from_binary(ngx.decode_base64("WyU="))
local bin, err = b:to_binary()
ngx.say(ngx.encode_base64(bin))
-- outputs "WyU="
bn:to_binary
语法: bin, err = bn:to_binary()
以二进制字符串导出 BIGNUM 值。
bn:to_hex
语法: hex, err = bn:to_hex()
以十六进制编码字符串导出 BIGNUM 值。
local b, err = require("resty.openssl.bn").new(23333)
local bin, err = b:to_binary()
ngx.say(ngx.encode_base64(bin))
-- outputs "WyU="
local hex, err = b:to_hex()
ngx.say(hex)
-- outputs "5B25"
resty.openssl.cipher
与对称加密 (EVP_CIPHER) 交互的模块。
cipher.new
语法: d, err = cipher.new(cipher_name)
创建一个密码实例。cipher_name
是密码算法名称的不区分大小写的字符串。要查看已实现的密码算法列表,请使用 openssl list -cipher-algorithms
。
cipher.istype
语法: ok = cipher.istype(table)
如果表是 cipher
的实例,则返回 true
。否则返回 false
。
cipher:encrypt
语法: s, err = cipher:encrypt(key, iv?, s, no_padding?)
使用密钥 key
和 IV iv
加密文本 s
。返回加密后的文本(以原始二进制字符串形式)和错误(如果有)。可以选择接受一个布尔值 no_padding
,它指示密码是否启用或禁用填充,默认值为 false
(启用填充)。如果 no_padding
为 true
,则 s
的长度必须是块大小的倍数,否则会发生错误。
此函数是 cipher:init
加 cipher:final
的简写形式。
cipher:decrypt
语法: s, err = cipher:decrypt(key, iv?, s, no_padding?)
使用密钥 key
和 IV iv
解密文本 s
。返回解密后的文本(以原始二进制字符串形式)和错误(如果有)。可以选择接受一个布尔值 no_padding
,它指示密码是否启用或禁用填充,默认值为 false
(启用填充)。如果 no_padding
为 true
,则 s
的长度必须是块大小的倍数,否则会发生错误;此外,解密后的文本中的填充不会被删除。
此函数是 cipher:init
加 cipher:final
的简写形式。
cipher:init
语法: ok, err = cipher:init(key, iv?, opts?)
使用密钥 key
和 IV iv
初始化密码。可选的第三个参数是一个表,包含
{
is_encrypt = false,
no_padding = false,
}
在 cipher:update
和 cipher:final
之前需要调用该函数,但 cipher:encrypt
或 cipher:decrypt
不需要。
cipher:update
语法: s, err = cipher:update(partial, ...)
使用一个或多个字符串更新密码。如果密码有大于块大小的数据要刷新,则该函数将返回一个非空字符串作为第一个参数。此函数可以以流式方式使用,以加密或解密连续的数据流。
cipher:final
语法: s, err = cipher:final(partial?)
返回加密或解密后的文本(以原始二进制字符串形式),可以选择接受一个字符串进行加密或解密。
-- encryption
local c, err = require("resty.openssl.cipher").new("aes256")
c:init(string.rep("0", 32), string.rep("0", 16), {
is_encrypt = true,
})
c:update("🦢")
local cipher, err = c:final()
ngx.say(ngx.encode_base64(cipher))
-- outputs "vGJRHufPYrbbnYYC0+BnwQ=="
-- OR:
local c, err = require("resty.openssl.cipher").new("aes256")
local cipher, err = c:encrypt(string.rep("0", 32), string.rep("0", 16), "🦢")
ngx.say(ngx.encode_base64(cipher))
-- outputs "vGJRHufPYrbbnYYC0+BnwQ=="
-- decryption
local encrypted = ngx.decode_base64("vGJRHufPYrbbnYYC0+BnwQ==")
local c, err = require("resty.openssl.cipher").new("aes256")
c:init(string.rep("0", 32), string.rep("0", 16), {
is_encrypt = false,
})
c:update(encrypted)
local cipher, err = c:final()
ngx.say(cipher)
-- outputs "🦢"
-- OR:
local c, err = require("resty.openssl.cipher").new("aes256")
local cipher, err = c:decrypt(string.rep("0", 32), string.rep("0", 16), encrypted)
ngx.say(cipher)
-- outputs "🦢"
resty.openssl.digest
与消息摘要 (EVP_MD_CTX) 交互的模块。
digest.new
语法: d, err = digest.new(digest_name?)
创建一个摘要实例。digest_name
是摘要算法名称的不区分大小写的字符串。要查看已实现的摘要算法列表,请使用 openssl list -digest-algorithms
。
如果省略 digest_name
,则默认值为 sha1
。
digest.istype
语法: ok = digest.istype(table)
如果表是 digest
的实例,则返回 true
。否则返回 false
。
digest:update
语法: ok, err = digest:update(partial, ...)
使用一个或多个字符串更新摘要。
digest:final
语法: str, err = digest:final(partial?)
返回摘要(以原始二进制字符串形式),可以选择接受一个字符串进行摘要。
local d, err = require("resty.openssl.digest").new("sha256")
d:update("🦢")
local digest, err = d:final()
ngx.say(ngx.encode_base64(digest))
-- outputs "tWW/2P/uOa/yIV1gRJySJLsHq1xwg0E1RWCvEUDlla0="
-- OR:
local d, err = require("resty.openssl.digest").new("sha256")
local digest, err = d:final("🦢")
ngx.say(ngx.encode_base64(digest))
-- outputs "tWW/2P/uOa/yIV1gRJySJLsHq1xwg0E1RWCvEUDlla0="
resty.openssl.hmac
与基于散列的消息认证码 (HMAC_CTX) 交互的模块。
hmac.new
语法: h, err = hmac.new(key, digest_name?)
创建一个 hmac 实例。digest_name
是摘要算法名称的不区分大小写的字符串。要查看已实现的摘要算法列表,请使用 openssl list -digest-algorithms
。
如果省略 digest_name
,则默认值为 sha1
。
hmac.istype
语法: ok = hmac.istype(table)
如果表是 hmac
的实例,则返回 true
。否则返回 false
。
hmac:update
语法: ok, err = hmac:update(partial, ...)
使用一个或多个字符串更新 HMAC。
hmac:final
语法: str, err = hmac:final(partial?)
返回 HMAC(以原始二进制字符串形式),可以选择接受一个字符串进行摘要。
local d, err = require("resty.openssl.hmac").new("goose", "sha256")
d:update("🦢")
local hmac, err = d:final()
ngx.say(ngx.encode_base64(hmac))
-- outputs "k2UcrRp25tj1Spff89mJF3fAVQ0lodq/tJT53EYXp0c="
-- OR:
local d, err = require("resty.openssl.hmac").new("goose", "sha256")
local hmac, err = d:final("🦢")
ngx.say(ngx.encode_base64(hmac))
-- outputs "k2UcrRp25tj1Spff89mJF3fAVQ0lodq/tJT53EYXp0c="
resty.openssl.objects
ASN1_OBJECT 的辅助模块。
objects.obj2table
语法: tbl = objects.bytes(asn1_obj)
将 ASN1_OBJECT 指针转换为 Lua 表,其中
{
id: OID of the object,
nid: NID of the object,
sn: short name of the object,
ln: long name of the object,
}
objects.nid2table
语法: tbl, err = objects.nid2table(nid)
将 [NID] 转换为 Lua 表,返回与 objects.obj2table 相同的格式
objects.txt2nid
语法: nid, err = objects.txt2nid(txt)
将文本表示转换为 [NID]。
resty.openssl.rand
与随机数生成器交互的模块。
rand.bytes
语法: str, err = rand.bytes(length)
生成长度为 length
的随机字节。
resty.openssl.x509
与 X.509 证书交互的模块。
x509.new
语法: crt, err = x509.new(txt?, fmt?)
创建一个 x509
实例。txt
可以是 PEM 或 DER 格式的文本;fmt
是加载特定格式的 PEM
、DER
或自动检测的 *
之一。
当省略 txt
时,new()
将创建一个空的 x509
实例。
x509.dup
语法: x509, err = x509.dup(x509_ptr_cdata)
复制一个 X509*
以创建一个新的 x509
实例。
x509.istype
语法: ok = x509.istype(table)
如果表是 x509
的实例,则返回 true
。否则返回 false
。
x509:digest
语法: d, err = x509:digest(digest_name?)
返回 X509 证书对象的 DER 表示形式的摘要,以原始二进制文本形式表示。
digest_name
是一个不区分大小写的摘要算法名称字符串。要查看已实现的摘要算法列表,请使用 openssl list -digest-algorithms
。
如果省略 digest_name
,则默认值为 sha1
。
x509:pubkey_digest
语法: d, err = x509:pubkey_digest(digest_name?)
返回 X509 对象中公钥的 DER 表示形式的摘要,以原始二进制文本形式表示。
digest_name
是一个不区分大小写的摘要算法名称字符串。要查看已实现的摘要算法列表,请使用 openssl list -digest-algorithms
。
如果省略 digest_name
,则默认值为 sha1
。
x509:get_, x509:set_
语法: ok, err = x509:set_attribute(instance)
语法: instance, err = x509:get_attribute()
x509 属性的 setter 和 getter 使用相同的语法。
| 属性名称 | 类型 | 描述 | | ------------ | ---- | ----------- | | issuer_name | x509.name | 证书的颁发者 | | not_before | number | 证书在有效期前的 Unix 时间戳 | | not_after | number | 证书在有效期后的 Unix 时间戳 | | pubkey | pkey | 证书的公钥 | | serial_number | bn | 证书的序列号 | | subject_name | x509.name | 证书的主题 | | version | number | 证书的版本,值为版本号减 1。例如,2
代表 版本 3
|
此外,还提供了扩展的 getter 和 setter。
| 扩展名称 | 类型 | 描述 | | ------------ | ---- | ----------- | | subject_alt_name | x509.altname | 证书的 主题备用名称,SAN 通常用于定义“其他通用名称” | | issuer_alt_name | x509.altname | 证书的 颁发者备用名称 | | basic_constraints | table, { ca = bool, pathlen = int} | 证书的 基本约束 | | info_access | x509.extension.info_access | 证书的 授权信息访问,包含 OCSP 响应者 URL 等信息。 | | crl_distribution_points | x509.extension.dist_points | 证书的 CRL 分发点,包含证书吊销列表 (CRL) URL 等信息。 |
对于所有扩展,get_{extension}_critical
和 set_{extension}_critical
也受支持,以访问扩展的 critical
标志。
local x509, err = require("resty.openssl.x509").new()
err = x509:set_not_before(ngx.time())
local not_before, err = x509:get_not_before()
ngx.say(not_before)
-- outputs 1571875065
err = x509:set_basic_constraints_critical(true)
如果类型是表,setter 需要一个具有不区分大小写键的表来设置;getter 返回给定不区分大小写键的值,或者如果未提供键则返回所有键的表。
local x509, err = require("resty.openssl.x509").new()
err = x509:set_basic_constraints({
cA = false,
pathlen = 0,
})
ngx.say(x509:get_basic_constraints("pathlen"))
-- outputs 0
ngx.say(x509:get_basic_constraints())
-- outputs '{"ca":false,"pathlen":0}'
请注意,用户还可以通过 x509:get_extension 和 x509:set_extension 访问特定扩展,而后面两个函数分别返回或需要 extension。如果需要修改当前扩展,用户可以使用此处列出的 getter 和 setter;如果用户要添加或替换整个扩展或未实现 getter/setter,请使用 x509:get_extension 或 x509:set_extension。如果 getter 返回 x509.*
实例类型,则可以通过 extension:from_data 将其转换为 extension 实例,然后由 x509:get_extension 和 x509:set_extension 使用。
x509:get_lifetime
语法: not_before, not_after, err = x509:get_lifetime()
x509:get_not_before
加上 x509:get_not_after
的快捷方式。
x509:set_lifetime
语法: ok, err = x509:set_lifetime(not_before, not_after)
x509:set_not_before
加上 x509:set_not_after
的快捷方式。
x509:get_extension
语法: extension, pos, err = x509:get_extension(nid_or_txt, last_pos?)
获取与给定 [NID] 匹配的 X.509 extension
到证书,返回一个 resty.openssl.x509.extension 实例和找到的位置。
如果定义了 last_pos
,则该函数从该位置开始搜索;否则,它从开头开始查找。索引从 1 开始。
local ext, pos, err = x509:get_extension("keyUsage")
x509:add_extension
语法: ok, err = x509:add_extension(extension)
将 X.509 extension
添加到证书,第一个参数必须是 resty.openssl.x509.extension 实例。
local extension, err = require("resty.openssl.extension").new({
"keyUsage", "critical,keyCertSign,cRLSign",
})
local x509, err = require("resty.openssl.x509").new()
local ok, err = x509:add_extension(extension)
x509:set_extension
语法: ok, err = x509:set_extension(extension, last_pos?)
将 X.509 extension
添加到证书,第一个参数必须是 resty.openssl.x509.extension 实例。与 x509:add_extension 的区别在于,在这个函数中,如果已经存在具有相同类型的 extension
,则旧扩展将被替换。
如果定义了 last_pos
,则该函数从该位置替换相同的扩展;否则,它从开头开始查找。索引从 1 开始。如果未找到,则返回 nil, nil
。
请注意,此函数不是线程安全的。
x509:get_critical
语法: ok, err = x509:get_critical(nid_or_txt)
获取与给定 [NID] 匹配的 X.509 extension
的 critical 标志,从证书中获取。
x509:set_critical
语法: ok, err = x509:set_critical(nid_or_txt, crit?)
设置与给定 [NID] 匹配的 X.509 extension
的 critical 标志,到证书。
x509:get_ocsp_url
语法: url_or_urls, err = x509:get_ocsp_url(return_all?)
获取 X.509 对象的 OCSP URL(s)。如果 return_all
设置为 true,则返回包含所有 OCSP URL 的表;否则,返回找到的第一个 OCSP URL 的字符串。如果未找到扩展,则返回 nil
。
x509:get_crl_url
语法: url_or_urls, err = x509:get_crl_url(return_all?)
获取 X.509 对象的 CRL URL(s)。如果 return_all
设置为 true,则返回包含所有 CRL URL 的表;否则,返回找到的第一个 CRL URL 的字符串。如果未找到扩展,则返回 nil
。
x509:sign
语法: ok, err = x509:sign(pkey, digest?)
使用 pkey
指定的私钥对证书进行签名,pkey
必须是存储私钥的 resty.openssl.pkey。可以选择接受 digest
参数来设置摘要方法,该方法必须是 resty.openssl.digest 实例。返回一个布尔值,指示签名是否成功,以及任何错误。
x509:verify
语法: ok, err = x509:verify(pkey)
使用 pkey
指定的公钥验证证书签名,pkey
必须是 resty.openssl.pkey。返回一个布尔值,指示验证是否成功以及任何错误。
x509:tostring
语法: str, err = x509:tostring(fmt?)
以 PEM 格式的文本或 DER 格式的二进制输出证书。第一个参数可以是 PEM
或 DER
;如果省略,则此函数默认输出 PEM。
x509:to_PEM
语法: pem, err = x509:to_PEM()
以 PEM 格式的文本输出证书。
resty.openssl.x509.csr
与证书签名请求 (X509_REQ) 交互的模块。
csr.new
语法: csr, err = csr.new(txt?, fmt?)
创建一个空的 csr
实例。txt
可以是 PEM 或 DER 格式的文本;fmt
是 PEM
、DER
用于加载特定格式,或 *
用于自动检测。
如果省略了 txt
,则 new()
创建一个空的 csr
实例。
csr.istype
语法: ok = csr.istype(table)
如果表是 csr
的实例,则返回 true
。否则返回 false
。
csr:get_, csr:set_
语法: ok, err = csr:set_attribute(instance)
语法: instance, err = csr:get_attribute()
x509 属性的 setter 和 getter 使用相同的语法。
| 属性名称 | 类型 | 描述 | | ------------ | ---- | ----------- | | pubkey | pkey | 证书请求的公钥 | | subject_name | x509.name | 证书请求的主题 | | version | number | 证书请求的版本,值为版本号减 1。例如,2
代表 版本 3
|
此外,还提供了扩展的 getter 和 setter。
| 扩展名称 | 类型 | 描述 | | ------------ | ---- | ----------- | | subject_alt_name | x509.altname | 证书请求的 主题备用名称,SAN 通常用于定义“其他通用名称” |
local csr, err = require("resty.openssl.csr").new()
err = csr:set_version(3)
local version, err = csr:get_version()
ngx.say(version)
-- outputs 3
csr:set_subject_alt
与 csr:set_subject_alt_name 相同,此函数已弃用,以与其他函数的命名约定保持一致。
csr:sign
语法: ok, err = csr:sign(pkey, digest?)
使用 pkey
指定的私钥对证书请求进行签名,pkey
必须是存储私钥的 resty.openssl.pkey。可以选择接受 digest
参数来设置摘要方法,该方法必须是 resty.openssl.digest 实例。返回一个布尔值,指示签名是否成功,以及任何错误。
csr:verify
语法: ok, err = csr:verify(pkey)
使用 pkey
指定的公钥验证 CSR 签名,pkey
必须是 resty.openssl.pkey。返回一个布尔值,指示验证是否成功以及任何错误。
csr:tostring
语法: str, err = csr:tostring(fmt?)
以 PEM 格式的文本或 DER 格式的二进制输出证书请求。第一个参数可以是 PEM
或 DER
;如果省略,则此函数默认输出 PEM。
csr:to_PEM
语法: pem, err = csr:to_PEM(?)
以 PEM 格式的文本输出 CSR。
resty.openssl.crl
与 X509_CRL(证书吊销列表)交互的模块。
crl.new
语法: crt, err = crl.new(txt?, fmt?)
创建一个 crl
实例。txt
可以是 PEM 或 DER 格式的文本;fmt
是 PEM
、DER
用于加载特定格式,或 *
用于自动检测。
如果省略了 txt
,则 new()
创建一个空的 crl
实例。
crl.dup
语法: crl, err = crl.dup(crl_ptr_cdata)
复制 X509_CRL*
以创建一个新的 crl
实例。
crl.istype
语法: ok = crl.istype(table)
如果表是 crl
的实例,则返回 true
。否则返回 false
。
crl:get_, crl:set_
语法: ok, err = crl:set_attribute(instance)
语法: instance, err = crl:get_attribute()
crl 属性的 setter 和 getter 使用相同的语法。
| 属性名称 | 类型 | 描述 | | ------------ | ---- | ----------- | | issuer_name | x509.name | CRL 的颁发者 | | last_update | number | CRL 在有效期前的 Unix 时间戳 | | next_update | number | CRL 在有效期后的 Unix 时间戳 | | version | number | 证书的版本,值为版本号减 1。例如,2
代表 版本 3
|
此外,还提供了扩展的 getter 和 setter。
| 扩展名称 | 类型 | 描述 | | ------------ | ---- | ----------- |
对于所有扩展,get_{extension}_critical
和 set_{extension}_critical
也受支持,以访问扩展的 critical
标志。
local crl, err = require("resty.openssl.crl").new()
err = crl:set_next_update(ngx.time())
local not_before, err = crl:get_next_update()
ngx.say(not_before)
-- outputs 1571875065
请注意,用户还可以通过 crl:get_extension 和 crl:set_extension 访问特定扩展,而后面两个函数分别返回或需要 extension。如果需要修改当前扩展,用户可以使用此处列出的 getter 和 setter;如果用户要添加或替换整个扩展或未实现 getter/setter,请使用 crl:get_extension 或 crl:set_extension。如果 getter 返回 crl.*
实例类型,则可以通过 extension:from_data 将其转换为 extension 实例,然后由 crl:get_extension 和 crl:set_extension 使用。
crl:get_extension
语法: extension, pos, err = crl:get_extension(nid_or_txt, last_pos?)
获取与给定 [NID] 匹配的 X.509 extension
到 CRL,返回一个 resty.openssl.x509.extension 实例和找到的位置。
如果定义了 last_pos
,则该函数从该位置开始搜索;否则,它从开头开始查找。索引从 1 开始。
crl:add_extension
语法: ok, err = crl:add_extension(extension)
将 X.509 extension
添加到 CRL,第一个参数必须是 resty.openssl.x509.extension 实例。
crl:set_extension
语法: ok, err = crl:set_extension(extension, last_pos?)
向 CRL 添加 X.509 扩展
,第一个参数必须是 resty.openssl.x509.extension 实例。与 crl:add_extension 的区别在于,如果该函数中已经存在相同类型的 扩展
,则会替换旧的扩展。
如果定义了 last_pos
,则该函数从该位置替换相同的扩展;否则,它从开头开始查找。索引从 1 开始。如果未找到,则返回 nil, nil
。
请注意,此函数不是线程安全的。
crl:get_critical
语法: ok, err = crl:get_critical(nid_or_txt)
获取 CRL 中与给定 [NID] 匹配的 X.509 扩展
的关键标志。
crl:set_critical
语法: ok, err = crl:set_critical(nid_or_txt, crit?)
将与给定 [NID] 匹配的 X.509 扩展
的关键标志设置为 CRL。
crl:sign
语法: ok, err = crl:sign(pkey, digest?)
使用 pkey
指定的私钥对 CRL 进行签名,pkey
必须是存储私钥的 resty.openssl.pkey。可以选择接受 digest
参数来设置摘要方法,该方法必须是 resty.openssl.digest 实例。返回一个布尔值,指示签名是否成功,如果有错误则返回错误。
crl:verify
语法: ok, err = crl:verify(pkey)
使用 pkey
指定的公钥验证 CRL 签名,pkey
必须是 resty.openssl.pkey。返回一个布尔值,指示验证是否成功,如果有错误则返回错误。
crl:tostring
语法: str, err = crl:tostring(fmt?)
输出 PEM 格式的文本或 DER 格式的二进制 CRL。第一个参数可以是 PEM
或 DER
的选择;如果省略,则此函数默认输出 PEM。
crl:to_PEM
语法: pem, err = crl:to_PEM()
输出 PEM 格式的文本 CRL。
resty.openssl.x509.name
用于与 X.509 名称交互的模块。
name.new
语法: name, err = name.new()
创建一个空的 name
实例。
name.dup
语法: name, err = name.dup(name_ptr_cdata)
复制 X509_NAME*
以创建一个新的 name
实例。
name.istype
语法: ok = name.istype(table)
如果表是 name
的实例,则返回 true
。否则返回 false
。
name:add
语法: name, err = name:add(nid_text, txt)
将 ASN.1 对象添加到 name
中。第一个参数是 [NID] 的 文本表示。第二个参数是 ASN.1 对象的纯文本值。
成功时返回 name 实例本身,失败时返回 nil
和错误。
此函数可以以链接方式多次调用。
local name, err = require("resty.openssl.x509.name").new()
local _, err = name:add("CN", "example.com")
_, err = name
:add("C", "US")
:add("ST", "California")
:add("L", "San Francisco")
name:find
语法: obj, pos, err = name:find(nid_text, last_pos?)
从 last_pos
的位置找到具有给定 [NID] 的 文本表示 的 ASN.1 对象。省略 last_pos
参数,find
将从开头开始查找。
返回表中的对象,格式与 这里 描述的相同,找到对象的 position 和错误(如果有)。索引从 1 开始。如果未找到,则返回 nil, nil
。
local name, err = require("resty.openssl.x509.name").new()
local _, err = name:add("CN", "example.com")
:add("CN", "example2.com")
local obj, pos, err = name:find("CN")
ngx.say(obj.blob, " at ", pos)
-- outputs "example.com at 1"
local obj, pos, err = name:find("2.5.4.3", 1)
ngx.say(obj.blob, " at ", pos)
-- outputs "example2.com at 2"
name:__metamethods
语法: for k, obj in pairs(name)
语法: len = #name
语法: k, v = name[i]
访问底层对象,因为它是一个 Lua 表。确保您的 LuaJIT 使用 -DLUAJIT_ENABLE_LUA52COMPAT
标志编译;否则使用 all
、each
、index
和 count
代替。
另请参见 "堆栈式对象函数"。
每个返回的对象都是一个表,其中
{
id: OID of the object,
nid: NID of the object,
sn: short name of the object,
ln: long name of the object,
blob: value of the object,
}
local name, err = require("resty.openssl.x509.name").new()
local _, err = name:add("CN", "example.com")
for k, obj in pairs(name) do
ngx.say(k, ":", require("cjson").encode(obj))
end
-- outputs 'CN: {"sn":"CN","id":"2.5.4.3","nid":13,"blob":"3.example.com","ln":"commonName"}'
resty.openssl.x509.altname
用于与 GENERAL_NAMES 交互的模块,GENERAL_NAMES 是 X.509 名称的扩展。
altname.new
语法: altname, err = altname.new()
创建一个空的 altname
实例。
altname.istype
语法: altname = digest.istype(table)
如果表是 altname
的实例,则返回 true
。否则返回 false
。
altname:add
语法: altname, err = altname:add(key, value)
将名称添加到 altname 堆栈,第一个参数不区分大小写,可以是以下之一
RFC822Name
RFC822
RFC822
UniformResourceIdentifier
URI
DNSName
DNS
IPAddress
IP
DirName
此函数可以以链接方式多次调用。
local altname, err = require("resty.openssl.x509").new()
local _, err = altname:add("DNS", "example.com")
_, err = altname
:add("DNS", "2.example.com")
:add("DnS", "3.example.com")
altname:__metamethods
语法: for k, obj in pairs(altname)
语法: len = #altname
语法: k, v = altname[i]
访问底层对象,因为它是一个 Lua 表。确保您的 LuaJIT 使用 -DLUAJIT_ENABLE_LUA52COMPAT
标志编译;否则使用 all
、each
、index
和 count
代替。
另请参见 "堆栈式对象函数"。
resty.openssl.x509.extension
用于与 X.509 扩展交互的模块。
extension.new
语法: ext, err = extension.new(name, value, data)
创建一个新的 extension
实例。name
和 value
是 OpenSSL 任意扩展格式 中的字符串。
data
可以是表或 nil。当 data 是表时,将查找以下键
data = {
issuer = resty.openssl.x509 instance,
subject = resty.openssl.x509 instance,
request = resty.opensl.csr instance,
}
示例
local x509, err = require("resty.openssl.x509").new()
local extension = require("resty.openssl.x509.extension")
local ext, err = extension.new("extendedKeyUsage", "serverAuth,clientAuth")
ext, err = extension.new("subjectKeyIdentifier", "hash", {
subject = crt
})
extension.dup
语法: ext, err = extension.dup(extension_ptr_cdata)
从 X509_EXTENSION*
指针创建一个新的 extension
实例。
extension.from_data
语法: ext, ok = extension.from_data(table, nid, crit?)
创建一个新的 extension
实例。table
可以是以下实例
nid
是 [NID] 的编号,crit
是扩展的关键标志。
extension.istype
语法: ok = extension.istype(table)
如果表是 extension
的实例,则返回 true
。否则返回 false
。
extension:get_critical
语法: crit, err = extension:get_critical()
如果扩展是关键的,则返回 true
。否则返回 false
。
extension:set_critical
语法: ok, err = extension:set_critical(crit)
设置扩展的关键标志。
extension:get_object
语法: obj = extension:get_object()
返回扩展的名称作为 ASN.1 对象。用户可以进一步使用 resty.openssl.objects 中的辅助函数打印人类可读的文本。
extension:text
语法: txt, err = extension:text()
返回扩展的文本表示。
local objects = require "resty.openssl.objects"
ngx.say(cjson.encode(objects.obj2table(extension:get_object())))
-- outputs {"ln":"X509v3 Subject Key Identifier","nid":82,"sn":"subjectKeyIdentifier","id":"2.5.29.14"}
ngx.say(extension:text())
-- outputs C9:C2:53:61:66:9D:5F:AB:25:F4:26:CD:0F:38:9A:A8:49:EA:48:A9
extension:tostring
语法: txt, err = extension:tostring()
与 extension:text 相同。
resty.openssl.x509.extension.dist_points
用于与 CRL 分发点 (DIST_POINT 堆栈) 交互的模块。
dist_points.new
语法: dp, err = dist_points.new()
创建一个新的 dist_points
实例。
dist_points.dup
语法: dp, err = dist_points.dup(dist_points_ptr_cdata)
复制 STACK_OF(DIST_POINT)
以创建一个新的 dist_points
实例。该函数创建一个新的堆栈,并将所有元素的引用计数增加 1。但它不会复制元素本身。
dist_points.istype
语法: ok = dist_points.istype(table)
如果表是 dist_points
的实例,则返回 true
。否则返回 false
。
dist_points:__metamethods
语法: for i, obj in ipairs(dist_points)
语法: len = #dist_points
语法: obj = dist_points[i]
访问底层对象,因为它是一个 Lua 表。确保您的 LuaJIT 使用 -DLUAJIT_ENABLE_LUA52COMPAT
标志编译;否则使用 all
、each
、index
和 count
代替。
另请参见 "堆栈式对象函数"。
resty.openssl.x509.extension.info_access
用于与授权信息访问数据 (AUTHORITY_INFO_ACCESS、ACCESS_DESCRIPTION 堆栈) 交互的模块。
info_access.new
语法: aia, err = info_access.new()
创建一个新的 info_access
实例。
info_access.dup
语法: aia, err = info_access.dup(info_access_ptr_cdata)
复制 AUTHORITY_INFO_ACCESS
以创建一个新的 info_access
实例。该函数创建一个新的堆栈,并将所有元素的引用计数增加 1。但它不会复制元素本身。
info_access.istype
语法: ok = info_access.istype(table)
如果表是 info_access
的实例,则返回 true
。否则返回 false
。
info_access:add
语法: ok, err = info_access:add(x509)
将 x509
对象添加到 info_access 中。第一个参数必须是 resty.openssl.x509 实例。
info_access:__metamethods
语法: for i, obj in ipairs(info_access)
语法: len = #info_access
语法: obj = info_access[i]
访问底层对象,因为它是一个 Lua 表。确保您的 LuaJIT 使用 -DLUAJIT_ENABLE_LUA52COMPAT
标志编译;否则使用 all
、each
、index
和 count
代替。
另请参见 "堆栈式对象函数"。
resty.openssl.x509.chain
用于与 X.509 堆栈交互的模块。
chain.new
语法: ch, err = chain.new()
创建一个新的 chain
实例。
chain.dup
语法: ch, err = chain.dup(chain_ptr_cdata)
复制 STACK_OF(X509)
以创建一个新的 chain
实例。该函数创建一个新的堆栈,并将所有元素的引用计数增加 1。但它不会复制元素本身。
chain.istype
语法: ok = chain.istype(table)
如果表是 chain
的实例,则返回 true
。否则返回 false
。
chain:add
语法: ok, err = chain:add(x509)
将 x509
对象添加到链中。第一个参数必须是 resty.openssl.x509 实例。
chain:__metamethods
语法: for i, obj in ipairs(chain)
语法: len = #chain
语法: obj = chain[i]
访问底层对象,因为它是一个 Lua 表。确保您的 LuaJIT 使用 -DLUAJIT_ENABLE_LUA52COMPAT
标志编译;否则使用 all
、each
、index
和 count
代替。
另请参见 "堆栈式对象函数"。
resty.openssl.x509.store
用于与 X.509 证书存储 (X509_STORE) 交互的模块。
store.new
语法: st, err = store.new()
创建一个新的 store
实例。
store.istype
语法: ok = store.istype(table)
如果表是 store
的实例,则返回 true
。否则返回 false
。
store:use_default
语法: ok, err = store:use_default()
从硬编码的默认路径中将证书加载到 X509_STORE 中。
请注意,要正确加载“默认”CA,通常需要安装 CA 证书捆绑包。例如,Debian/Ubuntu 中的包称为 ca-certificates
。
store:add
语法: ok, err = store:add(x509_or_crl)
将 X.509 或 CRL 对象添加到存储中。参数必须是 resty.openssl.x509 实例或 resty.openssl.x509.store 实例。
store:load_file
语法: ok, err = store:load_file(path)
将文件系统上的 X.509 证书加载到存储中。
store:load_directory
语法: ok, err = store:load_directory(path)
将文件系统上的 X.509 证书目录加载到存储中。目录中的证书必须以散列形式存在,如 X509_LOOKUP_hash_dir(3) 中所述。
store:verify
语法: chain, err = store:verify(x509, chain?, return_chain?)
使用存储验证 X.509 对象。第一个参数必须是 resty.openssl.x509 实例。可以选择接受验证链作为第二个参数,该参数必须是 resty.openssl.x509.chain 实例。
如果验证成功,并且 return_chain
设置为 true,则返回验证证明作为 resty.openssl.x509.chain;否则仅返回 true
。如果验证失败,则返回 nil
和解释原因的错误。
堆栈式对象函数
元方法
语法: for k, obj in pairs(x)
语法: for k, obj in ipairs(x)
语法: len = #x
语法: obj = x[i]
访问底层对象,因为它是一个 Lua 表。确保您的 LuaJIT 使用 -DLUAJIT_ENABLE_LUA52COMPAT
标志编译。
每个对象可能只支持 pairs
或 ipairs
之一。索引从 1 开始。
local name, err = require("resty.openssl.x509.name").new()
local _, err = name:add("CN", "example.com")
for k, obj in pairs(name) do
ngx.say(k, ":", require("cjson").encode(obj))
end
-- outputs 'CN: {"sn":"CN","id":"2.5.4.3","nid":13,"blob":"3.example.com","ln":"commonName"}'
each
语法: iter = x:each()
返回用于遍历对象的迭代器。在未启用 LUAJIT_ENABLE_LUA52COMPAT
时使用此方法。
local name, err = require("resty.openssl.x509.name").new()
local _, err = name:add("CN", "example.com")
local iter = name:each()
while true do
local k, obj = iter()
if not k then
break
end
end
all
语法: objs, err = x:all()
返回表中的所有对象。在未启用 LUAJIT_ENABLE_LUA52COMPAT
时使用此方法。
count
语法: len = x:count()
返回表的对象数量。在未启用 LUAJIT_ENABLE_LUA52COMPAT
时使用此方法。
index
语法: obj = x:index(i)
返回表中 i
索引处的对象,索引从 1 开始。如果索引超出范围,则返回 nil
。
关于垃圾回收的一般规则
创建者设置了 GC 处理程序;用户不得释放它。
对于堆栈
如果它是通过
new()
创建的:将 GC 处理程序设置为 sk_TYPE_pop_free添加到堆栈的元素的 gc 处理程序不应设置。相反,依靠堆栈的 gc 处理程序来释放每个单独的元素。
如果它是通过
dup()
(浅层复制)创建的如果元素支持引用计数器(如 X509):将所有元素的引用计数增加 1;将 GC 处理程序设置为 sk_TYPE_pop_free。
如果不是,将 GC 处理程序设置为 sk_free
此外,堆栈在将元素添加到堆栈时会复制该元素,必须为副本设置 GC 处理程序。但是,应该在 Lua 中保留一个引用以防止个别元素过早地被 GC 收集。(参见 x509.altname)
兼容性
虽然只测试了 CPU 架构和 OpenSSL 版本的一小部分组合,但只要链接的 OpenSSL 库与 API 兼容,该库应该可以正常工作。这意味着相同名称的函数以相同的参数类型导出。
但是,对于 OpenSSL 1.0.2 系列,必须确保二进制/ABI 兼容性,因为某些结构成员是直接访问的。它们是通过汇编中的内存偏移量访问的。
OpenSSL 保持与次要版本或字母版本之间的 ABI/二进制兼容性。因此,所有结构偏移量和宏常量保持一致。
如果您计划在未经测试的 OpenSSL 版本(例如自定义版本或预发布版本)上使用此库,这里可能是一个很好的参考资料。
版权和许可
此模块在 BSD 许可下发布。
版权所有 (C) 2019-2020,作者 fffonion <fffonion@gmail.com>。
保留所有权利。
在满足以下条件的情况下,允许以源代码和二进制形式重新分发和使用此软件,无论是否修改。
源代码的重新分发必须保留上述版权声明、此条件列表和以下免责声明。
二进制形式的重新分发必须在随分发提供的文档和/或其他材料中复制上述版权声明、此条件列表和以下免责声明。
本软件由版权持有人和贡献者“按现状”提供,任何明示或暗示的保证,包括但不限于适销性保证和特定目的适用性保证均被排除在外。在任何情况下,版权持有人或贡献者均不对因使用本软件而产生的任何直接、间接、附带、特殊、惩罚性或后果性损害(包括但不限于替代商品或服务的采购;使用、数据或利润损失;或业务中断)负责,无论任何责任理论如何,无论是基于合同、严格责任或侵权行为(包括疏忽或其他原因),即使已告知有发生此类损害的可能性。
另请参见
[NID]: https://github.com/openssl/openssl/blob/master/include/openssl/obj_mac.h
作者
xiaocang
许可证
3bsd
依赖项
luajit
版本
-
适用于 LuaJIT 的基于 FFI 的 OpenSSL 绑定 2020-02-17 12:19:15
-
适用于 LuaJIT 的基于 FFI 的 OpenSSL 绑定 2020-02-17 12:12:43
-
适用于 LuaJIT 的基于 FFI 的 OpenSSL 绑定 2020-02-17 11:31:14
-
适用于 LuaJIT 的基于 FFI 的 OpenSSL 绑定 2020-02-17 11:28:26
-
适用于 LuaJIT 的基于 FFI 的 OpenSSL 绑定 2020-02-17 11:19:47
-
适用于 LuaJIT 的基于 FFI 的 OpenSSL 绑定 2020-02-17 11:14:50
-
适用于 LuaJIT 的基于 FFI 的 OpenSSL 绑定 2020-02-17 10:57:36
-
适用于 LuaJIT 的基于 FFI 的 OpenSSL 绑定 2020-02-17 10:53:43
-
适用于 LuaJIT 的基于 FFI 的 OpenSSL 绑定 2020-02-17 10:49:08
-
适用于 LuaJIT 的基于 FFI 的 OpenSSL 绑定 2020-02-17 10:45:17
-
适用于 LuaJIT 的基于 FFI 的 OpenSSL 绑定 2020-02-17 10:35:03
-
适用于 LuaJIT 的基于 FFI 的 OpenSSL 绑定 2020-02-17 10:19:31
-
2020-02-17 10:07:51
-
适用于 LuaJIT 的基于 FFI 的 OpenSSL 绑定 2020-02-17 10:04:48
-
适用于 LuaJIT 的基于 FFI 的 OpenSSL 绑定 2020-02-17 10:02:19
-
适用于 LuaJIT 的基于 FFI 的 OpenSSL 绑定 2020-02-17 10:01:05
-
适用于 LuaJIT 的基于 FFI 的 OpenSSL 绑定 2020-02-17 09:59:55
-
适用于 LuaJIT 的基于 FFI 的 OpenSSL 绑定 2020-02-17 09:49:01
-
适用于 LuaJIT 的基于 FFI 的 OpenSSL 绑定 2020-02-17 09:42:42
-
适用于 LuaJIT 的基于 FFI 的 OpenSSL 绑定 2020-02-17 09:39:18
-
适用于 LuaJIT 的基于 FFI 的 OpenSSL 绑定 2020-02-17 09:34:14
-
适用于 LuaJIT 的基于 FFI 的 OpenSSL 绑定 2020-02-17 09:21:00
-
2020-02-17 08:40:29
-
2020-02-17 08:34:53
-
2020-02-17 08:32:19
-
2020-02-17 08:30:43
-
2020-02-17 08:25:44
-
2020-02-17 08:22:47
-
2020-02-17 08:21:54
-
2020-02-17 08:03:39
-
2020-02-17 07:55:15
-
2020-02-17 07:50:41
-
2020-02-17 07:49:07
-
2020-02-17 06:57:03
-
2020-02-17 06:55:29
-
2020-02-17 05:23:34
-
2020-02-17 05:01:53