Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

luajwtjitsi.lib.lua 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. local cjson_safe = require 'cjson.safe'
  2. local basexx = require 'basexx'
  3. local digest = require 'openssl.digest'
  4. local hmac = require 'openssl.hmac'
  5. local pkey = require 'openssl.pkey'
  6. -- Generates an RSA signature of the data.
  7. -- @param data The data to be signed.
  8. -- @param key The private signing key in PEM format.
  9. -- @param algo The digest algorithm to user when generating the signature: sha256, sha384, or sha512.
  10. -- @return The signature or nil and an error message.
  11. local function signRS (data, key, algo)
  12. local privkey = pkey.new(key)
  13. if privkey == nil then
  14. return nil, 'Not a private PEM key'
  15. else
  16. local datadigest = digest.new(algo):update(data)
  17. return privkey:sign(datadigest)
  18. end
  19. end
  20. -- Verifies an RSA signature on the data.
  21. -- @param data The signed data.
  22. -- @param signature The signature to be verified.
  23. -- @param key The public key of the signer.
  24. -- @param algo The digest algorithm to user when generating the signature: sha256, sha384, or sha512.
  25. -- @return True if the signature is valid, false otherwise. Also returns false if the key is invalid.
  26. local function verifyRS (data, signature, key, algo)
  27. local pubkey = pkey.new(key)
  28. if pubkey == nil then
  29. return false
  30. end
  31. local datadigest = digest.new(algo):update(data)
  32. return pubkey:verify(signature, datadigest)
  33. end
  34. local alg_sign = {
  35. ['HS256'] = function(data, key) return hmac.new(key, 'sha256'):final(data) end,
  36. ['HS384'] = function(data, key) return hmac.new(key, 'sha384'):final(data) end,
  37. ['HS512'] = function(data, key) return hmac.new(key, 'sha512'):final(data) end,
  38. ['RS256'] = function(data, key) return signRS(data, key, 'sha256') end,
  39. ['RS384'] = function(data, key) return signRS(data, key, 'sha384') end,
  40. ['RS512'] = function(data, key) return signRS(data, key, 'sha512') end
  41. }
  42. local alg_verify = {
  43. ['HS256'] = function(data, signature, key) return signature == alg_sign['HS256'](data, key) end,
  44. ['HS384'] = function(data, signature, key) return signature == alg_sign['HS384'](data, key) end,
  45. ['HS512'] = function(data, signature, key) return signature == alg_sign['HS512'](data, key) end,
  46. ['RS256'] = function(data, signature, key) return verifyRS(data, signature, key, 'sha256') end,
  47. ['RS384'] = function(data, signature, key) return verifyRS(data, signature, key, 'sha384') end,
  48. ['RS512'] = function(data, signature, key) return verifyRS(data, signature, key, 'sha512') end
  49. }
  50. -- Splits a token into segments, separated by '.'.
  51. -- @param token The full token to be split.
  52. -- @return A table of segments.
  53. local function split_token(token)
  54. local segments={}
  55. for str in string.gmatch(token, "([^\\.]+)") do
  56. table.insert(segments, str)
  57. end
  58. return segments
  59. end
  60. -- Parses a JWT token into it's header, body, and signature.
  61. -- @param token The JWT token to be parsed.
  62. -- @return A JSON header and body represented as a table, and a signature.
  63. local function parse_token(token)
  64. local segments=split_token(token)
  65. if #segments ~= 3 then
  66. return nil, nil, nil, "Invalid token"
  67. end
  68. local header, err = cjson_safe.decode(basexx.from_url64(segments[1]))
  69. if err then
  70. return nil, nil, nil, "Invalid header"
  71. end
  72. local body, err = cjson_safe.decode(basexx.from_url64(segments[2]))
  73. if err then
  74. return nil, nil, nil, "Invalid body"
  75. end
  76. local sig, err = basexx.from_url64(segments[3])
  77. if err then
  78. return nil, nil, nil, "Invalid signature"
  79. end
  80. return header, body, sig
  81. end
  82. -- Removes the signature from a JWT token.
  83. -- @param token A JWT token.
  84. -- @return The token without its signature.
  85. local function strip_signature(token)
  86. local segments=split_token(token)
  87. if #segments ~= 3 then
  88. return nil, nil, nil, "Invalid token"
  89. end
  90. table.remove(segments)
  91. return table.concat(segments, ".")
  92. end
  93. -- Verifies that a claim is in a list of allowed claims. Allowed claims can be exact values, or the
  94. -- catch all wildcard '*'.
  95. -- @param claim The claim to be verified.
  96. -- @param acceptedClaims A table of accepted claims.
  97. -- @return True if the claim was allowed, false otherwise.
  98. local function verify_claim(claim, acceptedClaims)
  99. for i, accepted in ipairs(acceptedClaims) do
  100. if accepted == '*' then
  101. return true;
  102. end
  103. if claim == accepted then
  104. return true;
  105. end
  106. end
  107. return false;
  108. end
  109. local M = {}
  110. -- Encodes the data into a signed JWT token.
  111. -- @param data The data the put in the body of the JWT token.
  112. -- @param key The key to use for signing the JWT token.
  113. -- @param alg The signature algorithm to use: HS256, HS384, HS512, RS256, RS384, or RS512.
  114. -- @param header Additional values to put in the JWT header.
  115. -- @param The resulting JWT token, or nil and an error message.
  116. function M.encode(data, key, alg, header)
  117. if type(data) ~= 'table' then return nil, "Argument #1 must be table" end
  118. if type(key) ~= 'string' then return nil, "Argument #2 must be string" end
  119. alg = alg or "HS256"
  120. if not alg_sign[alg] then
  121. return nil, "Algorithm not supported"
  122. end
  123. header = header or {}
  124. header['typ'] = 'JWT'
  125. header['alg'] = alg
  126. local headerEncoded, err = cjson_safe.encode(header)
  127. if headerEncoded == nil then
  128. return nil, err
  129. end
  130. local dataEncoded, err = cjson_safe.encode(data)
  131. if dataEncoded == nil then
  132. return nil, err
  133. end
  134. local segments = {
  135. basexx.to_url64(headerEncoded),
  136. basexx.to_url64(dataEncoded)
  137. }
  138. local signing_input = table.concat(segments, ".")
  139. local signature, error = alg_sign[alg](signing_input, key)
  140. if signature == nil then
  141. return nil, error
  142. end
  143. segments[#segments+1] = basexx.to_url64(signature)
  144. return table.concat(segments, ".")
  145. end
  146. -- Verify that the token is valid, and if it is return the decoded JSON payload data.
  147. -- @param token The token to verify.
  148. -- @param expectedAlgo The signature algorithm the caller expects the token to be signed with:
  149. -- HS256, HS384, HS512, RS256, RS384, or RS512.
  150. -- @param key The verification key used for the signature.
  151. -- @param acceptedIssuers Optional table of accepted issuers. If not nil, the 'iss' claim will be
  152. -- checked against this list.
  153. -- @param acceptedAudiences Optional table of accepted audiences. If not nil, the 'aud' claim will
  154. -- be checked against this list.
  155. -- @return A table representing the JSON body of the token, or nil and an error message.
  156. function M.verify(token, expectedAlgo, key, acceptedIssuers, acceptedAudiences)
  157. if type(token) ~= 'string' then return nil, "token argument must be string" end
  158. if type(expectedAlgo) ~= 'string' then return nil, "algorithm argument must be string" end
  159. if type(key) ~= 'string' then return nil, "key argument must be string" end
  160. if acceptedIssuers ~= nil and type(acceptedIssuers) ~= 'table' then
  161. return nil, "acceptedIssuers argument must be table"
  162. end
  163. if acceptedAudiences ~= nil and type(acceptedAudiences) ~= 'table' then
  164. return nil, "acceptedAudiences argument must be table"
  165. end
  166. if not alg_verify[expectedAlgo] then
  167. return nil, "Algorithm not supported"
  168. end
  169. local header, body, sig, err = parse_token(token)
  170. if err ~= nil then
  171. return nil, err
  172. end
  173. -- Validate header
  174. if not header.typ or header.typ ~= "JWT" then
  175. return nil, "Invalid typ"
  176. end
  177. if not header.alg or header.alg ~= expectedAlgo then
  178. return nil, "Invalid or incorrect alg"
  179. end
  180. -- Validate signature
  181. if not alg_verify[expectedAlgo](strip_signature(token), sig, key) then
  182. return nil, 'Invalid signature'
  183. end
  184. -- Validate body
  185. if body.exp and type(body.exp) ~= "number" then
  186. return nil, "exp must be number"
  187. end
  188. if body.nbf and type(body.nbf) ~= "number" then
  189. return nil, "nbf must be number"
  190. end
  191. if body.exp and os.time() >= body.exp then
  192. return nil, "Not acceptable by exp"
  193. end
  194. if body.nbf and os.time() < body.nbf then
  195. return nil, "Not acceptable by nbf"
  196. end
  197. if acceptedIssuers ~= nil then
  198. local issClaim = body.iss;
  199. if issClaim == nil then
  200. return nil, "'iss' claim is missing";
  201. end
  202. if not verify_claim(issClaim, acceptedIssuers) then
  203. return nil, "invalid 'iss' claim";
  204. end
  205. end
  206. if acceptedAudiences ~= nil then
  207. local audClaim = body.aud;
  208. if audClaim == nil then
  209. return nil, "'aud' claim is missing";
  210. end
  211. if not verify_claim(audClaim, acceptedAudiences) then
  212. return nil, "invalid 'aud' claim";
  213. end
  214. end
  215. return body
  216. end
  217. return M