Lua openssl 实现 EC JWT JWK ES256

1 篇文章 0 订阅

ES256和ECDSA-SHA256签名的区别在于signature的格式不同,ES256只包含R/S,而ECDSA-SHA256是包含了R/S的ASN.1格式的签名,二者可以互相转换,前面一章的Java中,使用了SunEC的ECDSAUtil来完成的。但是Lua中很难能找到对应的工具去解析。本章使用lua-openssl及自定义的es_asn来实现签名格式转换。

lua-openssl可以使用luarocks安装,还请注意兼容的lua版本,本文采用的lua 5.3.5版本,兼容lua-openssl-0.8.5-1。

lua-openssl Github地址:https://github.com/zhaozg/lua-openssl

lua-openssl Luarocks地址:https://luarocks.org/modules/zhaozg/openssl

注意:lua-openssl不同版本所依赖的lua版本是不一样的,需下载兼容的版本,如果lua版本过于超前,需要降低lua版本。

首先给出一个Lua的ES256签名格式转换工具

local ec_asn = {}
ec_asn.__index = ec_asn

function ec_asn.convert_to_concat(signature)
    if type(signature) ~= "string" then
        error("The signature #1 type error! ", 2)
    elseif #signature < 8 or string.byte(signature, 1) ~= 0x30 then
        error("Invalid ASN.1 format of ECDSA signature", 2)
    end

    local offset = string.byte(signature, 2) == 0x81 and 4 or 3
    local rLength = string.byte(signature, offset + 1)
    local i = rLength
    while i > 0 and string.byte(signature, offset + 2 + rLength - i) == 0 do
        i = i - 1
    end

    local sLength = string.byte(signature, offset + 2 + rLength + 1)

    local j = sLength
    while j > 0 and string.byte(signature, offset + 2 + rLength + 2 + sLength - j) == 0 do
        j = j - 1
    end
    local rawLen = math.max(i, j)

    local seq = {}
    seq[1] = string.rep("\x00", rawLen - i) .. string.sub(signature, offset + 2 + rLength - i, offset + 2 + rLength - 1)
    seq[2] =
        string.rep("\x00", rawLen - j) ..
        string.sub(signature, offset + 2 + rLength + 2 + sLength - j, offset + 2 + rLength + 2 + sLength - 1)
    return seq
end

