]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/tests/api/activitypub/security.ts
Move test functions outside extra-utils
[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 { activityPubContextify, buildSignedActivity } from '@server/helpers/activitypub'
6 import { buildDigest } from '@server/helpers/peertube-crypto'
7 import { HTTP_SIGNATURE } from '@server/initializers/constants'
8 import { buildGlobalHeaders } from '@server/lib/job-queue/handlers/utils/activitypub-http-utils'
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]))
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]))
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]))
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]))
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', async function () {
151 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
152 const headers = buildGlobalHeaders(body)
153
154 const { statusCode } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
155 expect(statusCode).to.equal(HttpStatusCode.NO_CONTENT_204)
156 })
157
158 it('Should refresh the actor keys', async function () {
159 this.timeout(20000)
160
161 // Update keys of server 2 to invalid keys
162 // Server 1 should refresh the actor and fail
163 await setKeysOfServer(servers[1], servers[1], invalidKeys.publicKey, invalidKeys.privateKey)
164 await setUpdatedAtOfServer(servers[0], servers[1], '2015-07-17 22:00:00+00')
165
166 // Invalid peertube actor cache
167 await killallServers([ servers[1] ])
168 await servers[1].run()
169
170 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
171 const headers = buildGlobalHeaders(body)
172
173 try {
174 await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
175 expect(true, 'Did not throw').to.be.false
176 } catch (err) {
177 console.error(err)
178 expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
179 }
180 })
181 })
182
183 describe('When checking Linked Data Signature', function () {
184 before(async function () {
185 this.timeout(10000)
186
187 await setKeysOfServer(servers[0], servers[1], keys.publicKey, keys.privateKey)
188 await setKeysOfServer(servers[1], servers[1], keys.publicKey, keys.privateKey)
189 await setKeysOfServer(servers[2], servers[2], keys.publicKey, keys.privateKey)
190
191 const to = { url: 'http://localhost:' + servers[0].port + '/accounts/peertube' }
192 const by = { url: 'http://localhost:' + servers[2].port + '/accounts/peertube', privateKey: keys.privateKey }
193 await makeFollowRequest(to, by)
194 })
195
196 it('Should fail with bad keys', async function () {
197 this.timeout(10000)
198
199 await setKeysOfServer(servers[0], servers[2], invalidKeys.publicKey, invalidKeys.privateKey)
200 await setKeysOfServer(servers[2], servers[2], invalidKeys.publicKey, invalidKeys.privateKey)
201
202 const body = getAnnounceWithoutContext(servers[1])
203 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
204
205 const signer: any = { privateKey: invalidKeys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
206 const signedBody = await buildSignedActivity(signer, body)
207
208 const headers = buildGlobalHeaders(signedBody)
209
210 try {
211 await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
212 expect(true, 'Did not throw').to.be.false
213 } catch (err) {
214 expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
215 }
216 })
217
218 it('Should fail with an altered body', async function () {
219 this.timeout(10000)
220
221 await setKeysOfServer(servers[0], servers[2], keys.publicKey, keys.privateKey)
222 await setKeysOfServer(servers[0], servers[2], keys.publicKey, keys.privateKey)
223
224 const body = getAnnounceWithoutContext(servers[1])
225 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
226
227 const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
228 const signedBody = await buildSignedActivity(signer, body)
229
230 signedBody.actor = 'http://localhost:' + servers[2].port + '/account/peertube'
231
232 const headers = buildGlobalHeaders(signedBody)
233
234 try {
235 await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
236 expect(true, 'Did not throw').to.be.false
237 } catch (err) {
238 expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
239 }
240 })
241
242 it('Should succeed with a valid signature', async function () {
243 this.timeout(10000)
244
245 const body = getAnnounceWithoutContext(servers[1])
246 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
247
248 const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
249 const signedBody = await buildSignedActivity(signer, body)
250
251 const headers = buildGlobalHeaders(signedBody)
252
253 const { statusCode } = await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
254 expect(statusCode).to.equal(HttpStatusCode.NO_CONTENT_204)
255 })
256
257 it('Should refresh the actor keys', async function () {
258 this.timeout(20000)
259
260 // Wait refresh invalidation
261 await wait(10000)
262
263 // Update keys of server 3 to invalid keys
264 // Server 1 should refresh the actor and fail
265 await setKeysOfServer(servers[2], servers[2], invalidKeys.publicKey, invalidKeys.privateKey)
266
267 const body = getAnnounceWithoutContext(servers[1])
268 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
269
270 const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
271 const signedBody = await buildSignedActivity(signer, body)
272
273 const headers = buildGlobalHeaders(signedBody)
274
275 try {
276 await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
277 expect(true, 'Did not throw').to.be.false
278 } catch (err) {
279 expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
280 }
281 })
282 })
283
284 after(async function () {
285 this.timeout(10000)
286
287 await cleanupTests(servers)
288 })
289 })