"Fossies" - the Fresh Open Source Software Archive 
Member "jitsi-meet-7315/resources/prosody-plugins/luajwtjitsi.lib.lua" (2 Jun 2023, 8278 Bytes) of package /linux/misc/jitsi-meet-7315.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Lua source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
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
7 -- Generates an RSA signature of the data.
8 -- @param data The data to be signed.
9 -- @param key The private signing key in PEM format.
10 -- @param algo The digest algorithm to user when generating the signature: sha256, sha384, or sha512.
11 -- @return The signature or nil and an error message.
12 local function signRS (data, key, algo)
13 local privkey = pkey.new(key)
14 if privkey == nil then
15 return nil, 'Not a private PEM key'
16 else
17 local datadigest = digest.new(algo):update(data)
18 return privkey:sign(datadigest)
19 end
20 end
21
22 -- Verifies an RSA signature on the data.
23 -- @param data The signed data.
24 -- @param signature The signature to be verified.
25 -- @param key The public key of the signer.
26 -- @param algo The digest algorithm to user when generating the signature: sha256, sha384, or sha512.
27 -- @return True if the signature is valid, false otherwise. Also returns false if the key is invalid.
28 local function verifyRS (data, signature, key, algo)
29 local pubkey = pkey.new(key)
30 if pubkey == nil then
31 return false
32 end
33
34 local datadigest = digest.new(algo):update(data)
35 return pubkey:verify(signature, datadigest)
36 end
37
38 local alg_sign = {
39 ['HS256'] = function(data, key) return hmac.new(key, 'sha256'):final(data) end,
40 ['HS384'] = function(data, key) return hmac.new(key, 'sha384'):final(data) end,
41 ['HS512'] = function(data, key) return hmac.new(key, 'sha512'):final(data) end,
42 ['RS256'] = function(data, key) return signRS(data, key, 'sha256') end,
43 ['RS384'] = function(data, key) return signRS(data, key, 'sha384') end,
44 ['RS512'] = function(data, key) return signRS(data, key, 'sha512') end
45 }
46
47 local alg_verify = {
48 ['HS256'] = function(data, signature, key) return signature == alg_sign['HS256'](data, key) end,
49 ['HS384'] = function(data, signature, key) return signature == alg_sign['HS384'](data, key) end,
50 ['HS512'] = function(data, signature, key) return signature == alg_sign['HS512'](data, key) end,
51 ['RS256'] = function(data, signature, key) return verifyRS(data, signature, key, 'sha256') end,
52 ['RS384'] = function(data, signature, key) return verifyRS(data, signature, key, 'sha384') end,
53 ['RS512'] = function(data, signature, key) return verifyRS(data, signature, key, 'sha512') end
54 }
55
56 -- Splits a token into segments, separated by '.'.
57 -- @param token The full token to be split.
58 -- @return A table of segments.
59 local function split_token(token)
60 local segments={}
61 for str in string.gmatch(token, "([^\\.]+)") do
62 table.insert(segments, str)
63 end
64 return segments
65 end
66
67 -- Parses a JWT token into it's header, body, and signature.
68 -- @param token The JWT token to be parsed.
69 -- @return A JSON header and body represented as a table, and a signature.
70 local function parse_token(token)
71 local segments=split_token(token)
72 if #segments ~= 3 then
73 return nil, nil, nil, "Invalid token"
74 end
75
76 local header, err = cjson_safe.decode(basexx.from_url64(segments[1]))
77 if err then
78 return nil, nil, nil, "Invalid header"
79 end
80
81 local body, err = cjson_safe.decode(basexx.from_url64(segments[2]))
82 if err then
83 return nil, nil, nil, "Invalid body"
84 end
85
86 local sig, err = basexx.from_url64(segments[3])
87 if err then
88 return nil, nil, nil, "Invalid signature"
89 end
90
91 return header, body, sig
92 end
93
94 -- Removes the signature from a JWT token.
95 -- @param token A JWT token.
96 -- @return The token without its signature.
97 local function strip_signature(token)
98 local segments=split_token(token)
99 if #segments ~= 3 then
100 return nil, nil, nil, "Invalid token"
101 end
102
103 table.remove(segments)
104 return table.concat(segments, ".")
105 end
106
107 -- Verifies that a claim is in a list of allowed claims. Allowed claims can be exact values, or the
108 -- catch all wildcard '*'.
109 -- @param claim The claim to be verified.
110 -- @param acceptedClaims A table of accepted claims.
111 -- @return True if the claim was allowed, false otherwise.
112 local function verify_claim(claim, acceptedClaims)
113 for i, accepted in ipairs(acceptedClaims) do
114 if accepted == '*' then
115 return true;
116 end
117 if claim == accepted then
118 return true;
119 end
120 end
121
122 return false;
123 end
124
125 local M = {}
126
127 -- Encodes the data into a signed JWT token.
128 -- @param data The data the put in the body of the JWT token.
129 -- @param key The key to use for signing the JWT token.
130 -- @param alg The signature algorithm to use: HS256, HS384, HS512, RS256, RS384, or RS512.
131 -- @param header Additional values to put in the JWT header.
132 -- @param The resulting JWT token, or nil and an error message.
133 function M.encode(data, key, alg, header)
134 if type(data) ~= 'table' then return nil, "Argument #1 must be table" end
135 if type(key) ~= 'string' then return nil, "Argument #2 must be string" end
136
137 alg = alg or "HS256"
138
139 if not alg_sign[alg] then
140 return nil, "Algorithm not supported"
141 end
142
143 header = header or {}
144
145 header['typ'] = 'JWT'
146 header['alg'] = alg
147
148 local headerEncoded, err = cjson_safe.encode(header)
149 if headerEncoded == nil then
150 return nil, err
151 end
152
153 local dataEncoded, err = cjson_safe.encode(data)
154 if dataEncoded == nil then
155 return nil, err
156 end
157
158 local segments = {
159 basexx.to_url64(headerEncoded),
160 basexx.to_url64(dataEncoded)
161 }
162
163 local signing_input = table.concat(segments, ".")
164 local signature, error = alg_sign[alg](signing_input, key)
165 if signature == nil then
166 return nil, error
167 end
168
169 segments[#segments+1] = basexx.to_url64(signature)
170
171 return table.concat(segments, ".")
172 end
173
174 -- Verify that the token is valid, and if it is return the decoded JSON payload data.
175 -- @param token The token to verify.
176 -- @param expectedAlgo The signature algorithm the caller expects the token to be signed with:
177 -- HS256, HS384, HS512, RS256, RS384, or RS512.
178 -- @param key The verification key used for the signature.
179 -- @param acceptedIssuers Optional table of accepted issuers. If not nil, the 'iss' claim will be
180 -- checked against this list.
181 -- @param acceptedAudiences Optional table of accepted audiences. If not nil, the 'aud' claim will
182 -- be checked against this list.
183 -- @return A table representing the JSON body of the token, or nil and an error message.
184 function M.verify(token, expectedAlgo, key, acceptedIssuers, acceptedAudiences)
185 if type(token) ~= 'string' then return nil, "token argument must be string" end
186 if type(expectedAlgo) ~= 'string' then return nil, "algorithm argument must be string" end
187 if type(key) ~= 'string' then return nil, "key argument must be string" end
188 if acceptedIssuers ~= nil and type(acceptedIssuers) ~= 'table' then
189 return nil, "acceptedIssuers argument must be table"
190 end
191 if acceptedAudiences ~= nil and type(acceptedAudiences) ~= 'table' then
192 return nil, "acceptedAudiences argument must be table"
193 end
194
195 if not alg_verify[expectedAlgo] then
196 return nil, "Algorithm not supported"
197 end
198
199 local header, body, sig, err = parse_token(token)
200 if err ~= nil then
201 return nil, err
202 end
203
204 -- Validate header
205 if not header.typ or header.typ ~= "JWT" then
206 return nil, "Invalid typ"
207 end
208
209 if not header.alg or header.alg ~= expectedAlgo then
210 return nil, "Invalid or incorrect alg"
211 end
212
213 -- Validate signature
214 if not alg_verify[expectedAlgo](strip_signature(token), sig, key) then
215 return nil, 'Invalid signature'
216 end
217
218 -- Validate body
219 if body.exp and type(body.exp) ~= "number" then
220 return nil, "exp must be number"
221 end
222
223 if body.nbf and type(body.nbf) ~= "number" then
224 return nil, "nbf must be number"
225 end
226
227
228 if body.exp and os.time() >= body.exp then
229 return nil, "Not acceptable by exp"
230 end
231
232 if body.nbf and os.time() < body.nbf then
233 return nil, "Not acceptable by nbf"
234 end
235
236 if acceptedIssuers ~= nil then
237 local issClaim = body.iss;
238 if issClaim == nil then
239 return nil, "'iss' claim is missing";
240 end
241 if not verify_claim(issClaim, acceptedIssuers) then
242 return nil, "invalid 'iss' claim";
243 end
244 end
245
246 if acceptedAudiences ~= nil then
247 local audClaim = body.aud;
248 if audClaim == nil then
249 return nil, "'aud' claim is missing";
250 end
251 if not verify_claim(audClaim, acceptedAudiences) then
252 return nil, "invalid 'aud' claim";
253 end
254 end
255
256 return body
257 end
258
259 return M