]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/tests/api/activitypub/security.ts
Add new player string to custom translations
[github/Chocobozzz/PeerTube.git] / server / tests / api / activitypub / security.ts
1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3 import 'mocha'
4 import * as chai from 'chai'
5 import { buildDigest } from '@server/helpers/peertube-crypto'
6 import { HTTP_SIGNATURE } from '@server/initializers/constants'
7 import { activityPubContextify } from '@server/lib/activitypub/context'
8 import { buildGlobalHeaders, signAndContextify } from '@server/lib/activitypub/send'
9 import { makeFollowRequest, makePOSTAPRequest } from '@server/tests/shared'
10 import { buildAbsoluteFixturePath, wait } from '@shared/core-utils'
11 import { HttpStatusCode } from '@shared/models'
12 import { cleanupTests, createMultipleServers, killallServers, PeerTubeServer } from '@shared/server-commands'
13
14 const expect = chai.expect
15
16 function setKeysOfServer (onServer: PeerTubeServer, ofServer: PeerTubeServer, publicKey: string, privateKey: string) {
17 const url = 'http://localhost:' + ofServer.port + '/accounts/peertube'
18
19 return Promise.all([
20 onServer.sql.setActorField(url, 'publicKey', publicKey),
21 onServer.sql.setActorField(url, 'privateKey', privateKey)
22 ])
23 }
24
25 function setUpdatedAtOfServer (onServer: PeerTubeServer, ofServer: PeerTubeServer, updatedAt: string) {
26 const url = 'http://localhost:' + ofServer.port + '/accounts/peertube'
27
28 return Promise.all([
29 onServer.sql.setActorField(url, 'createdAt', updatedAt),
30 onServer.sql.setActorField(url, 'updatedAt', updatedAt)
31 ])
32 }
33
34 function getAnnounceWithoutContext (server: PeerTubeServer) {
35 const json = require(buildAbsoluteFixturePath('./ap-json/peertube/announce-without-context.json'))
36 const result: typeof json = {}
37
38 for (const key of Object.keys(json)) {
39 if (Array.isArray(json[key])) {
40 result[key] = json[key].map(v => v.replace(':9002', `:${server.port}`))
41 } else {
42 result[key] = json[key].replace(':9002', `:${server.port}`)
43 }
44 }
45
46 return result
47 }
48
49 describe('Test ActivityPub security', function () {
50 let servers: PeerTubeServer[]
51 let url: string
52
53 const keys = require(buildAbsoluteFixturePath('./ap-json/peertube/keys.json'))
54 const invalidKeys = require(buildAbsoluteFixturePath('./ap-json/peertube/invalid-keys.json'))
55 const baseHttpSignature = () => ({
56 algorithm: HTTP_SIGNATURE.ALGORITHM,
57 authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME,
58 keyId: 'acct:peertube@localhost:' + servers[1].port,
59 key: keys.privateKey,
60 headers: HTTP_SIGNATURE.HEADERS_TO_SIGN
61 })
62
63 // ---------------------------------------------------------------
64
65 before(async function () {
66 this.timeout(60000)
67
68 servers = await createMultipleServers(3)
69
70 url = servers[0].url + '/inbox'
71
72 await setKeysOfServer(servers[0], servers[1], keys.publicKey, null)
73 await setKeysOfServer(servers[1], servers[1], keys.publicKey, keys.privateKey)
74
75 const to = { url: 'http://localhost:' + servers[0].port + '/accounts/peertube' }
76 const by = { url: 'http://localhost:' + servers[1].port + '/accounts/peertube', privateKey: keys.privateKey }
77 await makeFollowRequest(to, by)
78 })
79
80 describe('When checking HTTP signature', function () {
81
82 it('Should fail with an invalid digest', async function () {
83 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
84 const headers = {
85 Digest: buildDigest({ hello: 'coucou' })
86 }
87
88 try {
89 await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
90 expect(true, 'Did not throw').to.be.false
91 } catch (err) {
92 expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
93 }
94 })
95
96 it('Should fail with an invalid date', async function () {
97 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
98 const headers = buildGlobalHeaders(body)
99 headers['date'] = 'Wed, 21 Oct 2015 07:28:00 GMT'
100
101 try {
102 await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
103 expect(true, 'Did not throw').to.be.false
104 } catch (err) {
105 expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
106 }
107 })
108
109 it('Should fail with bad keys', async function () {
110 await setKeysOfServer(servers[0], servers[1], invalidKeys.publicKey, invalidKeys.privateKey)
111 await setKeysOfServer(servers[1], servers[1], invalidKeys.publicKey, invalidKeys.privateKey)
112
113 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
114 const headers = buildGlobalHeaders(body)
115
116 try {
117 await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
118 expect(true, 'Did not throw').to.be.false
119 } catch (err) {
120 expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
121 }
122 })
123
124 it('Should reject requests without appropriate signed headers', async function () {
125 await setKeysOfServer(servers[0], servers[1], keys.publicKey, keys.privateKey)
126 await setKeysOfServer(servers[1], servers[1], keys.publicKey, keys.privateKey)
127
128 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
129 const headers = buildGlobalHeaders(body)
130
131 const signatureOptions = baseHttpSignature()
132 const badHeadersMatrix = [
133 [ '(request-target)', 'date', 'digest' ],
134 [ 'host', 'date', 'digest' ],
135 [ '(request-target)', 'host', 'digest' ]
136 ]
137
138 for (const badHeaders of badHeadersMatrix) {
139 signatureOptions.headers = badHeaders
140
141 try {
142 await makePOSTAPRequest(url, body, signatureOptions, headers)
143 expect(true, 'Did not throw').to.be.false
144 } catch (err) {
145 expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
146 }
147 }
148 })
149
150 it('Should succeed with a valid HTTP signature draft 11 (without date but with (created))', async function () {
151 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
152 const headers = buildGlobalHeaders(body)
153
154 const signatureOptions = baseHttpSignature()
155 signatureOptions.headers = [ '(request-target)', '(created)', 'host', 'digest' ]
156
157 const { statusCode } = await makePOSTAPRequest(url, body, signatureOptions, headers)
158 expect(statusCode).to.equal(HttpStatusCode.NO_CONTENT_204)
159 })
160
161 it('Should succeed with a valid HTTP signature', async function () {
162 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
163 const headers = buildGlobalHeaders(body)
164
165 const { statusCode } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
166 expect(statusCode).to.equal(HttpStatusCode.NO_CONTENT_204)
167 })
168
169 it('Should refresh the actor keys', async function () {
170 this.timeout(20000)
171
172 // Update keys of server 2 to invalid keys
173 // Server 1 should refresh the actor and fail
174 await setKeysOfServer(servers[1], servers[1], invalidKeys.publicKey, invalidKeys.privateKey)
175 await setUpdatedAtOfServer(servers[0], servers[1], '2015-07-17 22:00:00+00')
176
177 // Invalid peertube actor cache
178 await killallServers([ servers[1] ])
179 await servers[1].run()
180
181 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
182 const headers = buildGlobalHeaders(body)
183
184 try {
185 await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
186 expect(true, 'Did not throw').to.be.false
187 } catch (err) {
188 console.error(err)
189 expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
190 }
191 })
192 })
193
194 describe('When checking Linked Data Signature', function () {
195 before(async function () {
196 this.timeout(10000)
197
198 await setKeysOfServer(servers[0], servers[1], keys.publicKey, keys.privateKey)
199 await setKeysOfServer(servers[1], servers[1], keys.publicKey, keys.privateKey)
200 await setKeysOfServer(servers[2], servers[2], keys.publicKey, keys.privateKey)
201
202 const to = { url: 'http://localhost:' + servers[0].port + '/accounts/peertube' }
203 const by = { url: 'http://localhost:' + servers[2].port + '/accounts/peertube', privateKey: keys.privateKey }
204 await makeFollowRequest(to, by)
205 })
206
207 it('Should fail with bad keys', async function () {
208 this.timeout(10000)
209
210 await setKeysOfServer(servers[0], servers[2], invalidKeys.publicKey, invalidKeys.privateKey)
211 await setKeysOfServer(servers[2], servers[2], invalidKeys.publicKey, invalidKeys.privateKey)
212
213 const body = getAnnounceWithoutContext(servers[1])
214 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
215
216 const signer: any = { privateKey: invalidKeys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
217 const signedBody = await signAndContextify(signer, body, 'Announce')
218
219 const headers = buildGlobalHeaders(signedBody)
220
221 try {
222 await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
223 expect(true, 'Did not throw').to.be.false
224 } catch (err) {
225 expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
226 }
227 })
228
229 it('Should fail with an altered body', async function () {
230 this.timeout(10000)
231
232 await setKeysOfServer(servers[0], servers[2], keys.publicKey, keys.privateKey)
233 await setKeysOfServer(servers[0], servers[2], keys.publicKey, keys.privateKey)
234
235 const body = getAnnounceWithoutContext(servers[1])
236 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
237
238 const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
239 const signedBody = await signAndContextify(signer, body, 'Announce')
240
241 signedBody.actor = 'http://localhost:' + servers[2].port + '/account/peertube'
242
243 const headers = buildGlobalHeaders(signedBody)
244
245 try {
246 await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
247 expect(true, 'Did not throw').to.be.false
248 } catch (err) {
249 expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
250 }
251 })
252
253 it('Should succeed with a valid signature', async function () {
254 this.timeout(10000)
255
256 const body = getAnnounceWithoutContext(servers[1])
257 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
258
259 const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
260 const signedBody = await signAndContextify(signer, body, 'Announce')
261
262 const headers = buildGlobalHeaders(signedBody)
263
264 const { statusCode } = await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
265 expect(statusCode).to.equal(HttpStatusCode.NO_CONTENT_204)
266 })
267
268 it('Should refresh the actor keys', async function () {
269 this.timeout(20000)
270
271 // Wait refresh invalidation
272 await wait(10000)
273
274 // Update keys of server 3 to invalid keys
275 // Server 1 should refresh the actor and fail
276 await setKeysOfServer(servers[2], servers[2], invalidKeys.publicKey, invalidKeys.privateKey)
277
278 const body = getAnnounceWithoutContext(servers[1])
279 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
280
281 const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
282 const signedBody = await signAndContextify(signer, body, 'Announce')
283
284 const headers = buildGlobalHeaders(signedBody)
285
286 try {
287 await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
288 expect(true, 'Did not throw').to.be.false
289 } catch (err) {
290 expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
291 }
292 })
293 })
294
295 after(async function () {
296 this.timeout(10000)
297
298 await cleanupTests(servers)
299 })
300 })