浏览代码

feat: Moves luajwtjitsi in jitsi-meet. (#11501)

* feat: Moves luajwtjitsi in jitsi-meet.

* squash: Fix luajwtjitsi name to include lib.
master
Дамян Минков 3 年前
父节点
当前提交
1400b6ff0a
没有帐户链接到提交者的电子邮件

+ 1
- 1
debian/control 查看文件

@@ -47,7 +47,7 @@ Description: Prosody configuration for Jitsi Meet
47 47
 
48 48
 Package: jitsi-meet-tokens
49 49
 Architecture: all
50
-Depends: ${misc:Depends}, prosody-trunk | prosody-0.11 | prosody-0.12 | prosody (>= 0.11.2), libssl-dev, luarocks, jitsi-meet-prosody, git, lua-basexx
50
+Depends: ${misc:Depends}, prosody-trunk | prosody-0.11 | prosody-0.12 | prosody (>= 0.11.2), jitsi-meet-prosody, lua-basexx, lua-luaossl, lua-cjson
51 51
 Description: Prosody token authentication plugin for Jitsi Meet
52 52
 
53 53
 Package: jitsi-meet-turnserver

+ 0
- 5
debian/jitsi-meet-tokens.postinst 查看文件

@@ -48,11 +48,6 @@ case "$1" in
48 48
         db_stop
49 49
 
50 50
         if [ -f "$PROSODY_HOST_CONFIG" ] ; then
51
-            # Install luajwt (also on update, to make sure we get the latest version).
52
-            if ! luarocks install luajwtjitsi 3.0-0; then
53
-               echo "Failed to install luajwtjitsi - try installing it manually"
54
-            fi
55
-
56 51
             # search for the token auth, if this is not enabled this is the
57 52
             # first time we install tokens package and needs a config change
58 53
             if ! egrep -q '^\s*authentication\s*=\s*"token"' "$PROSODY_HOST_CONFIG"; then

+ 259
- 0
resources/prosody-plugins/luajwtjitsi.lib.lua 查看文件

@@ -0,0 +1,259 @@
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

+ 1
- 1
resources/prosody-plugins/token/util.lib.lua 查看文件

@@ -4,7 +4,7 @@
4 4
 local basexx = require "basexx";
5 5
 local have_async, async = pcall(require, "util.async");
6 6
 local hex = require "util.hex";
7
-local jwt = require "luajwtjitsi";
7
+local jwt = module:require "luajwtjitsi";
8 8
 local jid = require "util.jid";
9 9
 local json_safe = require "cjson.safe";
10 10
 local path = require "util.paths";

正在加载...
取消
保存