--- -- Common utilities for session library and storage backends -- -- @module resty.session.utils local require = require local buffer = require "string.buffer" local bit = require "bit" local select = select local floor = math.floor local ceil = math.ceil local byte = string.byte local char = string.char local band = bit.band local bnot = bit.bnot local bor = bit.bor local fmt = string.format local sub = string.sub local is_fips_mode do local IS_FIPS local function is_fips_mode_real() return IS_FIPS == true end --- -- Returns whether OpenSSL is in FIPS-mode. -- -- @function utils.is_fips_mode -- @treturn boolean `true` if OpenSSL is in FIPS-mode, otherwise `false` -- -- @usage -- local is_fips = require "resty.session.utils".is_fips_mode() is_fips_mode = function() IS_FIPS = require("resty.openssl").get_fips_mode() is_fips_mode = is_fips_mode_real return is_fips_mode() end end local bpack, bunpack do local buf = buffer.new() --- -- Binary pack unsigned integer. -- -- Returns binary packed version of an integer in little endian unsigned format. -- -- Size can be: -- -- * `1`, pack input as a little endian unsigned char (` 0 then msg = fmt(msg, ...) end if err then return fmt("%s (%s)", msg, err) end return msg end --- -- Helper to create a delimited key. -- -- @function utils.get_name -- @tparam table storage a storage implementation -- @tparam string name name -- @tparam string key key -- @tparam string subject subject -- @treturn string formatted and delimited name local function get_name(storage, name, key, subject) local prefix = storage.prefix local suffix = storage.suffix if prefix and suffix and subject then return fmt("%s:%s:%s:%s:%s", prefix, name, key, subject, suffix) elseif prefix and suffix then return fmt("%s:%s:%s:%s", prefix, name, key, suffix) elseif prefix and subject then return fmt("%s:%s:%s:%s", prefix, name, key, subject) elseif suffix and subject then return fmt("%s:%s:%s:%s", name, key, subject, suffix) elseif prefix then return fmt("%s:%s:%s", prefix, name, key) elseif suffix then return fmt("%s:%s:%s", name, key, suffix) elseif subject then return fmt("%s:%s:%s", name, key, subject) else return fmt("%s:%s", name, key) end end --- -- Helper to turn on a flag. -- -- @function utils.set_flag -- @tparam number flags flags on which the flag is applied -- @tparam number flag flag that is applied -- @treturn number flags with the flag applied -- -- @usage -- local flags = 0x0000 -- local FLAG_DOG = 0x001 -- flags = utils.set_flag(flags, FLAG_DOG) local function set_flag(options, flag) return bor(options, flag) end --- -- Helper to turn off a flag. -- -- @function utils.unset_flag -- @tparam number flags flags on which the flag is removed -- @tparam number flag flag that is removed -- @treturn number flags with the flag removed -- -- @usage -- local options = 0x0000 -- local FLAG_DOG = 0x001 -- flags = utils.set_flag(options, FLAG_DOG) -- flags = utils.unset_flag(options, FLAG_DOG) local function unset_flag(flags, flag) return band(flags, bnot(flag)) end --- -- Helper to check if flag is enabled. -- -- @function utils.has_flag -- @tparam number flags flags on which the flag is checked -- @tparam number flag flag that is checked -- @treturn boolean true if flag has is present, otherwise false -- -- @usage -- local flags = 0x0000 -- local FLAG_DOG = 0x001 -- local FLAG_BONE = 0x010 -- flags = utils.set_flag(flags, FLAG_DOG) -- flags = utils.set_flag(flags, FLAG_BONE) -- print(utils.has_flag(flags, FLAG_BONE) local function has_flag(flags, flag) return band(flags, flag) ~= 0 end --- -- Helper to get the value used to store metadata for a certain aud and sub -- Empty exp means the session id has been invalidated -- -- @function utils.meta_get_value -- @tparam string key storage key -- @tparam string exp expiration -- @treturn string the value to store in the metadata collection local function meta_get_value(key, exp) if not exp or exp == 0 then return fmt("%s;", key) end return fmt("%s:%s;", key, encode_base64url(bpack(5, exp))) end local meta_get_next do local KEY_OFFSET = 43 local DEL_OFFSET = 1 local EXP_OFFSET = 7 local COLON = byte(":") --- -- Function to extract the next key and exp from a serialized -- metadata list, starting from index -- -- @function utils.meta_get_next -- @tparam string val list of key:exp; -- @tparam number index start index -- @treturn key string session id -- @treturn err string error -- @treturn exp number expiration -- @treturn index number|nil index of the cursor meta_get_next = function(val, index) local key = sub(val, index, index + KEY_OFFSET - 1) index = index + KEY_OFFSET local del = byte(val, index + DEL_OFFSET - 1) index = index + DEL_OFFSET if del ~= COLON then return key, nil, nil, index end local exp = sub(val, index, index + EXP_OFFSET - 1) index = index + EXP_OFFSET + DEL_OFFSET local exp, err = decode_base64url(exp) if err then return nil, err end exp = bunpack(5, exp) return key, nil, exp, index end end --- -- Function to filter out the latest valid key:exp from a -- serialized list, used to store session metadata -- -- @function utils.meta_get_latest -- @tparam string sessions list of key:exp; -- @treturn table valid sessions and their exp local function meta_get_latest(sessions, current_time) current_time = current_time local latest = {} local index = 1 local length = #sessions while index < length do local key, err, exp key, err, exp, index = meta_get_next(sessions, index) if err then return nil, err end if exp and exp > current_time then latest[key] = exp else latest[key] = nil end end return latest end return { is_fips_mode = is_fips_mode, bpack = bpack, bunpack = bunpack, trim = trim, encode_json = encode_json, decode_json = decode_json, encode_base64url = encode_base64url, decode_base64url = decode_base64url, base64_size = base64_size, inflate = inflate, deflate = deflate, rand_bytes = rand_bytes, sha256 = sha256, derive_hkdf_sha256 = derive_hkdf_sha256, derive_pbkdf2_hmac_sha256 = derive_pbkdf2_hmac_sha256, derive_aes_gcm_256_key_and_iv = derive_aes_gcm_256_key_and_iv, derive_hmac_sha256_key = derive_hmac_sha256_key, encrypt_aes_256_gcm = encrypt_aes_256_gcm, decrypt_aes_256_gcm = decrypt_aes_256_gcm, hmac_sha256 = hmac_sha256, load_storage = load_storage, errmsg = errmsg, get_name = get_name, set_flag = set_flag, unset_flag = unset_flag, has_flag = has_flag, meta_get_value = meta_get_value, meta_get_next = meta_get_next, meta_get_latest = meta_get_latest, }