function ec_asn.convert_to_asn(esSignature)
    if type(esSignature) ~= "string" then
        error("The signature #1 must be string", 2)
    end
    local rawLen = math.floor(#esSignature / 2)
    local i = rawLen
    while i > 0 and string.byte(esSignature, rawLen - i + 1) == 0 do
        i = i - 1
    end
    local j = string.byte(esSignature, rawLen - i + 1) > 0x80 and i + 1 or i
    local k = rawLen
    while k > 0 and string.byte(esSignature, 2 * rawLen - k + 1) == 0 do
        k = k - 1
    end

    local l = string.byte(esSignature, 2 * rawLen - k + 1) > 0x80 and k + 1 or k

    local len = 2 + j + 2 + l
    if len > 255 then
        error("Invalid ES format of ECDSA signature", 2)
    end

    local signature =
        "\x30" ..
        (len > 128 and "\x81" or "") ..
            string.char(len) ..
                "\x02" ..
                    string.char(j) ..
                        string.rep("\x00", j - i) ..
                            string.sub(esSignature, rawLen - i + 1, rawLen) ..
                                "\x02" ..
                                    string.char(l) ..
                                        string.rep("\x00", l - k) ..
                                            string.sub(esSignature, 2 * rawLen - k + 1, 2 * rawLen)
    return signature
end

return ec_asn

顺便给出openssl的代码示例ecc_sign:

local cjson = require 'cjson'
local openssl = require 'openssl'
local uuid = require "uuid"
local base64_url = require 'util/base64_url'
local pkey = openssl.pkey
local now = os.time();
uuid.randomseed(now)

local ec_asn = require 'util/ec_asn'

local header = {
    alg = 'ES256',
    kid = 'b1e0953c-827a-448d-ae73-e920e0c6c735'
}
local data = {
    iss = "http://junyee.org",
    typ = "TOKEN",
    aud = "junyee.org",
    exp = now+360*24*3600,
    jti = uuid(),
    iat = now
  }

local segments = {
    base64_url.encode(cjson.encode(header)),
    base64_url.encode(cjson.encode(data))
}
local content = table.concat(segments, ".")

local factor = {
    alg = "ec",
    -- NID 415 is prime256v1
    ec_name = 415,
    d = assert(base64_url.decode('ALds0XN06VCrIaxG-WMiosWWgD7DrocR4SCH5ckC-901')),
    x = assert(base64_url.decode('Ek7imKhgJDJUnKTEq0n8-Mi16H-qVfSesbQj2C4_bf8')),
    y = assert(base64_url.decode('Bww57PhQpcOOunOIG595yJ-MV55xewZC2nLYwFotEmA'))
  }

  -- Generate an ecc private key
  local pk = pkey.new(factor)
  local signsource = pk:sign(content,"SHA256")

  local derSequence2 = ec_asn.convert_to_concat(signsource)
  
  segments[#segments+1] = base64_url.encode(derSequence2[1] .. derSequence2[2])
  print(table.concat(segments, "."))

ECC的verify

local openssl = require 'openssl'
local ec_asn = require 'util/ec_asn'

local base64_url = require 'util/base64_url'
local pkey = openssl.pkey
local sub = string.sub

local function verify()
  local factor = {
    alg = "ec",
    -- NID 415 is prime256v1 of openssl
    ec_name = 415,
    x = assert(base64_url.decode('Ek7imKhgJDJUnKTEq0n8-Mi16H-qVfSesbQj2C4_bf8')),
    y = assert(base64_url.decode('Bww57PhQpcOOunOIG595yJ-MV55xewZC2nLYwFotEmA'))
  }
  local ec = pkey.new(factor)
  -- local pem = assert(ec:export('pem'))
  -- print(pem)
  local content = 'eyJhbGciOiJFUzI1NiIsImtpZCI6ImIxZTA5NTNjLTgyN2EtNDQ4ZC1hZTczLWU5MjBlMGM2YzczNSJ9.eyJ0eXAiOiJUT0tFTiIsImF1ZCI6Imp1bnllZS5vcmciLCJleHAiOjE2NjUxMjM4MDQsImp0aSI6ImE1YTJjMWU0LTg1NGMtNGM3ZC05MTg4LTc4MTk5NjA2YTYzOSIsImlhdCI6MTYzNDAxOTgwNCwiaXNzIjoiaHR0cDpcL1wvanVueWVlLm9yZyJ9'
  local signature = base64_url.decode('i32GXFY3-DU1C5y4TkrxONb7cKMYUNCJ34Y_BT6fnSuTluLsfZ8ROS4xGmQ1X78_yzRUY0VoSOiue6qyc3w8RA')
  assert(#signature == 64, "Signature must be 64 bytes.")
  local signatureAsn2 = ec_asn.convert_to_asn(signature)
  print('Verify Signature2:',ec:verify(content, signatureAsn2, 'SHA256'))
end
verify()

补充一套JWKS的生成代码

local openssl = require 'openssl'
local uuid = require "uuid"
local base64_url = require 'util/base64_url'
uuid.randomseed(os.time())
local pkey = openssl.pkey
local ec_factory = {'ec',  'prime256v1'};
local jwks_option = {enable_private=false,jwks_length=3,raw=true}
local es256_jwks = {}

function set_option(opt)
    opt = opt or {}
    assert(not opt.jwks_length or opt.jwks_length>0,"opt.jwks_length must gt 0")
    local option = {}
    option.enable_private = opt.enable_private or jwks_option.enable_private
    option.jwks_length = opt.jwks_length or jwks_option.jwks_length
    option.raw = opt.raw==nil and jwks_option.raw or opt.raw
    return option
end

function es256_jwks:new(opt)
    local _instance = {}
    setmetatable(_instance, self)
    self.__index = self
    _instance.option = set_option(opt)
    return _instance
end

function es256_jwks:to_jwks()
    local keys = {} 
    local metadata = {}
    for i = 1,self.option.jwks_length do 
        local kid = uuid()
        local pk = assert(pkey.new(table.unpack(ec_factory)))
        local pubkey = pk:get_public()
       
        local t = pk:parse()
        local ec = t.ec
        t = ec:parse('pem')
        metadata[kid] = {
            public_key = pubkey:export("pem", self.option.raw),
            private_key = pk:export("pem", self.option.raw),
            d = base64_url.encode(t.d:totext()),
            x = base64_url.encode(t.x:totext()),
            y = base64_url.encode(t.y:totext())
        }
        local x,y = metadata[kid].x,metadata[kid].y
        local d = self.option.enable_private and '"d":"' .. metadata[kid].d ..'",' or ''
        keys[i] = '{"kty":"EC","use":"sig","crv":"P-256","kid":"'.. kid ..'",'.. d ..'"x":"'.. x ..'","y":"'.. y ..'"}'
    end
    return '{"keys":['.. table.concat(keys,',')..']}', metadata
end
return es256_jwks


Base64 url safe 工具

local openssl = require "openssl"
local _M = {}
_M.__index = _M

function  _M.encode(input)
    local encoder = openssl.base64(input,true)
    encoder = encoder:gsub("+", "-"):gsub("/", "_"):gsub("=","")
    return encoder
  end 
  
function _M.decode(input)
    local remainder = #input % 4
    if remainder > 0 then
      local padlen = 4 - remainder
      input = input .. string.rep("=", padlen)
    end
    input = input:gsub("-", "+"):gsub("_", "/")
    return openssl.base64(input,false)
  end

return _M

JWKS测试

local es256_jwks = require 'es256/es256_jwks'
local cjson = require 'cjson'

-- enbale_private : Is print private key named 'd' in jwks json string.
-- jwks_length : jwks keys length.
local jwks = es256_jwks:new({enable_private=false,jwks_length=3,raw=true})
local jwks_json,metadata = jwks:to_jwks()

-- jwks url json string
print(jwks_json)

-- jwks private key and public key
for i,v in pairs(metadata) do
    print('kid: ',i)
    print(cjson.encode(v))
end

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

车联网服务端架构

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值