]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/tests/api/activitypub/security.ts
Increase test timeout
[github/Chocobozzz/PeerTube.git] / server / tests / api / activitypub / security.ts
CommitLineData
a1587156 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
df66d815
C
2
3import 'mocha'
df66d815 4import * as chai from 'chai'
8dc8a34e 5import { buildDigest } from '@server/helpers/peertube-crypto'
c0e8b12e 6import { HTTP_SIGNATURE } from '@server/initializers/constants'
7e98a7df 7import { activityPubContextify } from '@server/lib/activitypub/context'
a219c910 8import { buildGlobalHeaders, signAndContextify } from '@server/lib/activitypub/send'
06aad801 9import { makeFollowRequest, makePOSTAPRequest } from '@server/tests/shared'
c55e3d72 10import { buildAbsoluteFixturePath, wait } from '@shared/core-utils'
c0e8b12e 11import { HttpStatusCode } from '@shared/models'
c55e3d72 12import { cleanupTests, createMultipleServers, killallServers, PeerTubeServer } from '@shared/server-commands'
df66d815
C
13
14const expect = chai.expect
15
254d3579 16function setKeysOfServer (onServer: PeerTubeServer, ofServer: PeerTubeServer, publicKey: string, privateKey: string) {
e7053b1d
C
17 const url = 'http://localhost:' + ofServer.port + '/accounts/peertube'
18
19 return Promise.all([
89d241a7
C
20 onServer.sql.setActorField(url, 'publicKey', publicKey),
21 onServer.sql.setActorField(url, 'privateKey', privateKey)
e7053b1d
C
22 ])
23}
24
254d3579 25function setUpdatedAtOfServer (onServer: PeerTubeServer, ofServer: PeerTubeServer, updatedAt: string) {
e7053b1d
C
26 const url = 'http://localhost:' + ofServer.port + '/accounts/peertube'
27
df66d815 28 return Promise.all([
89d241a7
C
29 onServer.sql.setActorField(url, 'createdAt', updatedAt),
30 onServer.sql.setActorField(url, 'updatedAt', updatedAt)
df66d815
C
31 ])
32}
33
254d3579 34function getAnnounceWithoutContext (server: PeerTubeServer) {
3d470a53 35 const json = require(buildAbsoluteFixturePath('./ap-json/peertube/announce-without-context.json'))
48f07b4a
C
36 const result: typeof json = {}
37
38 for (const key of Object.keys(json)) {
39 if (Array.isArray(json[key])) {
e7053b1d 40 result[key] = json[key].map(v => v.replace(':9002', `:${server.port}`))
48f07b4a 41 } else {
e7053b1d 42 result[key] = json[key].replace(':9002', `:${server.port}`)
48f07b4a
C
43 }
44 }
45
46 return result
df66d815
C
47}
48
49describe('Test ActivityPub security', function () {
254d3579 50 let servers: PeerTubeServer[]
df66d815
C
51 let url: string
52
3d470a53
C
53 const keys = require(buildAbsoluteFixturePath('./ap-json/peertube/keys.json'))
54 const invalidKeys = require(buildAbsoluteFixturePath('./ap-json/peertube/invalid-keys.json'))
48f07b4a 55 const baseHttpSignature = () => ({
df66d815
C
56 algorithm: HTTP_SIGNATURE.ALGORITHM,
57 authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME,
48f07b4a 58 keyId: 'acct:peertube@localhost:' + servers[1].port,
df66d815
C
59 key: keys.privateKey,
60 headers: HTTP_SIGNATURE.HEADERS_TO_SIGN
48f07b4a 61 })
df66d815
C
62
63 // ---------------------------------------------------------------
64
65 before(async function () {
66 this.timeout(60000)
67
254d3579 68 servers = await createMultipleServers(3)
df66d815
C
69
70 url = servers[0].url + '/inbox'
71
e7053b1d
C
72 await setKeysOfServer(servers[0], servers[1], keys.publicKey, null)
73 await setKeysOfServer(servers[1], servers[1], keys.publicKey, keys.privateKey)
df66d815 74
48f07b4a
C
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 }
df66d815
C
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 () {
a219c910 83 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
df66d815
C
84 const headers = {
85 Digest: buildDigest({ hello: 'coucou' })
86 }
87
b5c36108
C
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 }
df66d815
C
94 })
95
96 it('Should fail with an invalid date', async function () {
a219c910 97 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
df66d815
C
98 const headers = buildGlobalHeaders(body)
99 headers['date'] = 'Wed, 21 Oct 2015 07:28:00 GMT'
100
b5c36108
C
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 }
df66d815
C
107 })
108
109 it('Should fail with bad keys', async function () {
48f07b4a
C
110 await setKeysOfServer(servers[0], servers[1], invalidKeys.publicKey, invalidKeys.privateKey)
111 await setKeysOfServer(servers[1], servers[1], invalidKeys.publicKey, invalidKeys.privateKey)
df66d815 112
a219c910 113 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
df66d815
C
114 const headers = buildGlobalHeaders(body)
115
b5c36108
C
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 }
df66d815
C
122 })
123
797d05bd 124 it('Should reject requests without appropriate signed headers', async function () {
48f07b4a
C
125 await setKeysOfServer(servers[0], servers[1], keys.publicKey, keys.privateKey)
126 await setKeysOfServer(servers[1], servers[1], keys.publicKey, keys.privateKey)
df66d815 127
a219c910 128 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
df66d815 129 const headers = buildGlobalHeaders(body)
797d05bd
C
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
b5c36108
C
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 }
797d05bd
C
147 }
148 })
149
e08ec7a7
C
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
797d05bd 161 it('Should succeed with a valid HTTP signature', async function () {
a219c910 162 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
797d05bd 163 const headers = buildGlobalHeaders(body)
df66d815 164
db4b15f2 165 const { statusCode } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
db4b15f2 166 expect(statusCode).to.equal(HttpStatusCode.NO_CONTENT_204)
df66d815 167 })
095e2258
C
168
169 it('Should refresh the actor keys', async function () {
170 this.timeout(20000)
171
095e2258
C
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)
e7053b1d
C
175 await setUpdatedAtOfServer(servers[0], servers[1], '2015-07-17 22:00:00+00')
176
177 // Invalid peertube actor cache
9293139f 178 await killallServers([ servers[1] ])
254d3579 179 await servers[1].run()
095e2258 180
a219c910 181 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
095e2258
C
182 const headers = buildGlobalHeaders(body)
183
b5c36108
C
184 try {
185 await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
186 expect(true, 'Did not throw').to.be.false
187 } catch (err) {
e7053b1d 188 console.error(err)
b5c36108
C
189 expect(err.statusCode).to.equal(HttpStatusCode.FORBIDDEN_403)
190 }
095e2258 191 })
df66d815
C
192 })
193
194 describe('When checking Linked Data Signature', function () {
095e2258
C
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)
48f07b4a 200 await setKeysOfServer(servers[2], servers[2], keys.publicKey, keys.privateKey)
df66d815 201
48f07b4a
C
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 }
df66d815
C
204 await makeFollowRequest(to, by)
205 })
206
207 it('Should fail with bad keys', async function () {
208 this.timeout(10000)
209
48f07b4a
C
210 await setKeysOfServer(servers[0], servers[2], invalidKeys.publicKey, invalidKeys.privateKey)
211 await setKeysOfServer(servers[2], servers[2], invalidKeys.publicKey, invalidKeys.privateKey)
df66d815 212
48f07b4a
C
213 const body = getAnnounceWithoutContext(servers[1])
214 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
df66d815 215
48f07b4a 216 const signer: any = { privateKey: invalidKeys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
a219c910 217 const signedBody = await signAndContextify(signer, body, 'Announce')
df66d815
C
218
219 const headers = buildGlobalHeaders(signedBody)
220
b5c36108
C
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 }
df66d815
C
227 })
228
229 it('Should fail with an altered body', async function () {
230 this.timeout(10000)
231
48f07b4a
C
232 await setKeysOfServer(servers[0], servers[2], keys.publicKey, keys.privateKey)
233 await setKeysOfServer(servers[0], servers[2], keys.publicKey, keys.privateKey)
df66d815 234
48f07b4a
C
235 const body = getAnnounceWithoutContext(servers[1])
236 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
df66d815 237
48f07b4a 238 const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
a219c910 239 const signedBody = await signAndContextify(signer, body, 'Announce')
df66d815 240
48f07b4a 241 signedBody.actor = 'http://localhost:' + servers[2].port + '/account/peertube'
df66d815
C
242
243 const headers = buildGlobalHeaders(signedBody)
244
b5c36108
C
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 }
df66d815
C
251 })
252
253 it('Should succeed with a valid signature', async function () {
254 this.timeout(10000)
255
48f07b4a
C
256 const body = getAnnounceWithoutContext(servers[1])
257 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
df66d815 258
48f07b4a 259 const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
a219c910 260 const signedBody = await signAndContextify(signer, body, 'Announce')
df66d815
C
261
262 const headers = buildGlobalHeaders(signedBody)
263
db4b15f2 264 const { statusCode } = await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
db4b15f2 265 expect(statusCode).to.equal(HttpStatusCode.NO_CONTENT_204)
df66d815 266 })
095e2258
C
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' }
a219c910 282 const signedBody = await signAndContextify(signer, body, 'Announce')
095e2258
C
283
284 const headers = buildGlobalHeaders(signedBody)
285
b5c36108
C
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 }
095e2258 292 })
df66d815
C
293 })
294
295 after(async function () {
48f07b4a
C
296 this.timeout(10000)
297
298 await cleanupTests(servers)
df66d815
C
299 })
300})