lua-resty-openssl

基于 FFI 的 LuaJIT OpenSSL 绑定

$ opm get xiaocang/lua-resty-openssl

lua-resty-openssl

基于 FFI 的 LuaJIT OpenSSL 绑定,支持 OpenSSL 1.1 和 1.0.2 系列

!构建状态 !luarocks

描述

lua-resty-openssl 是一个基于 FFI 的 OpenSSL 绑定库,目前支持 OpenSSL 1.1.11.1.01.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 之前的版本,仅支持 VERSIONCFLAGSBUILT_ONPLATFORMDIR

    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 实例。第一个参数可以是

  1. 一个 config 表,用于创建一个新的 PKEY 对。默认值为

    pkey.new({
      type = 'RSA',
      bits = 2048,
      exp = 65537
    })

创建 EC 私钥

    pkey.new({
      type = 'EC',
      curve = 'primve196v1',
    })
  1. 一个 string,包含 PEM 或 DER 格式的私钥或公钥;可以选择接受一个表 opts 来明确加载 format 和密钥 type。在加载 PEM 格式的密钥时,可以提供 passphrasepassphrase_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
    }
    
  1. nil,用于创建一个 2048 位的 RSA 密钥。

  2. 一个 EVP_PKEY* 指针,用于返回一个包装的 pkey 实例。通常用户不会使用这种方法。用户不应该自行释放指针,因为指针不会被复制。

pkey.istype

语法: ok = pkey.istype(table)

如果表是 pkey 的实例,则返回 true。否则返回 false

pkey:get_parameters

语法: parameters, err = pk:get_parameters()

返回一个包含 pkey 实例的 parameters 的表。目前仅支持 RSA 密钥的 ned 参数。返回表中的每个值都是一个 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,该实例必须加载了私钥。

可选的第二个参数 paddingpkey: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 实例的私钥或公钥。第一个参数必须是 publicPublicKeyprivatePrivateKey 或 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_paddingtrue,则 s 的长度必须是块大小的倍数,否则会发生错误。

此函数是 cipher:initcipher:final 的简写形式。

cipher:decrypt

语法: s, err = cipher:decrypt(key, iv?, s, no_padding?)

使用密钥 key 和 IV iv 解密文本 s。返回解密后的文本(以原始二进制字符串形式)和错误(如果有)。可以选择接受一个布尔值 no_padding,它指示密码是否启用或禁用填充,默认值为 false(启用填充)。如果 no_paddingtrue,则 s 的长度必须是块大小的倍数,否则会发生错误;此外,解密后的文本中的填充不会被删除。

此函数是 cipher:initcipher:final 的简写形式。

cipher:init

语法: ok, err = cipher:init(key, iv?, opts?)

使用密钥 key 和 IV iv 初始化密码。可选的第三个参数是一个表,包含

    {
        is_encrypt = false,
        no_padding = false,
    }

cipher:updatecipher:final 之前需要调用该函数,但 cipher:encryptcipher: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 可以是 PEMDER 格式的文本;fmt 是加载特定格式的 PEMDER 或自动检测的 * 之一。

当省略 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}_criticalset_{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_extensionx509:set_extension 访问特定扩展,而后面两个函数分别返回或需要 extension。如果需要修改当前扩展,用户可以使用此处列出的 getter 和 setter;如果用户要添加或替换整个扩展或未实现 getter/setter,请使用 x509:get_extensionx509:set_extension。如果 getter 返回 x509.* 实例类型,则可以通过 extension:from_data 将其转换为 extension 实例,然后由 x509:get_extensionx509: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 格式的二进制输出证书。第一个参数可以是 PEMDER;如果省略,则此函数默认输出 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 可以是 PEMDER 格式的文本;fmtPEMDER 用于加载特定格式,或 * 用于自动检测。

如果省略了 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 格式的二进制输出证书请求。第一个参数可以是 PEMDER;如果省略,则此函数默认输出 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 可以是 PEMDER 格式的文本;fmtPEMDER 用于加载特定格式,或 * 用于自动检测。

如果省略了 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}_criticalset_{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_extensioncrl:set_extension 访问特定扩展,而后面两个函数分别返回或需要 extension。如果需要修改当前扩展,用户可以使用此处列出的 getter 和 setter;如果用户要添加或替换整个扩展或未实现 getter/setter,请使用 crl:get_extensioncrl:set_extension。如果 getter 返回 crl.* 实例类型,则可以通过 extension:from_data 将其转换为 extension 实例,然后由 crl:get_extensioncrl: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。第一个参数可以是 PEMDER 的选择;如果省略,则此函数默认输出 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 标志编译;否则使用 alleachindexcount 代替。

另请参见 "堆栈式对象函数"

每个返回的对象都是一个表,其中

    {
      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 标志编译;否则使用 alleachindexcount 代替。

另请参见 "堆栈式对象函数"

resty.openssl.x509.extension

用于与 X.509 扩展交互的模块。

extension.new

语法: ext, err = extension.new(name, value, data)

创建一个新的 extension 实例。namevalue 是 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 标志编译;否则使用 alleachindexcount 代替。

另请参见 "堆栈式对象函数"

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 标志编译;否则使用 alleachindexcount 代替。

另请参见 "堆栈式对象函数"

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 标志编译;否则使用 alleachindexcount 代替。

另请参见 "堆栈式对象函数"

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 标志编译。

每个对象可能只支持 pairsipairs 之一。索引从 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

版本